为什么需要WebSocket 协议?
众所周知,HTTP 协议是一个遵循请求-响应模式的协议。这种模式有两个特点:1. 由客户端先发起请求,然后等待服务器的响应。2. 服务器不能在没有接收到客户端请求时,就发送数据。也就是说,客户端和服务器之间的通信是单向的。
在某些应用中,通常有服务器向客户端推送数据的需求,也就是需要客户端和服务器之间能够双向通信。而使用现有的HTTP协议不能很好地实现这个需求,于是 HTTP 协议的升级版——WebSocket 协议被发明出来。
协议概述
WebSocket 协议包括两部分:握手和数据传输。握手时会使用 HTTP 协议,所以说它是 HTTP 协议的升级版。
握手
客户端发起握手时,会发送如下格式的 HTTP GET 请求:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Host 头部就不用说了。
Upgrade 和 Connection 头部表明要求服务器升级到 WebSocket 协议。
Sec-WebSocket-Key 头部是一串 Base64 编码的随机字符串,用于握手验证。
Sec-WebSocket-Version 头部指明协议版本。
Origin 和 Sec-WebSocket-Protocol 头部是可选的,后者表示客户端支持的子协议。通常这两个头部都可以忽略。
服务器在收到握手请求时,回复如下的 HTTP 101 响应完成握手(协议升级):
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
Upgrade 和 Connection 头部表明服务器已经升级到 WebSocket 协议。
Sec-WebSocket-Accept 头部是为了给客户端做验证,它是的值是根据客户端 Sec-WebSocket-Key 头部的值使用某种算法计算出来的。
数据传输
握手成功后的数据传输过程就与 HTTP 协议没有任何关系了。传输过程中,任意一方都可以自由地发送数据。协议中的数据传输单元称作消息,一个消息包含一个或多个数据帧,每个数据帧都是消息的一个分片。WebSocket 数据帧的格式如下:
FIN,标志位。值为1时表示是一个消息的最后一个分片。
Opcode,4位。表示数据帧的类型,有如下值:
0x0。是一个连续的帧,即它的类型与上一个帧的类型相同。
0x1。是一个文本帧。
0x2。是一个二进制帧。
0x8。是一个关闭帧。
0x9。是一个 ping 帧。
0xa。是一个 pong 帧。
关闭帧、ping 帧和pong 帧都是控制帧,ping 帧和pong 帧可用来做心跳。
Mask,标志位。值为1时表示数据已经做了掩码操作。
Payload length,数据长度。它的值为0-125时,就表示数据长度。它的值为126时,接下来的16位(Extended payload length 字段)表示数据长度。它的值为127时,表示接下来的64位(16位的 Extended payload length 字段和48位的 Extended payload length continued 字段)表示数据长度。
Masking-key,Mask标志为1时,表示一个32位的数据掩码。Mask标志为0时,该字段不存在。
Payload Data,数据。
结束传输
当双方不需要再传输数据时,一端发送关闭帧(不能分片发送),另一端也回复关闭帧。关闭帧可携带数据,携带的数据前两个字节表示关闭状态码。连接正常关闭的状态码是1000。
通常关闭帧先由客户端发起,服务器在回复关闭帧后就立即关闭 TCP 连接。如果关闭帧先由服务器发起,那么客户端在回复关闭帧后要等待服务器先关闭 TCP 连接。