详细信息请查看rfc6455。
Websocket协议握手基于http协议Upgrade机制。
golang Websocket依靠http.Hijacker接口,获得net.Conn的tcp连接,然后利用tcp协议连接封装生成WebSocket协议,重新定义协议行为。
Upgrade
ws握手使用http的Upgrade机制
例如一次ws握手请求header
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com
重点如下两个 header:
Upgrade: websocket
Connection: Upgrade
Upgrade 表示升级到 WebSocket 协议,Connection 表示这个 HTTP 请求是一次协议升级,Origin 表示发请求的来源。
Sec-WebSocket-Key是一个随机值,服务端需要返回Sec-Websocket-Accept这个header,值为Sec-WebSocket-Key的值加固定字符串"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",然后计算sha1.
Sec-WebSocket-Protocol是支持的子协议。
Sec-WebSocket-Version是ws版本,一般都是13.
wss握手除了了ws握手外,tls扩展协议为http/1.1,避免建立了h2连接后无法使用http Upgrade.
golang upgrade
在golang标准库实现ws协议握手时,需要使用http.Hijacker接口,该接口的作用是劫持连接,这样就直接获得了tcp连接,在Hijack后,net/http就会忽略这个连接。
在Hijack获得tcp连接后,可以使用开始读取到的请求信息来完整握手,不论握手结果如何,都是直接写tcp连接写入http格式的响应,告诉客户端握手结果。
通常握手成功后就会for循环操作ws连接。
*Websocket.Connnet.Conn
golang.org/x/net/websocketgithub.com/gorilla/websocketgithub.com/gobwas/ws
消息帧
websocket协议是二进制分帧传输,目前标准有继续帧、二进制帧、文本帧、ping帧、pong帧、close帧六种。
帧结构
该图来源于rfc5.2章。
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
fin是第一个bit,表示是否是最后一个分片,如果是0就是最后一个。
rsv123,是三个标志位一般都是0,如果使用协议扩展,可能非0,需要客户端和服务端协商好。
opcode就帧的类型,目前使用了六种,其他作为保留,0x0延续帧,0x1文本帧,0x2二进制帧,0x8关闭帧,0x9 ping帧,0xa ping帧。
mask 表示是否使用掩码,客户端向服务端发送消息需要掩码mask=1,服务端返回消息不要掩码mask=1,该项要求在rfc有说明。
Payload length为数据长度,有三种含义,0-125是数据长度,126表示后续两byte是一个16位无符号整数为长度,127表示后续8 byte是一个64位无符号整数为长度(最高有效位必须是0),
往后就是payload扩展长度,如果mask=1后续就有4位是掩码的长度。
最后是Payload Data,就是帧的数据,前面都是固定的header结构和长度,Payload的长度在header中可以解析到,一个完整的帧就是header+payload,通过长度来实现帧边界分割。
ws帧的结构基本就是标志位、掩码数据、长度、数据四块,标志位一般都可以忽略,掩码在需要解析数据时使用,剩下就是数据长度和数据了,帧固定结构位置读取到了数据长度,就然后知道了数据的结束位置,不会tcp粘包。
帧场景
继续帧:上一次数据写入部分,然后继续帧发送后续数据,gobwas库example有使用。
文本帧 & 二进制帧:发送数据,有些特殊字符无法使用文本帧发送。
关闭帧:一般在连接关闭时发送,一般阅览器会自动发送关闭帧。
ping帧 & pong帧:发送心跳包,部分阅览器在收到ping帧后会自动回复pong帧。
反馈和交流请加群组:QQ群373278915。