github.com/Mrs4s/MiraiGo@v0.0.0-20240226124653-54bdd873e3fe/client/internal/highway/highway.go (about) 1 package highway 2 3 import ( 4 "fmt" 5 "net" 6 "runtime" 7 "sync" 8 "sync/atomic" 9 "time" 10 11 "github.com/pkg/errors" 12 13 "github.com/Mrs4s/MiraiGo/binary" 14 "github.com/Mrs4s/MiraiGo/client/pb" 15 "github.com/Mrs4s/MiraiGo/internal/proto" 16 ) 17 18 // see com/tencent/mobileqq/highway/utils/BaseConstants.java#L120-L121 19 const ( 20 _REQ_CMD_DATA = "PicUp.DataUp" 21 _REQ_CMD_HEART_BREAK = "PicUp.Echo" 22 ) 23 24 type Addr struct { 25 IP uint32 26 Port int 27 } 28 29 func (a Addr) AsNetIP() net.IP { 30 return net.IPv4(byte(a.IP>>24), byte(a.IP>>16), byte(a.IP>>8), byte(a.IP)) 31 } 32 33 func (a Addr) String() string { 34 return fmt.Sprintf("%v:%v", binary.UInt32ToIPV4Address(a.IP), a.Port) 35 } 36 37 func (a Addr) empty() bool { 38 return a.IP == 0 || a.Port == 0 39 } 40 41 type Session struct { 42 Uin string 43 AppID int32 44 SigSession []byte 45 SessionKey []byte 46 47 seq int32 48 49 addrMu sync.Mutex 50 idx int 51 SsoAddr []Addr 52 53 idleMu sync.Mutex 54 idleCount int 55 idle *idle 56 } 57 58 const highwayMaxResponseSize int32 = 1024 * 100 // 100k 59 60 func (s *Session) AddrLength() int { 61 s.addrMu.Lock() 62 defer s.addrMu.Unlock() 63 return len(s.SsoAddr) 64 } 65 66 func (s *Session) AppendAddr(ip, port uint32) { 67 s.addrMu.Lock() 68 defer s.addrMu.Unlock() 69 addr := Addr{ 70 IP: ip, 71 Port: int(port), 72 } 73 s.SsoAddr = append(s.SsoAddr, addr) 74 } 75 76 func (s *Session) nextSeq() int32 { 77 return atomic.AddInt32(&s.seq, 2) 78 } 79 80 func (s *Session) sendHeartbreak(conn net.Conn) error { 81 head, _ := proto.Marshal(&pb.ReqDataHighwayHead{ 82 MsgBasehead: &pb.DataHighwayHead{ 83 Version: 1, 84 Uin: s.Uin, 85 Command: _REQ_CMD_HEART_BREAK, 86 Seq: s.nextSeq(), 87 Appid: s.AppID, 88 Dataflag: 4096, 89 CommandId: 0, 90 LocaleId: 2052, 91 }, 92 }) 93 buffers := frame(head, nil) 94 _, err := buffers.WriteTo(conn) 95 return err 96 } 97 98 func (s *Session) ping(pc *persistConn) error { 99 start := time.Now() 100 err := s.sendHeartbreak(pc.conn) 101 if err != nil { 102 return errors.Wrap(err, "echo error") 103 } 104 if _, err = readResponse(binary.NewNetworkReader(pc.conn)); err != nil { 105 return errors.Wrap(err, "echo error") 106 } 107 // update delay 108 pc.ping = time.Since(start).Milliseconds() 109 return nil 110 } 111 112 func readResponse(r *binary.NetworkReader) (*pb.RspDataHighwayHead, error) { 113 _, err := r.ReadByte() 114 if err != nil { 115 return nil, errors.Wrap(err, "failed to read byte") 116 } 117 hl, _ := r.ReadInt32() 118 a2, _ := r.ReadInt32() 119 if hl > highwayMaxResponseSize || a2 > highwayMaxResponseSize { 120 return nil, errors.Errorf("highway response invild. head size: %v body size: %v", hl, a2) 121 } 122 head, _ := r.ReadBytes(int(hl)) 123 _, _ = r.ReadBytes(int(a2)) // skip payload 124 _, _ = r.ReadByte() 125 rsp := new(pb.RspDataHighwayHead) 126 if err = proto.Unmarshal(head, rsp); err != nil { 127 return nil, errors.Wrap(err, "failed to unmarshal protobuf message") 128 } 129 return rsp, nil 130 } 131 132 type persistConn struct { 133 conn net.Conn 134 addr Addr 135 ping int64 // echo ping 136 } 137 138 const maxIdleConn = 7 139 140 type idle struct { 141 pc persistConn 142 next *idle 143 } 144 145 // getIdleConn ... 146 func (s *Session) getIdleConn() persistConn { 147 s.idleMu.Lock() 148 defer s.idleMu.Unlock() 149 150 // no idle 151 if s.idle == nil { 152 return persistConn{} 153 } 154 155 // switch the fastest idle conn 156 conn := s.idle.pc 157 s.idle = s.idle.next 158 s.idleCount-- 159 if s.idleCount < 0 { 160 panic("idle count underflow") 161 } 162 163 return conn 164 } 165 166 func (s *Session) putIdleConn(pc persistConn) { 167 s.idleMu.Lock() 168 defer s.idleMu.Unlock() 169 170 // check persistConn 171 if pc.conn == nil || pc.addr.empty() { 172 panic("put bad idle conn") 173 } 174 175 cur := &idle{pc: pc} 176 s.idleCount++ 177 if s.idle == nil { // quick path 178 s.idle = cur 179 return 180 } 181 182 // insert between pre and succ 183 var pre, succ *idle 184 succ = s.idle 185 for succ != nil && succ.pc.ping < pc.ping { // keep idle list sorted by delay incremental 186 pre = succ 187 succ = succ.next 188 } 189 if pre != nil { 190 pre.next = cur 191 } 192 cur.next = succ 193 194 // remove the slowest idle conn if idle count greater than maxIdleConn 195 if s.idleCount > maxIdleConn { 196 for cur.next != nil { 197 pre = cur 198 cur = cur.next 199 } 200 pre.next = nil 201 s.idleCount-- 202 } 203 } 204 205 func (s *Session) connect(addr Addr) (persistConn, error) { 206 conn, err := net.DialTimeout("tcp", addr.String(), time.Second*3) 207 if err != nil { 208 return persistConn{}, err 209 } 210 _ = conn.(*net.TCPConn).SetKeepAlive(true) 211 212 // close conn 213 runtime.SetFinalizer(conn, func(conn net.Conn) { 214 _ = conn.Close() 215 }) 216 217 pc := persistConn{conn: conn, addr: addr} 218 if err = s.ping(&pc); err != nil { 219 return persistConn{}, err 220 } 221 return pc, nil 222 } 223 224 func (s *Session) nextAddr() Addr { 225 s.addrMu.Lock() 226 defer s.addrMu.Unlock() 227 addr := s.SsoAddr[s.idx] 228 s.idx = (s.idx + 1) % len(s.SsoAddr) 229 return addr 230 } 231 232 func (s *Session) selectConn() (pc persistConn, err error) { 233 for { // select from idle pc 234 pc = s.getIdleConn() 235 if pc.conn == nil { 236 // no idle connection 237 break 238 } 239 240 err = s.ping(&pc) // ping 241 if err == nil { 242 return 243 } 244 } 245 246 try := 0 247 for { 248 addr := s.nextAddr() 249 pc, err = s.connect(addr) 250 if err == nil { 251 break 252 } 253 try++ 254 if try > 5 { 255 break 256 } 257 } 258 return 259 }