WebSocket协议规范浅析

初识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比特):当前数据帧是否是一条消息的最后一个数据帧,利用该字段来将多个数据帧组装为一条完整的消息。

  • RSVRSV1~RSV3, 3比特):用于协议扩展,一般情况下为全0

  • opcode(4比特):帧的类型,取值可以为:

    数据类型
    0x0 附加数据帧
    0x1 UTF-8编码的文本数据帧
    0x2 二进制数据帧
    0x8 用于关闭WebSocket连接
    0x9 表示ping,用于检查WebSocket连接是否正常
    0xA 表示pong,用于答复ping数据帧表示连接正常

    其它值暂时未定义其数据帧类型。

  • Mask(1比特):是否经过掩码处理。

  • Payload LenExtended 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,则代表连接正常,否则表示连接断开。

连接的关闭

主动关闭的一方发送一个opcode0x8的数据帧即可,随后会进行TCP四次挥手阶段。

发布了117 篇原创文章 · 获赞 96 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/abc123lzf/article/details/102468366