网络编程:WebSocket协议浅析

欢迎关注colinsusie的微信公众号,colinsusie就是之前的colin大神哦!继续向colinsusie学习网络协议!

前言

当前好多手游都要求支持全平台,即要支持IOS和Android,也要支持原生App和H5,这让游戏的研发门槛越来越高。服务器这一端相对好一点,但也要考虑不同平台的通讯协议差异。综合各个平台的差异,只有HTTP和WebSocket是全平台支持的。HTTP适合于短连接的游戏,WebSocket则常用在长连接,通信比较频繁的游戏,比如像一些RPG,回合制,对战类的等等。

这一篇就来讲讲WebSocket协议的内容。

握手阶段

WebSocket以一个HTTP的请求和响应来进行握手,客户端请求的文本大概是这样:

GET / HTTP/1.1
Host: example.com:8000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
  • 请求方法必须是GET,协议必须是1.1以上,请求路径没有强制要求,这里是/
  • Upgrade 必须是websocket,Connection必须是Upgrade
  • Sec-WebSocket-Version 为WebSocket版本号,当然是13
  • Sec-WebSocket-Key 是客户端发来的一个Key,看下面响应描述。

服务器响应的文本为:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
  • 响应码必须是101,表示Switching Protocols
  • Upgrade和Connection与上面一样
  • Sec-WebSocket-Accept根据上面请求的Sec-WebSocket-Key算出来的,它的算法是:Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11,然后计算出这个字符串的SHA-1哈希值,最后用base64得到结果。

响应完之后,握手完成,接下来就可以交换数据帧。

数据帧格式

每一个数据帧都包含帧头+有效数据,帧头的格式如下(注意字节顺序都是网络字节序):

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 ...                |
+---------------------------------------------------------------+

协议的大概内容是:

  • 第1位FIN表示该帧是否为连续帧(即需要多个帧才能组成完整有效数据),如果为1表示单独帧,如果为0表示连续帧。我觉得这个有点过度设计了,连续帧应该由应用层自己解决,并且一个帧可以表示的长度是很长的,完全没必要设计这个连接帧。
  • RSV1~RSV3 这3位未用,默认为0。
  • opcode 为操作码,占4位,这个会决定该帧的类型,后面描述。
  • MASK 为掩码标志,占1位,如果为1,有效数据需要和Masking-key进行异或才得到原始数据,后面描述。
  • Payload len,Extended payload length 用于描述有效数据的大小,这个长度是动态的,后面描述。
  • Masking-key和MASK位结合,如果MASK为1,这个才会存在,否则不存在。
  • 后面就是上层应用的有效数据。

MASK数据

如果MASK位为1,那么Masking-key这4个字节就存在,用于和Playload data进行解码,才能得到原始的数据,解码的伪代码如下:

var DECODED = "";
for (var i = 0; i < PayloadData.length; i++) {
    DECODED[i] = PayloadData[i] ^ MaskKey[i % 4];
}

这其实就是一种异或加密,客户端发过来的数据规定必须要加密。服务器可以根据需要自己决定。

Payload Data的长度

帧头有一块内容用来表示有效数据的大小,这一块内容是动态长度的,它是这样计算有效数据大小的:

  • 先读7位(Payload len),如果其值小于126,那么有效数据的长度就是它的值。
  • 如果等于126,还要再读16位,这16位的值就是有效数据的长度。
  • 如果等于127,还要再读64位,这64位的值就是有效数据的长度。

操作码

操作码分为两种,一种是数据类型,一种是控制码,描述如下:

  • 0x1 表示内容是文本数据,并且总是以utf-8编码。
  • 0x2 表示内容是二进制数据。
  • 0x9 是一个控制帧,叫PING帧。
  • 0xA 是一个控制帧,叫PONG帧,服务器收PING帧到后必须回应一个PONG帧,这其实就是一种心跳机制,且PING和PONG可以带有效数据,数据长度必须小于等125,即可以用前面的7位表示。
  • 0x8 是一个控制帧,叫关闭帧,告诉对端我要关闭了,关闭帧可以带有效数据,其中前面2个字节是关闭码,后面跟着utf-8的判断原因文本。。
  • 0x0 是一个控制帧,叫连续帧,假设连续帧有3帧,下面3帧的信息可以描述opcode和FIN怎么组合的: Client:FIN=0,opcode=0x1,msg="and a"Client:FIN=0,opcode=0x0,msg="happy new"Client:FIN=1,opcode=0x0,msg="c" 最后收到的信息就是: anda happynewhappynew

断开挥手阶段

  • 开始断开前,一端需要发送一个关闭控制帧。
  • 另一端收到关闭帧后,需要发送一个关闭帧作为响应。
  • 两端都发送并收到关闭帧后,就可以正常断开连接。

这个断开挥手阶段一直不大明白,TCP已经有断开过程了,WebSocket为什么还要自己设计一套挥手的过程,如果两端主动关闭,那么两端的TCP都会处于TIME_WAIT状态,这样会有什么好处呢?查看过几个实现,一般都是发送关闭帧后自己立即断开连接,并没有遵循WebSocket的协议说明等对端返回关闭帧才关闭。这个有人理解的话,欢迎告知。

关于WebSocket更详细的协议说明,请查看RFC6455

本文分享自微信公众号 - Creator星球游戏开发社区(creator-star)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-08-21

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券