WebSocket

详细信息请查看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。