初识WebSocket
WebSocket是一个持久化应用层协议,随HTTP2.0协议标准发布,客户端(浏览器)和服务端可以维持一个长连接,双方均可自由地发送、接收数据(全双工),从而真正实现双向通信。WebSocket不像HTTP协议是请求——响应模式,需要客户端主动请求才能返回响应,HTTP1.1的长连接(keep-alive
机制)实际上是一个伪长连接,它是无状态的,并且会在短时间完成数据请求、响应后就会关闭TCP连接,而WebSocket不存在这种限制。
利用WebSocket协议,我们可以真正实现实时地与服务器进行交互。例如Web页面上的聊天系统、文档协作编辑系统等,在没有WebSocket之前,这些功能只能通过浏览器不停轮询服务器来实现。
WebSocket相比HTTP具有以下优势:
- 真正意义上的双向通信,可以保证数据的实时性;
- 协议头长度比HTTP协议小很多,只有2字节~14字节。HTTP协议每次通信的请求头一般会占用数十字节。
- 协议支持扩展,用户可以自己扩展协议。
前端创建WebSocket连接
在JavaScript中,一个WebSocket对象代表一个WebSocket连接:
var socket = new WebSocket(url, [protocol]);
参数url
为指定WebSocket URL,protocol
表示可接受的子协议(可选)
WebSocket对象包含2个属性:
readyState
:表示连接状态,取值范围是0~3。0表示连接尚未被建立,1表示连接正在建立,2表示连接正在关闭,3表示连接已经关闭。bufferedAmount
:处于队列中尚未被发送的字节数。
包含4个事件:
事件 | 描述 |
---|---|
onopen | WebSocket连接被建立时调用 |
onmessage | 接收到服务端传来的数据时调用 |
onerror | WebSocket连接发生异常时调用 |
onclose | WebSocket连接被关闭时触发 |
包含两个方法:
send(msg)
:使用WebSocket连接发送数据。close()
:关闭当前WebSocket连接。
利用上述规则,我们可以很轻松地向服务器发起一个WebSocket连接:
var ws = new WebSocket("wss://xxx.com/chart"); //如果没有SSL则为ws://
ws.onopen = function() {
ws.send("A message");
}
ws.onmessage = function(msg) {
alert(msg.data);
}
ws.onclose = function() {
alert("Connection closed");
}
WebSocket连接的建立
WebSocket连接建立过程又称之为握手,是HTTP协议转变到WebSocket协议的桥梁,需要客户端使用该HTTP连接向服务器发送类似于以下的HTTP报文:
GET /ws HTTP/1.1
Host: localhost:80
Connection: Upgrade
Upgrade: websocket
Origin: https://xxx.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: 8ni6j6x/oDl6IpKoJOeJIw==
正常情况下,服务器会给出包含以下字段的HTTP响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: A6IfD3WS44QyuV2I/XubFkImAH8=
Sec-WebSocket-Version: 13
请求URI(上述例子中是/ws
)一般用于区分各个业务。
请求头中需要包含以下内容:
Connection: Upgrade
:表示客户端需要升级协议。Upgrade: websocket
:表示需要升级到WebSocket协议。Sec-WebSocket-Version
:WebSocket协议版本,一般为13。如果服务器不支持,需要在HTTP响应头中包含Sec-WebSocket-Version
字段,并包含其支持的协议版本。Sec-WebSocket-Key
:一个以==
结尾的随机字符串。Origin
(可选):表示客户端浏览器发起WebSocket连接所在的页面,虽然这是个可选字段,但是一般都会包含该字段,主要用于安全用途(检查是否是同个域),大多业务场景中如果不包含这个字段服务器会返回403
。
响应头包含以下内容:
Sec-WebSocket-Accept
:服务端会将客户端请求头中的Sec-WebSocket-Key
字段加上258EAFA5-E914-47DA-95CA-C5AB0DC85B11
,然后计算其SHA-1
,将结果作为该字段的值,以避免浏览器将普通HTTP请求被认为WebSocket协议请求。- 其它相关字段的值等于请求头中的字段的值
上述握手步骤主要目的是为了让客户端确认服务器是否支持WebSocket,如果没有握手步骤直接传输WebSocket数据帧的话,可能会让服务器将该数据帧当成HTTP报文解析,可能会产生安全问题。
浏览器收到正确的HTTP响应报文后,就会利用当前连接发送WebSocket数据帧。至此,一个WebSocket连接就建立完成了。
连接建立后的通信
WebSocket是一个二进制协议,不像HTTP是一个文本协议。连接建立后,服务器和客户端传输的每条消息可能会被切分为多个WebSocket数据帧(WebSocket frame),每个数据帧的格式如下:
各个字段按照顺序解释如下:
-
FIN
(1比特):当前数据帧是否是一条消息的最后一个数据帧,利用该字段来将多个数据帧组装为一条完整的消息。 -
RSV
(RSV1
~RSV3
, 3比特):用于协议扩展,一般情况下为全0 -
opcode
(4比特):帧的类型,取值可以为:值 数据类型 0x0
附加数据帧 0x1
UTF-8
编码的文本数据帧0x2
二进制数据帧 0x8
用于关闭WebSocket连接 0x9
表示 ping
,用于检查WebSocket连接是否正常0xA
表示 pong
,用于答复ping
数据帧表示连接正常其它值暂时未定义其数据帧类型。
-
Mask
(1比特):是否经过掩码处理。 -
Payload Len
、Extended Payload Length
(7比特~71比特):承载数据的长度,为变长字段,长度随数值大小而变化,具体规则如下:范围 大小 0 至 125 7位直接表示长度,无需 Extended Payload Length
字段126 至 65535 Payload Len
部分为126,Extended Payload Length
字段为长度大小,占用2字节大于 65535 Payload Len
部分为127,Extended Payload Length
字段为长度大小,占用8字节 -
Masking-key
(0字节或4字节):掩码密钥,仅当Mask
为1时才有该字段。 -
数据内容(长度由
Payload Len
指定)
掩码
客户端向服务端发送数据帧时,出于安全考虑(防止代理缓存污染攻击)需要对所有帧进行掩码操作,所以客户端发送的数据帧Mask
字段均为1,服务端则没有这个要求。掩码密钥是一个32位的随机数,服务器在解析来自客户端的数据帧时需要根据该掩码密钥,与数据按照特定算法进行一次计算,才能得出原始数据内容。
连接状态检测
由于WebSocket是一个TCP长连接,在连接建立后需要保证TCP通道没有断开。如果一个WebSocket连接长时间没有数据交互,那么其中一方可以主动发送一个ping
数据帧,如果对方返回pong
,则代表连接正常,否则表示连接断开。
连接的关闭
主动关闭的一方发送一个opcode
为0x8
的数据帧即可,随后会进行TCP四次挥手阶段。