Chrome29的BUG:刷新网页后并未实现WebSocket关闭握手

来源:互联网 发布:走势图制作软件 编辑:程序博客网 时间:2024/05/16 17:05

原因:前一段时间做了一个在线对战的HTML5小游戏,要实时的判断用户的在线状态,在Chrome29下刷新网页,客户端WebSocket及时关闭连接,判断用户下线。在Chrome31,32上,刷新网页,服务器端取不到客户端关闭连接的报文,出现BUG.


这篇文章是关于WebSocket的报文协议的。个人感觉WebSocket是个比较大的话题,如果不仔细斟酌的话,由于个人能力问题不一定能很好的驾驭,所以该文只是谈到了WebSocket的两个技术细节,供大定参考。


谈到WebSocket,顾名意义,是在Web领域的Socket套接字实现。但他又有别于传统的Socket套接字:
1,使用HTTP头作为客户端与服务器端连接握手的媒介
2,它与HTTP协议类似都是基于TCP协议的应用层协议
3,WebSocket的连接握手与关闭握手有自身的约定

而我们今天就从实际开发中遇到的问题来谈谈WebSocket通讯
我之前一段时间做了一个在线对战的HTML5小游戏,具体的网址就不贴了,只是一个demo。很凑巧的是,服务器端的代码早已有同事用c++写好,所以没有用到socket.io这样的WebSocket框架,完全是自已写的WebSocket后端。也因此遇到了一些问题,下文会针对这些问题做详细的阐述

1.不同的浏览器对WebSocket实现不同的协议,所以我们要准备一个具备兼容能力的后端代码
大家知道,WebSocket协议时至今日依然没有定案,而在之前出版了多个协议标准,如draft-hixie,draft-ietf-hybi,而每个系列又有多个版本
主流浏览器实现会因为依据不同的协议而有区别。目前主要有两个版本:draft-hixie-76,draft-ietf-hybi-13。细心的你估计已经看到这两个版本前都是draft的字样,所以它们仍然具有不确定性。不过值得庆兴的是RFC的版本已经发布。

draft-hixie-76与draft-ietf-hybi-13对于WebSocket协议有较大的区别,从客户端的表现主要为HTTP头的变化。
hybi-13通过对请求头Sec-WebSocket-Key的设置进行一些计算输出Sec-WebSocket-Accept响应头值实现连接握手。
hixie-76通过对请求头Sec-WebSocket-Key1,Sec-WebSocket-Key2及请求主体三个共同计算出响应主体的,实现连接握手。
具体的代码参考:http://www.hoverlees.com/blog/?p=1413
其实这个问题对纯前端开发人员来说是无感知的,所有的握手实现都由浏览器和服务器端来实现。

在这里科普一下这个协议的制定历史:draft-hixie -> draft-ietf-hybi -> RFC首先是hixie阶段,然后到了hybi阶段,这个时候前面就有了ietf的前缀,ieft是互联网工程任务组,最后到了RFC阶段。在RFC阶段又分为四个小阶段:因特网草案、建议标准、草案标准、因特网标准。最后才能成为互联网的标准,路还漫长。

2.问题1介绍WebSocket对于连接握手的兼容问题,还有就是关闭连接的握手的问题

hixie-76规定任何一端都可以发送0x00长度的0xFF帧用以发起关闭连接请求,当另一端收到了这个请求,会回传一个相同的0xFF帧来确认连接关闭。当发起端收到了这个确认的回传报文后就可以安全的关闭连接,确保不会有新的报文需要处理。这个机制是用以代替TCP关闭握手协议的

而在hybi-13的版本中,对关闭连接的握手做了改进。
任何一端都可以发送带有指定控制序号的数据(opcode)的帧来开始关闭握手(0x08)。当另一端接收到这样的帧时,发送一个关闭帧作为响应。当发起端接收到那个帧时,关闭连接,因为知道没有更多的数据需要传输。


而在事实上chrome29(也许还有之前的版本)并没有按照任一约定来处理,而是实现了TCP关闭握手协议,既:
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送(报文段4)。 
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。 
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A(报文段6)。 
(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。 
虽然chrome29使用了hybi-13协议,但在关闭时却并未完全实现。


这一点,我和我同事都没有发现。因为用Chrome29连接后端刷新都正常,所以就忽略了,依然用的是TCP的走法开发(从头就是错的)。

而升级到了chrome31后,chrome31按hybi-13的协议发送了0x08的opCode,这个时候服务器端没有特别的处理这个opCode,压根不明白"落花有意,而流水无情",还在傻乎乎的等,直到linux的自动关闭socket超时,也就是我发现的刷新网页,websocket连接没有关闭,程序判断用户仍然在线的原因。

保险起见,我查阅了chrome的源码,找到了相关的代码,但由于chrome项目较庞大,我没法通过它的代码来肯定这一切。
一时好奇心又起,我用socket.io做了服务器端,发现结果如同想象的一样,socket.io的服务器端,也一样没法抓到close socket的任何关键帧,无法走到close方法里,约半分钟后(在我的机器上)连接自动关闭。

而推开来说,这个问题其实并不仅仅只是后端没有处理的事情
因为走的是tcp关闭握手协议,在客户端关闭连接后,其实服务器端相应的端口都进入了CLOSE_WAIT的状态,必须等到linux系统配置的连接超时才会CLOSE这个端口。如果访问量大的话,会造成端口大量占用的情况
draft-ietf-hybi-13文档中也明确写到:TCP关闭握手不总是端到端可靠的,特别是出现拦截代理和其他的中间设施。通过发送一个关闭帧并等待返回关闭帧响应,可以避免一些数据丢失的特殊情况。例如,在一些平台上,如果一个套接字关闭时有数据在接收队列,RST包发送后,将导致recv()失败,因为接收到RST,即使仍有数据等待读取。

参考:
http://blog.csdn.net/yd2005zxq/article/details/7308013
http://blog.csdn.net/yd2005zxq/article/details/7308013
http://tools.ietf.org/search/draft-hixie-thewebsocketprotocol-76
http://tools.ietf.org/html/rfc6455
http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-13
http://www.searchsoa.com.cn/whatis/word_2303.htm
0 0
原创粉丝点击