代码改变世界

关于websocket

2013-03-11 09:54 by 轩脉刃, ... 阅读, ... 评论, 收藏, 编辑

这个是一次组内分享,关于websocket的协议和应用的。文章在分享之前就写好了,整理下放出来。

对应的PPT地址是:http://websocket.funaio.com

从推送技术开始说

一篇文章10 Years of Push Technology, Comet, and WebSockets(http://cometdaily.com/2011/07/06/push-technology-comet-and-websockets-10-years-of-history-from-lightstreamers-perspective/)非常详细的说明清楚了从1996-2007年推送技术的更新。2000年之前为第一波Push技术,使用的概念叫Webcasting。大致思想就是用户来服务端注册一个或者多个通道channel,然后服务端确定给某些个channel或某个channel发送消息。2000到2007年最火的词叫comet,比如有Polling(这个是最普通的轮询),Long Polling(hold住HTTP的response端,当有消息的时候才返回),至于HTTP发起是在不同的哪些地方,Ajax,Flash,Iframe,然后就有各自不同的叫法和名词。但是不管什么技术,都限制于浏览器,因为浏览器只能发起HTTP请求。但是推送最好的一种方法当然是保持Socket长连接,浏览器如何能发起TCP连接呢?HTML5的Websocket技术就解决了这个问题。

websocket概述

现状

websocket的版本发展:

draft-hixie-00

draft-hixie-76

draft-hybi-00

draft-hybi-17

RFC 6455

所以有的地方会出现hixie,hybi的字样,纯粹是为了满足之前的版本。好消息是从2011年12月份开始,websocket的RFC正式版出来了。

客户端现状

从客户端来讲,大部分浏览器支持的是hybi-13版本,比如我使用的chrome v24,firefox v16。相信各家浏览器都很快能出现支持RFC版本的浏览器了。网上有很多文章说“支持websocket的浏览器”,这个说法其实是不对的,至少是不完整的。比如Chrome从草案开始就已经支持websocket了,你拿一个支持hixie76版本的chrome调用实现了RFC 6455的服务端,那是怎么样也对不上的。所以说“支持websocket的浏览器”还应该多带一句,支持什么版本。

浏览器来说还分为手机浏览器和PC浏览器。手机浏览器特别是android上的浏览器版本支持websocket的比PC上少很多(这可能和大家几乎不会去定期更新手机浏览器有关)。网上对那些版本浏览器支持那些版本的websocket有好多文章进行统计了。但是由于浏览器种类繁多,版本之多,记住这些版本,不如在定义需求的时候明确我需要支持哪些浏览器的哪些版本,然后写一个简单例子试一下更为准确。

服务端现状

下面是服务端。服务端对websocket的语言开发已经是没有问题了。http://en.wikipedia.org/wiki/WebSocket wiki上列出了各种服务端语言开发websocket的第三方包了。可以直接拿来用,也可以根据协议自己开发个websocket包,比如http://www.cnblogs.com/yjf512/archive/2013/02/18/2915171.html。甚至于像apache,nginx(1.3.13)这些现成的web服务器产品也都渐渐支持websocket了(nginx现在只是支持nginx代理)。

socket.io这个包是node上用的最多的一个第三方包。它其实并不仅仅是websocket。官方的faq http://socket.io/#faq 也说明清楚了为什么它不直接叫“websocket”包。socket.io的js包可以给客户端使用,也可以给服务端使用,客户端使用socket.io能很简单建立websocket连接,但是上,支持websocket的浏览器是不需要使用socket.io来建立websocket连接的。在服务端,使用socket.io并不是只能创建websocket服务器,就是说socket.io要建立的服务是“socket服务”而不仅仅是“websocket服务”。

websocket的原理

websocket是一种协议,本质上和http,tcp一样。协议是用来说明数据是如何传输的。它的url前缀是ws:// 或者wss://,后者是加密的websocket。它的url诸如这样:ws://10.16.15.64:3201/

客户端和服务端进行websocket交互的方式也有人理解为“HTTP握手+TCP数据传输”的方式。

HTTP握手+TCP数据传输

握手和传输的整个流程是这样的:

浏览器(支持Websocket的浏览器)像HTTP一样,发起一个请求,然后等待服务端的响应

服务器返回握手响应,告诉浏览器请将后续的数据按照websocket制定的数据格式传过来

浏览器和服务器的socket连接不中断,此时这个连接和http不同的是它是双工的了

浏览器和服务器有任何需要传递的数据的时候使用这个长连接进行数据传递

这里说它是HTTP握手,是因为浏览器和服务器在建立长连接的握手过程是按照HTTP1.1的协议发送的,有Request,Request Header, Response, Response Header。但是不同的是Header里面的字段是有特定含义的。

说它是TCP传输,主要体现在建立长连接后,浏览器是可以给服务器发送数据,服务器也可以给浏览器发送请求的。当然它的数据格式并不是自己定义的,是在要传输的数据外层有ws协议规定的外层包的。

握手过程

Image(1)

这是一个握手过程的例子。

Upgrade头表示的意思是“客户端除了http之外也支持websocket协议,而且更倾向使用websocket,服务端如果支持的话,咱们就换websocket协议吧”

sec-websocket-version:是指出浏览器支持的websocket号。这里是支持hybi-13。这里是不会出现9-12的版本号的。websocket协议规定9-12是保留字段。

sec-websocket-key:算是一种验证返回回来的服务端是否是支持websocket的验证算法。与Response中的sec-websocket-accept是对应的。

sec-websocket-accept与sec-websocket-key的对应算法是:

sec-websocket-accept = base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11))

如果返回的sec-websocket-accept不对,在chrome下会出现Sec-WebSocket-Accept dismatch的错误。

Response返回的HTTP Staus是101,代表服务端说“我们双方后面就按照websocket协议来进行数据传输吧”

数据传输过程

websocket的数据传输是frame形式传输的,比如会将一条消息分为几个frame,按照先后顺序传输出去。这样做会有几个好处:

1 大数据的传输可以分片传输,不用考虑到数据大小导致的长度标志位不足够的情况。

2 和http的chunk一样,可以边生成数据边传递消息,即提高传输效率。

传输协议:

Image(2)

FIN:1位,用来表明这是一个消息的最后的消息片断,当然第一个消息片断也可能是最后的一个消息片断

RSV1, RSV2, RSV3: 分别都是1位,如果双方之间没有约定自定义协议,那么这几位的值都必须为0,否则必须断掉WebSocket连接

Opcode:4位操作码,定义有效负载数据,如果收到了一个未知的操作码,连接也必须断掉,以下是定义的操作码:

      *  %x0 表示连续消息片断

      *  %x1 表示文本消息片断

      *  %x2 表未二进制消息片断

      *  %x3-7 为将来的非控制消息片断保留的操作码

      *  %x8 表示连接关闭

      *  %x9 表示心跳检查的ping

      *  %xA 表示心跳检查的pong

      *  %xB-F 为将来的控制消息片断的保留操作码

Mask:1位,定义传输的数据是否有加掩码,如果设置为1,掩码键必须放在masking-key区域,客户端发送给服务端的所有消息,此位的值都是1

Payload length: 传输数据的长度,以字节的形式表示:7位、7+16位、或者7+64位。如果这个值以字节表示是0-125这个范围,那这个值就表示传输数据的长度;如果这个值是126,则随后的两个字节表示的是一个16进制无符号数,用来表示传输数据的长度;如果这个值是127,则随后的是8个字节表示的一个64位无符合数,这个数用来表示传输数据的长度。多字节长度的数量是以网络字节的顺序表示。负载数据的长度为扩展数据及应用数据之和,扩展数据的长度可能为0,因而此时负载数据的长度就为应用数据的长度

Masking-key:0或4个字节,客户端发送给服务端的数据,都是通过内嵌的一个32位值作为掩码的;掩码键只有在掩码位设置为1的时候存在

Payload data: (x+y)位,负载数据为扩展数据及应用数据长度之和

Extension data:x位,如果客户端与服务端之间没有特殊约定,那么扩展数据的长度始终为0,任何的扩展都必须指定扩展数据的长度,或者长度的计算方式,以及在握手时如何确定正确的握手方式。如果存在扩展数据,则扩展数据就会包括在负载数据的长度之内

Application data:y位,任意的应用数据,放在扩展数据之后,应用数据的长度=负载数据的长度-扩展数据的长度

读取数据需要按照这个格式读取,发送数据也需要按照这个格式发送返回。

代码示例:

http://www.cnblogs.com/yjf512/archive/2013/02/18/2915171.html

应用:

1 chatofPemelo

聊天室

https://github.com/NetEase/chatofpomelo

2 osxair

远程控制air

https://github.com/jianfengye/MyWorks/tree/master/osxair

3 nginx1.3.13

nginx1.3.13只是支持了websocket代理

Feature: support for proxying of WebSocket connections.

参考文章

http://m.udpwork.com/item/6758.html

http://www.websocket.org/aboutwebsocket.html

http://tools.ietf.org/html/rfc6455

http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-13

http://www.cnblogs.com/yjf512/archive/2013/02/18/2915171.html