github.com/binbinly/pkg@v0.0.11-0.20240321014439-f4fbf666eb0f/transport/ws/connect.go (about) 1 package ws 2 3 import ( 4 "context" 5 "net" 6 "net/http" 7 "sync" 8 "time" 9 10 "github.com/binbinly/pkg/logger" 11 "github.com/gorilla/websocket" 12 ) 13 14 // Connection 定义连接接口 15 type Connection interface { 16 Start() //启动连接,让当前连接开始工作 17 Stop() //停止连接,结束当前连接状态 18 Context() context.Context //返回ctx,用于用户自定义的go程获取连接退出状态 19 20 GetID() uint64 //获取当前连接ID 21 GetUID() int //获取当前连接鉴权ID 22 RemoteAddr() net.Addr //获取远程客户端地址信息 23 24 Send(ctx context.Context, mid int, data []byte) error //发送消息 25 AsyncSend(ctx context.Context, mid int, data []byte) error //异步发送消息 26 } 27 28 const ( 29 // Time allowed to read the next pong message from the peer. 30 pongWait = 60 * time.Second 31 32 // Send pings to peer with this period. Must be less than pongWait. 33 pingPeriod = (pongWait * 9) / 10 34 ) 35 36 // wsConnection 连接 37 type wsConnection struct { 38 mu sync.RWMutex 39 //当前连接的ID 也可以称作为SessionID,ID全局唯一 40 id uint64 41 uid int 42 //告知该链接已经退出/停止的channel 43 ctx context.Context 44 cancel context.CancelFunc 45 //消息管道,用于读、写两个goroutine之间的消息通信 46 msgChan chan []byte 47 //当前Conn属于哪个Server 48 server *wsServer 49 //websocket连接对象 50 conn *websocket.Conn 51 //websocket响应对象 52 writer http.ResponseWriter 53 //websocket请求对象 54 request *http.Request 55 //当前连接的关闭状态 56 isClosed bool 57 } 58 59 // NewConnect 创建连接的方法 60 func NewConnect(s *wsServer, conn *websocket.Conn, id uint64, uid int) Connection { 61 return &wsConnection{ 62 server: s, 63 id: id, 64 uid: uid, 65 conn: conn, 66 msgChan: make(chan []byte, s.Options().MaxMsgChanLen), 67 } 68 } 69 70 // startWriter 写消息Goroutine, 用户将数据发送给客户端 71 func (c *wsConnection) startWriter() { 72 logger.Debug("[ws.write] Writer Goroutine is running") 73 // 心跳由客户端发送 ping 回复 pong 74 ticker := time.NewTicker(pingPeriod) 75 defer func() { 76 defer logger.Debugf("[ws.write] %v conn Writer exit!", c.RemoteAddr().String()) 77 ticker.Stop() 78 }() 79 80 for { 81 select { 82 case data, ok := <-c.msgChan: 83 if !ok { 84 _ = c.conn.WriteMessage(websocket.CloseMessage, []byte("close")) 85 return 86 } 87 _ = c.conn.SetWriteDeadline(time.Now().Add(c.server.Options().WriteWait)) 88 logger.Debugf("[ws.write] write msg:%v", string(data)) 89 if err := c.conn.WriteMessage(websocket.TextMessage, data); err != nil { 90 logger.Warnf("[ws.write] conn.write err :%v", err) 91 break 92 } 93 case <-c.ctx.Done(): 94 return 95 case <-ticker.C: 96 _ = c.conn.SetWriteDeadline(time.Now().Add(c.server.Options().WriteWait)) 97 if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { 98 logger.Warnf("[ws.write] ticker ping wrr:%v", err) 99 return 100 } 101 } 102 } 103 } 104 105 // startReader 读消息Goroutine,用于从客户端中读取数据 106 func (c *wsConnection) startReader() { 107 logger.Debug("[ws.read] Reader Goroutine is running") 108 defer func() { 109 logger.Debugf("[ws.read] %v conn Reader exit!", c.RemoteAddr().String()) 110 c.Stop() 111 }() 112 113 c.conn.SetReadLimit(int64(c.server.Options().MaxPacketSize)) 114 _ = c.conn.SetReadDeadline(time.Now().Add(pongWait)) 115 c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) 116 117 for { 118 select { 119 case <-c.ctx.Done(): 120 return 121 default: 122 mType, buff, err := c.conn.ReadMessage() 123 if err != nil { 124 logger.Infof("[ws.read] readPump ReadMessage err:%V", err) 125 c.cancel() 126 return 127 } 128 if mType == websocket.PingMessage { 129 continue 130 } 131 if len(buff) == 0 { 132 continue 133 } 134 logger.Debugf("[ws.read] reader message:%v", string(buff)) 135 if string(buff) == "ping" { 136 _ = c.AsyncSend(context.Background(), 0, []byte("pong")) 137 } else { 138 // 构建当前客户端请求的request数据 139 req, err := NewRequest(c, buff) 140 if err != nil { 141 logger.Warnf("[ws.read] data format err:%v", err) 142 return 143 } 144 c.server.handler.AsyncExecute(req) 145 } 146 } 147 } 148 } 149 150 // Start 启动连接,让当前连接开始工作 151 func (c *wsConnection) Start() { 152 c.ctx, c.cancel = context.WithCancel(context.Background()) 153 154 if c.server.Options().OnConnStart != nil { 155 c.server.Options().OnConnStart(c) 156 } 157 158 // 开启用户从客户端读取数据的Goroutine 159 go c.startReader() 160 // 开启用于写回客户端数据的Goroutine 161 go c.startWriter() 162 } 163 164 // Stop 关闭连接 165 func (c *wsConnection) Stop() { 166 c.mu.Lock() 167 defer c.mu.Unlock() 168 169 //如果当前链接已经关闭 170 if c.isClosed == true { 171 return 172 } 173 174 //如果用户注册了该连接的关闭回调业务,那么在此调用 175 if c.server.Options().OnConnStop != nil { 176 c.server.Options().OnConnStop(c) 177 } 178 179 //关闭socket连接 180 if err := c.conn.Close(); err != nil { 181 logger.Warnf("[ws.stop] connection closed err:%v", err) 182 } 183 184 //关闭Writer 185 c.cancel() 186 // 将连接从连接管理器中删除 187 c.server.GetManager(c.id).Remove(c) 188 // 关闭该连接全部管道 189 close(c.msgChan) 190 //设置标志位 191 c.isClosed = true 192 } 193 194 func (c *wsConnection) Context() context.Context { 195 return c.ctx 196 } 197 198 // GetID 获取连接id 199 func (c *wsConnection) GetID() uint64 { 200 return c.id 201 } 202 203 // GetUID 获取连接id 204 func (c *wsConnection) GetUID() int { 205 return c.uid 206 } 207 208 // RemoteAddr 获取远程客户端地址信息 209 func (c *wsConnection) RemoteAddr() net.Addr { 210 return c.conn.RemoteAddr() 211 } 212 213 // Send 发送数据给远程的WS客户端 214 func (c *wsConnection) Send(ctx context.Context, mid int, msg []byte) error { 215 if c.isClosed == true { 216 return ErrConnNotFinish 217 } 218 219 //写回客户端 220 if err := c.conn.WriteMessage(websocket.TextMessage, msg); err != nil { 221 return err 222 } 223 return nil 224 } 225 226 // AsyncSend 异步发送数据给远程的WS客户端 227 func (c *wsConnection) AsyncSend(ctx context.Context, mid int, msg []byte) error { 228 if c.isClosed == true { 229 return ErrConnNotFinish 230 } 231 232 //写回客户端 233 c.msgChan <- msg 234 return nil 235 }