Websoket学习
这是我参与「第五届青训营 」伴学笔记创作活动的第 9 天
简介
WebSocket 是基于TCP/IP协议,独立于HTTP协议的通信协议。
双向通讯,有状态,客户端一(多)个与服务端一(多)双向实时响应(客户端 ⇄ 服务端)。
应用在浏览器的 Socket (是 Socket 模型接口的实现),Socket 是一个网络通信接口 (通信规范)。
协议端口80,SSL协议端口是443。
Socket是TCP/IP协议的网络数据通讯接口(一种底层的通讯的方式)。
Socket是IP地址和端口号的组合。例如:192.168.1.100:8080。
WebSocket可以实现客户端与服务器间双向、基于消息的文本或二进制数据传输。是浏览器中最靠近套接字的API,但WebSocket连接远远不是一个网络套接字,因为浏览器在这个简单的API之后隐藏了所有的复杂性,而且还提供了更多服务:
连接协商和同源策略
与既有HTTP基础设施的互操作
基于消息的通信和高效消息分帧
子协议协商及可扩展能力
WebSocket是浏览器中最通用最灵活的一个传输机制,其极简的API可以让我们在客户端和服务器之间以数据流的形式实现各种应用数据交换(包括JSON及自定义的二进制消息格式),而且两端都可以向另一端发送数据。
优点
较少的控制开销,连接建立后,客户端和服务器之间交换数据时,用于协议控制的数据包头部相对较小,相对于HTTP每次请求都要携带完整的头部,此项开销显著减少了
更强的实时性,由于协议是全双工的,服务器可以随时主动给客户端下发数据。相对于HTTP请求需要客户端发送请求服务端才能响应,延迟明显减少
长连接保持连接状态,与HTTP不同的是,WebSocket需要先创建连接,这使得它成为一种有状态的协议,之后通信就可以忽略状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)
双向通信、更好的二进制支持。与HTTP协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用HTTP协议,因此握手时不容易被屏蔽,能通过各种HTTP代理服务器
状态码
连接成功状态码
101:HTTP协议切换为WebSocket协议。
连接关闭状态码
1000:正常断开连接。
1001:服务器断开连接。
1002:websocket协议错误。
1003:客户端接受了不支持数据格式(只允许接受文本消息,不允许接受二进制数据,是客户端限制不接受二进制数据,而不是websocket协议不支持二进制数据)。
1006:异常关闭。
1007:客户端接受了无效数据格式(文本消息编码不是utf-8)。
1009:传输数据量过大。
1010:客户端终止连接。
1011:服务器终止连接。
1012:服务端正在重新启动。
1013:服务端临时终止。
1014:通过网关或代理请求服务器,服务器无法及时响应。
1015:TLS握手失败。
连接关闭状态码是WebSocket对象的onclose属性返回的。
握手规则
websocket是基于应用层的传输控制协议,而socket是基于传输层的传输控制协议。它们都是全双工的(可以同时接收和发送),传输层意味着数据都是以16进制传输,而传输层以二进制传输。
websocket的握手分为客户端请求和服务器端回应。
客户端的请求如下:
GET / HTTP/1.1 // 这个请求必须是GET请求,并且HTTP的协议必须是1.1
Connection:Upgrade // 表示客户端希望连接升级
Upgrade:websocket // 这个值必须是websocket,表示请求升级为websocket协议
Host:xxxx // 请求的主机名称
Origin:xxxx // 打开这个socket的页面
Sec-Websocket-Key:sN9cRrP/n9NdMgdcy2VJFQ== // 随机生成的字符串,客户端通过GUID来验证这个参数是不是一个有效请求
Sec-WebSocket-Version:13 // websocket使用的协议,RFC6455规定这个值必须万为13
Sec-WebSocket-Protocol:xxx // 客户端可以指定应用程序支持的协议,服务器选择其中的一个做为双方通信的协议
Sec-webSocket-Extension:xxxx // 客户端指定某些扩展协议,服务器选择其中一个
服务器端的回应如下:
HTTP/1.1 101 Switching Protocols // 服务器端同意客户端的握手请求
Connection: Upgrade // 同意这个连接升级
Sec-WebSocket-Accept: IIRiohCjop4iJrmvySrFcwcXpHo= // 通过GUID计算出来的值
Sec-WebSocket-Version: 13 // websocket的版本
Upgrade: websocket // 同意升级为websocket协议
如果HTTP的状态不是101客户端应该断开这个连接,如果Sec-WebSocket-Protocol规定了其中的子协议则服务器必须响应其中一个,否则客户端必须断开这个连接。
Sec-WebSocket-Accept的值以php计算如下:
$client_key = 'xxxxx';
$guid = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
$join = $client_key . $guid;
$sha1 = sha1($join,true);
$accept = base64_encode($sha1);
也就是客户端的Sec-WebSocket-Key的值加上GUID后,哈希为20个字符串后,再使用base64编码即可得到服务器的Sec-WebSocket-Accept的值
传输格式
WebSocket frame 的结构如下所示:
该结构的字段语义如下:
-
FIN, 长度为 1 比特, 该标志位用于指示当前的 frame 是消息的最后一个分段, 因为 WebSocket 支持将长消息切分为若干个 frame 发送, 切分以后, 除了最后一个 frame, 前面的 frame 的 FIN 字段都为 0, 最后一个 frame 的 FIN 字段为 1, 当然, 若消息没有分段, 那么一个 frame 便包含了完成的消息, 此时其 FIN 字段值为 1。
-
RSV 1 ~ 3, 这三个字段为保留字段, 只有在 WebSocket 扩展时用, 若不启用扩展, 则该三个字段应置为 1, 若接收方收到 RSV 1 ~ 3 不全为 0 的 frame, 并且双方没有协商使用 WebSocket 协议扩展, 则接收方应立即终止 WebSocket 连接。
-
Opcode, 长度为 4 比特, 该字段将指示 frame 的类型, RFC 6455 定义的 Opcode 共有如下几种:
0x0, 代表当前是一个 continuation frame,既被切分的长消息的每个分片frame
0x1, 代表当前是一个 text frame
0x2, 代表当前是一个 binary frame
0x3 ~ 7, 目前保留, 以后将用作更多的非控制类 frame
0x8, 代表当前是一个 connection close, 用于关闭 WebSocket 连接
0x9, 代表当前是一个 ping frame
0xA, 代表当前是一个 pong frame
0xB ~ F, 目前保留, 以后将用作更多的控制类 frame
Mask, 长度为 1 比特, 该字段是一个标志位, 用于指示 frame 的数据 (Payload) 是否使用掩码掩盖, RFC 6455 规定当且仅当由客户端向服务端发送的 frame, 需要使用掩码覆盖, 掩码覆盖主要为了解决代理缓存污染攻击 (更多细节见 RFC 6455 Section 10.3)。 -
Payload Len, 以字节为单位指示 frame Payload 的长度, 该字段的长度可变, 可能为 7 比特, 也可能为 7 + 16 比特, 也可能为 7 + 64 比特. 具体来说, 当 Payload 的实际长度在 [0, 125] 时, 则 Payload Len 字段的长度为 7 比特, 它的值直接代表了 Payload 的实际长度; 当 Payload 的实际长度为 126 时, 则 Payload Len 后跟随的 16 位将被解释为 16-bit 的无符号整数, 该整数的值指示 Payload 的实际长度; 当 Payload 的实际长度为 127 时, 其后的 64 比特将被解释为 64-bit 的无符号整数, 该整数的值指示 Payload 的实际长度。
-
Masking-key, 该字段为可选字段, 当 Mask 标志位为 1 时, 代表这是一个掩码覆盖的 frame, 此时 Masking-key 字段存在, 其长度为 32 位, RFC 6455 规定所有由客户端发往服务端的 frame 都必须使用掩码覆盖, 即对于所有由客户端发往服务端的 frame, 该字段都必须存在, 该字段的值是由客户端使用熵值足够大的随机数发生器生成, 关于掩码覆盖, 将下面讨论, 若 Mask 标识位 0, 则 frame 中将设置该字段 (注意是不设置该字段, 而不仅仅是不给该字段赋值)。
-
Payload, 该字段的长度是任意的, 该字段即为 frame 的数据部分, 若通信双方协商使用了 WebSocket 扩展, 则该扩展数据 (Extension data) 也将存放在此处, 扩展数据 + 应用数据, 它们的长度和便为 Payload Len 字段指示的值。
常见问题
浏览器因网络故障而断开连接,不调用onClose方法问题。
不同浏览器(客户端)实现WebSocket实现的机制不一样导致,所以不触发onClose方法。