查看原文
其他

网络编程:WebSocket协议浅析

The following article is from Hello Wld Author colinsusie

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


前言

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

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

握手阶段

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

  1. GET / HTTP/1.1

  2. Host: example.com:8000

  3. Upgrade: websocket

  4. Connection: Upgrade

  5. Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

  6. Sec-WebSocket-Version: 13

  • 请求方法必须是GET,协议必须是1.1以上,请求路径没有强制要求,这里是/

  • Upgrade 必须是websocket,Connection必须是Upgrade

  • Sec-WebSocket-Version 为WebSocket版本号,当然是13

  • Sec-WebSocket-Key 是客户端发来的一个Key,看下面响应描述。

服务器响应的文本为:

  1. HTTP/1.1 101 Switching Protocols

  2. Upgrade: websocket

  3. Connection: Upgrade

  4. 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得到结果。

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

数据帧格式

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

  1. 0 1 2 3

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

  3. +-+-+-+-+-------+-+-------------+-------------------------------+

  4. |F|R|R|R| opcode|M| Payload len | Extended payload length |

  5. |I|S|S|S| (4) |A| (7) | (16/64) |

  6. |N|V|V|V| |S| | (if payload len==126/127) |

  7. | |1|2|3| |K| | |

  8. +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +

  9. | Extended payload length continued, if payload len == 127 |

  10. + - - - - - - - - - - - - - - - +-------------------------------+

  11. | |Masking-key, if MASK set to 1 |

  12. +-------------------------------+-------------------------------+

  13. | Masking-key (continued) | Payload Data |

  14. +-------------------------------- - - - - - - - - - - - - - - - +

  15. : Payload Data continued ... :

  16. + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +

  17. | Payload Data continued ... |

  18. +---------------------------------------------------------------+

协议的大概内容是:

  • 第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进行解码,才能得到原始的数据,解码的伪代码如下:

  1. var DECODED = "";

  2. for (var i = 0; i < PayloadData.length; i++) {

  3. DECODED[i] = PayloadData[i] ^ MaskKey[i % 4];

  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

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存