package connect import ( "container/list" "context" "gim/config" "gim/pkg/grpclib" "gim/pkg/logger" "gim/pkg/pb" "gim/pkg/rpc" "sync" "time" "go.uber.org/zap" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" "github.com/alberliu/gn" "github.com/gorilla/websocket" ) const ( CoonTypeTCP int8 = 1 // tcp连接 ConnTypeWS int8 = 2 // websocket连接 ) type Conn struct { CoonType int8 // 连接类型 TCP *gn.Conn // tcp连接 WSMutex sync.Mutex // WS写锁 WS *websocket.Conn // websocket连接 UserId int64 // 用户ID DeviceId int64 // 设备ID RoomId int64 // 订阅的房间ID Element *list.Element // 链表节点 } // Write 写入数据 func (c *Conn) Write(bytes []byte) error { if c.CoonType == CoonTypeTCP { return c.TCP.WriteWithEncoder(bytes) } else if c.CoonType == ConnTypeWS { return c.WriteToWS(bytes) } logger.Logger.Error("unknown conn type", zap.Any("conn", c)) return nil } // WriteToWS 消息写入WebSocket func (c *Conn) WriteToWS(bytes []byte) error { c.WSMutex.Lock() defer c.WSMutex.Unlock() err := c.WS.SetWriteDeadline(time.Now().Add(10 * time.Millisecond)) if err != nil { return err } return c.WS.WriteMessage(websocket.BinaryMessage, bytes) } // Close 关闭 func (c *Conn) Close() error { // 取消设备和连接的对应关系 if c.DeviceId != 0 { DeleteConn(c.DeviceId) } // 取消订阅,需要异步出去,防止重复加锁造成死锁 go func() { SubscribedRoom(c, 0) }() if c.DeviceId != 0 { _, _ = rpc.GetLogicIntClient().Offline(context.TODO(), &pb.OfflineReq{ UserId: c.UserId, DeviceId: c.DeviceId, ClientAddr: c.GetAddr(), }) } if c.CoonType == CoonTypeTCP { c.TCP.Close() } else if c.CoonType == ConnTypeWS { return c.WS.Close() } return nil } func (c *Conn) GetAddr() string { if c.CoonType == CoonTypeTCP { return c.TCP.GetAddr() } else if c.CoonType == ConnTypeWS { return c.WS.RemoteAddr().String() } return "" } // HandleMessage 消息处理 func (c *Conn) HandleMessage(bytes []byte) { var input = new(pb.Input) err := proto.Unmarshal(bytes, input) if err != nil { logger.Logger.Error("unmarshal error", zap.Error(err)) return } logger.Logger.Debug("HandleMessage", zap.Any("input", input)) // 对未登录的用户进行拦截 if input.Type != pb.PackageType_PT_SIGN_IN && c.UserId == 0 { // 应该告诉用户没有登录 return } switch input.Type { case pb.PackageType_PT_SIGN_IN: c.SignIn(input) case pb.PackageType_PT_SYNC: c.Sync(input) case pb.PackageType_PT_HEARTBEAT: c.Heartbeat(input) case pb.PackageType_PT_MESSAGE: c.MessageACK(input) case pb.PackageType_PT_SUBSCRIBE_ROOM: c.SubscribedRoom(input) default: logger.Logger.Error("handler switch other") } } // Send 下发消息 func (c *Conn) Send(pt pb.PackageType, requestId int64, message proto.Message, err error) { var output = pb.Output{ Type: pt, RequestId: requestId, } if err != nil { status, _ := status.FromError(err) output.Code = int32(status.Code()) output.Message = status.Message() } if message != nil { msgBytes, err := proto.Marshal(message) if err != nil { logger.Sugar.Error(err) return } output.Data = msgBytes } outputBytes, err := proto.Marshal(&output) if err != nil { logger.Sugar.Error(err) return } err = c.Write(outputBytes) if err != nil { logger.Sugar.Error(err) c.Close() return } } // SignIn 登录 func (c *Conn) SignIn(input *pb.Input) { var signIn pb.SignInInput err := proto.Unmarshal(input.Data, &signIn) if err != nil { logger.Sugar.Error(err) return } _, err = rpc.GetLogicIntClient().ConnSignIn(grpclib.ContextWithRequestId(context.TODO(), input.RequestId), &pb.ConnSignInReq{ UserId: signIn.UserId, DeviceId: signIn.DeviceId, Token: signIn.Token, ConnAddr: config.LocalAddr, ClientAddr: c.GetAddr(), }) c.Send(pb.PackageType_PT_SIGN_IN, input.RequestId, nil, err) if err != nil { return } c.UserId = signIn.UserId c.DeviceId = signIn.DeviceId SetConn(signIn.DeviceId, c) } // Sync 消息同步 func (c *Conn) Sync(input *pb.Input) { var sync pb.SyncInput err := proto.Unmarshal(input.Data, &sync) if err != nil { logger.Sugar.Error(err) return } resp, err := rpc.GetLogicIntClient().Sync(grpclib.ContextWithRequestId(context.TODO(), input.RequestId), &pb.SyncReq{ UserId: c.UserId, DeviceId: c.DeviceId, Seq: sync.Seq, }) var message proto.Message if err == nil { message = &pb.SyncOutput{Messages: resp.Messages, HasMore: resp.HasMore} } c.Send(pb.PackageType_PT_SYNC, input.RequestId, message, err) } // Heartbeat 心跳 func (c *Conn) Heartbeat(input *pb.Input) { c.Send(pb.PackageType_PT_HEARTBEAT, input.RequestId, nil, nil) logger.Sugar.Infow("heartbeat", "device_id", c.DeviceId, "user_id", c.UserId) } // MessageACK 消息收到回执 func (c *Conn) MessageACK(input *pb.Input) { var messageACK pb.MessageACK err := proto.Unmarshal(input.Data, &messageACK) if err != nil { logger.Sugar.Error(err) return } _, _ = rpc.GetLogicIntClient().MessageACK(grpclib.ContextWithRequestId(context.TODO(), input.RequestId), &pb.MessageACKReq{ UserId: c.UserId, DeviceId: c.DeviceId, DeviceAck: messageACK.DeviceAck, ReceiveTime: messageACK.ReceiveTime, }) } // SubscribedRoom 订阅房间 func (c *Conn) SubscribedRoom(input *pb.Input) { var subscribeRoom pb.SubscribeRoomInput err := proto.Unmarshal(input.Data, &subscribeRoom) if err != nil { logger.Sugar.Error(err) return } SubscribedRoom(c, subscribeRoom.RoomId) c.Send(pb.PackageType_PT_SUBSCRIBE_ROOM, input.RequestId, nil, nil) _, err = rpc.GetLogicIntClient().SubscribeRoom(context.TODO(), &pb.SubscribeRoomReq{ UserId: c.UserId, DeviceId: c.DeviceId, RoomId: subscribeRoom.RoomId, Seq: subscribeRoom.Seq, ConnAddr: config.LocalAddr, }) if err != nil { logger.Logger.Error("SubscribedRoom error", zap.Error(err)) } }