ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

websocket基本使用

websocket基本使用 参考1使用Go语言创建WebSocket服务器和客户端WebSocket 是一种新型的网络通信协议可以在 Web 应用程序中 - 掘金参考2websocket的连接原理WebSocket 是一种全双工通信协议基于 TCP 连接实现允许客户端和服务器之间建立 - 掘金参考3RFC 6455: The WebSocket Protocol | RFC Editor我们来写一个最简单的golang服务端package main import ( log net/http time github.com/gorilla/websocket ) var upgrader websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { return true }, } func main() { http.HandleFunc(/wspath, handleWebSocket) log.Println(Listening on :8080) log.Fatal(http.ListenAndServe(:8080, nil)) } func handleWebSocket(w http.ResponseWriter, r *http.Request) { conn, err : upgrader.Upgrade(w, r, nil) if err ! nil { log.Println(err) return } defer conn.Close() for { messageType, p, err : conn.ReadMessage() if err ! nil { log.Println(ReadMessage: , err) return } log.Println(Received message:, string(p)) err conn.WriteMessage(messageType, []byte(hello client)) if err ! nil { log.Println(WriteMessage: , err) return } } }再写一个最简单的网页作为客户端!DOCTYPE html html head meta charsetutf-8 titleWebSocket Client/title /head body div idroot input idinput typetext valuehello, server / button idsendSend/button div idoutput/div /div script const ws new WebSocket(ws://127.0.0.1:8080/wspath); // 新建对象等于连接 ws.onopen () { console.log(Connected!); } ws.onmessage (e) { console.log(Received: e.data); document.querySelector(#output).innerHTML e.data } ws.onclose (e) { console.log(Closed: e.data); } ws.onerror (e) { console.log(Error: e.data); } document.querySelector(#send).addEventListener(click, (e) { const data JSON.stringify({ type: text, text: document.getElementById(input).value }) console.log(To Send: data); ws.send(data) }) /script /body /html测试方式go run main.go 运行服务端再用浏览器打开网页客户端文件方式打开就能工作点击网页客户端按钮发送消息到服务端。服务端的显示如下步骤4服务器在接收客户消息后发送消息给客户d:\GoPath\src\websocketTestgo run main.go 2026/10/08 16:06:28 Listening on :8080 步骤0服务器监听指定端口 2026/10/08 16:06:52 Received message: {type:text,text:hello, server} 步骤3接收到客户消息 2026/10/08 16:21:17 websocket: close 1001 (going away) 步骤7关闭连接网页客户端控制台的显示如下步骤6关闭网页客户主动关闭连接client.html:16 Connected! 步骤1打开网页连接上服务端 client.html:33 To Send: {type:text,text:hello, server} 步骤2发送消息到服务端 client.html:19 Received: hello client 步骤5收到服务端消息如果服务端强制退出网页客户端控制台显示Closed: undefined上面的流程中服务端表现得还是比较被动的即它只是在收到客户端消息后对应回复一条消息。但实际websocket是完全双向的长连接。我们插入以下代码来观察服务端主动给客户端发送消息每间隔几秒就把时间发送给客户端//......................................... defer conn.Close() go func() { for { time.Sleep(5 * time.Second) err : conn.WriteMessage(websocket.TextMessage, []byte(time.Now().Format(time.RFC3339))) if err ! nil { log.Println(WriteMessage time:, err) return } } }() for { //.........................................这样服务端会每隔几秒就把时间发给客户端只要客户端连接了无论客户端有没有主动发送消息。我们的处理是简化的所以客户端接收到服务端的hello, client还是当前时间都是显示在div#output 中的。真实的处理中应该自己设计消息格式如JSON中用 msgtype 之类的字段区分消息类别然后 ws.onmessage 中解析JSON并分门别类处理消息如显示到不同的位置。另外要注意的一点是如果刷新页面服务端会输出错误信息2026/10/09 08:48:31 ReadMessage: websocket: close 1001 (going away) 2026/10/09 08:48:33 WriteMessage time: websocket: close sent也就是服务端认为客户断开连接了所以关闭了对应连接。这时会发现页面上仍然能收到时间戳消息这是因为刷新页面和新打开页面一样都会new一个WebSocket对象本质上就是新建了一个连接对服务端来说http.HandleFunc 为每个连接创建了 go routine客户端收到的时间戳是新的go routine 中的 handleWebSocket 其中的 go func发送给它的。可以回顾 golang web学习随便记1-快速入门_golang 背记手册-CSDN博客 和 golang学习随便记11-goroutine和channel3_golang os.stdin.read(make([]byte, 1))-CSDN博客我们用WireShark来学习以下WebSocket的原理因为我们是发到本机所以适配器选择loopback过滤器设定为 tcp.port8080 如果设置为 websocket and tcp.port8080 那就只能看到upgrade之后的通信了先是TCP握手过程客户端端口61006 to 服务端端口8080 SYN8080 to 61006 SYNACK 61006 to 8080 ACK。然后是 HTTP GET /wspath HTTP/1.1和一般的HTTP GET请求相比值得关注的头部字段是Connection、Upgrade、Sec-WebSocket-Version、Sec-WebSocket-Key、Sec-WebSocket-Extensions。GET /wspath HTTP/1.1\r\n Host: 127.0.0.1:8080\r\n Connection: Upgrade\r\n Pragma: no-cache\r\n Cache-Control: no-cache\r\n User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/154.0.0.0 Safari/537.36 Edg/154.0.0.0\r\n Upgrade: websocket\r\n Origin: null\r\n Sec-WebSocket-Version: 13\r\n Accept-Encoding: gzip, deflate, br, zstd\r\n Accept-Language: zh-CN,zh;q0.9,en;q0.8,en-GB;q0.7,en-US;q0.6\r\n Sec-WebSocket-Key: xyKtSMPj/xsIpMarwN7eEg\r\n Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n \r\n服务端响应 TCP 8080 to 61006 ACK 后是HTTP响应101 Switching Protocols 切换协议其中 Sec-WebSocket-Accept 的值是根据客户端发送的 Sec-WebSocket-Key 值计算得到计算方式是后者拼接固定的字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11后计算SHA1散列的二进制值20字节再用BASE64编码得到前者。HTTP/1.1 101 Switching Protocols\r\n Upgrade: websocket\r\n Connection: Upgrade\r\n Sec-WebSocket-Accept: hIb4DHYsBmjOKIAnwJ2sh5kXtxY\r\n \r\n客户端 TCP 61006 to 8080 ACK 响应。服务端发送 WebSocket 帧下图中81 19开始的部分1bit Finish标记3bit 扩展保留4bit Opcode操作码类型1bit 掩码指示7bit 负载长度如果是126就是716bit 负载长度是127就是764bit 负载长度掩码key没有或者4字节有效负载数据应用数据之前可能有扩展数据。81 19 1/000/ 0001/ 0/001 1001/ 这是消息帧最后片段操作码0x1表示这是文本帧无掩码key负载长度25字节2026-10-09T10:09:5308:00的长度0000 02 00 00 00 45 00 00 43 1e 25 40 00 80 06 00 00 ....E..C.%..... 0010 7f 00 00 01 7f 00 00 01 1f 90 ee 4e fb 5a 9a 47 ...........N.Z.G 0020 17 e8 45 46 50 18 27 f9 59 81 00 00 81 19 32 30 ..EFP..Y.....20 0030 32 36 2d 31 30 2d 30 39 54 31 30 3a 30 39 3a 35 26-10-09T10:09:5 0040 33 2b 30 38 3a 30 30 308:00客户端发送的 WebSocket 帧下图中 81 a6开始的部分带掩码81 a6 1/000/ 0001/ 1/010 0110 FIN最后片段0x1文本帧有掩码负载长度 0b10 01100x2638字节。掩码keydf 13 39 e1。负载数据 a4 31 4d 98 ... 5c 93 fd 6e 共38字节。我们把负载数据按字节和掩码key进行异或操作就可以得到实际负载了例如 df XOR a47b, 13 XOR 3122 分别是{和的ASCII码。0000 02 00 00 00 45 00 00 54 1e 45 40 00 80 06 00 00 ....E..T.E..... 0010 7f 00 00 01 7f 00 00 01 ee 4e 1f 90 17 e8 45 46 .........N....EF 0020 fb 5a 9a 62 50 18 27 f8 bb 40 00 00 81 a6 df 13 .Z.bP......... 0030 39 e1 a4 31 4d 98 af 76 1b db fd 67 5c 99 ab 31 9..1M..v...g\..1 0040 15 c3 ab 76 41 95 fd 29 1b 89 ba 7f 55 8e f3 33 ...vA..)....U..3 0050 4a 84 ad 65 5c 93 fd 6e J..e\..n上面2段都是数据帧除了数据帧还有控制帧用操作码区分如关闭0x8心跳ping 0x9心跳pong 0xa。从HTTP到WebSocket协议升级请求是客户端主动发起的用HTTP GET请求服务端用 gorilla/websocket 的 websocket.Upgrader.Upgrade()方法处理这个请求时会对 GET 请求头部中的信息进行必要检查之后先接管 w 这个 http.ResponseWriter 对应的连接再根据其创建“改造成”新的连接。具体可以参考 gorilla/websocket 库中 server.go、conn.goVue项目中刷新页面导致的 WebSocket 断开连接是否需要重新连接可以参考 关于在vue项目中,刷新页面时websocket断开连接的解决办法_websocket页面刷新会断开连接吗-CSDN博客采用了类似认定已经登录那么刷新页面就重新连接的思路。对于服务端来说要认定消息来源还是需要采用类似会话控制的方式才能在新的连接中继续会话。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进