GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13 Origin: http://example.com
Upgrade: websocket Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: chat
Upgrade: websocket Connection: Upgrade
最近应项目组要求研究了下WebRTC(目前支持Firefox和Chrome),WebRTC,名称源自网页实时通信(Web Real-Time Communication)的缩写,是一个支持网页浏览器进行实时语音对话或视频对话的技术,是谷歌2010年以6820万美元收购Global IP Solutions公司而获得的一项技术。WebRTC使得开发者在浏览器无需安装任何插件就可以实现语音视频通信。
为了将近期成果做个总结,决定写一个"Web即时通讯“的专题,因为WebSocket是WebRTC的基础,WebSocket为WebRTC负责客服端发现和数据转发,(因为WebSocket是一项“服务器推”技术,如果想更深入了解这方面的技术,可参考博文Comet:基于 HTTP 长连接的“服务器推”技术 )。基本上能实现WebSocket,就能实现WebRTC(浏览器支持方面有差异,目前IE10以上浏览器、Opera/Firefox/Chrome支持WebSocket,但各版本IE不支持WebRTC),
所以先从WebSocket开始。目前大部分实现WebSocket有三种方式,分别是使用Node.js、tomcat7、spring4。下面将逐一介绍。
一、使用Node.js作为信令服务器实现WebRTC
通过收集整理网上资料发现,大部分WebRTC信令服务器使用Node.Js(如果不清楚WebRTC相关技术术语,请自行学习初探WebRTC 这篇文章不错,有图有真相
), 下面列出三个经本人测试可用的Demo,(下载地址为WebRTC Demo)。 其中第三个demo是根据博文最简单的WebRTC示例 整理而来的。
以第三个demo为例,安装完NodeJS后,使用node server.js启动信令服务器。就可以Chrome上分别输入http://127.0.0.1:3000和http://127.0.0.1:3000#true测试了。
Node.js做信令服务器代码少,测试比较方便,这方面的DEMO也特别多。
二、使用Tomcat7作为WebSocket数据转发服务器
有博友用Tomcat7做WebSocket数据转发服务器,实现了类似WebQQ的在线聊天室,详见博文基于Tomcat7、Java、WebSocket的服务器推送聊天室 后续的WebRTC视频聊天没有涉及,不过应该可以实现,本人没有去实验。
这类实现方式和特定的服务器有关,系统部署到其他应用服务器如WebLogic,Jetty等,可能功能就会失效。所以使用该种方式实现需要考虑清楚。
三、使用Spring4作为WebSocket数据转发服务器
Spring4出现后,增加了对WebSocket的支持,这对java web开发者来说,是个好消息,因为可以不用针对具体的应用服务器实现WebSocket。而Spring是java web开发中应用比较广泛的了。使用该种方式实现的demo可参照博文Spring WebSocket教程(一) 和Spring WebSocket教程(二) ,它实现了一个文档编辑实时同步和简单的web聊天功能。这个例子采用了Spring MVC+Spirng4+Hibernate3框架,并使用了Maven进行jar包依赖管理和项目构建,数据库采用的是mysql,作者已经发项目发布到gitHub上,地址为https://github.com/xjyaikj/OnlinePreparation(这位博文的作者还是相当给力的)
我在本地能正常运行项目,但是给出的数据库只有表结构,没有数据。后来我手动添加了数据,如果需要数据可以给我留言啊。
结束语
基于Spring4的Demo没有实现WebRTC,因为我们项目中也是用SpringMVC+Spring4+Hibernate+Maven,所以后续的文章会以这个demo为基础实现一个web聊天室(包括WebSocket实现的文字聊天和WebRTC视频聊天)以及基于WebSocket消息推送实现的实时曲线图。
先上图预告一下:
web聊天室,实现了即时通信
下面为实时曲线,会随着时间动态变化
最后谢谢引用的超链接的原始作者,正式因为有大家分享技术,才能使后来者少走弯路。后续如果时间允许我也会将我的项目demo放到github上。
下篇文章将会讲一下web聊天室(WebSocket实现文字聊天)。
websocket是html5提出的一个协议规范,参考rfc6455. 不过目前还都是在草案,没有成为标准,毕竟html5还在路上。
websocket约定了一个通信的规范,通过一个握手的机制,客户端(浏览器)和服务器(web server)之间能建立一个类似tcp的连接,从而方便c-s之间的通信。
在websocket出现之前,web交互一般是基于http协议的短连接或者长连接。
(1)建立tcp连接;
(2)浏览器发出http请求;
(3)web server应答;
(4)断开tcp连接;
优点是简洁明了,缺点也很明显,每一次的交互中,建立和断开tcp连接带来了比较大的开销,而且http协议头比较长,也会带来带宽的浪费。
通过设置http头中的keep-alive域可以实现http长连接,避免了建立和断开连接的开销,但是http协议头的问题仍然无法解决。
除此之外,web server主动向浏览器推送数据的处理会比较麻烦,要么是浏览器发起轮询(毫无疑问,这是一个低效的方式),或者利用comet技术,比较复杂,而且这已经不是在html层面上解决问题了。
而web socket的出现,解决了上面描述的问题。
websocket是一种全新的协议,不属于http无状态协议,协议名为"ws",这意味着一个websocket连接地址会是这样的写法:ws://**。
websocket协议本质上是一个基于tcp的协议。建立连接需要握手,客户端(浏览器)首先向服务器(web server)发起一条特殊的http请求,web server解析后生成应答到浏览器,这样子一个websocket连接就建立了,直到某一方关闭连接。
由于是草案的原因,前前后后就出现了多个版本的握手协议,分情况说明一下:
使用场景是IE的多数版本,因为IE的多数版本不都不支持WebSocket协议,以及FF、CHROME等浏览器的低版本,还没有原生的支持WebSocket。此处,server唯一要做的,就是准备一个WebSocket-Location域给client,没有加密,可靠性很差。
GET /ls HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: www.qixing318.com
Origin: http://www.qixing318.com
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://www.qixing318.com
WebSocket-Location: ws://www.qixing318.com/ls
GET /demo HTTP/1.1
Host: example.com
Connection: Upgrade
Sec-WebSocket-Key2: **
Upgrade: WebSocket
Sec-WebSocket-Key1: **
Origin: http://www.qixing318.com
[8-byte security key]
服务端返回:
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://www.qixing318.com
WebSocket-Location: ws://example.com/demo
[16-byte hash response]
其中 Sec-WebSocket-Key1,Sec-WebSocket-Key2 和 [8-byte security key] 这几个头信息是web server用来生成应答信息的来源,依据 draft-hixie-thewebsocketprotocol-76 草案的定义。
web server基于以下的算法来产生正确的应答信息:
1)逐个字符读取 Sec-WebSocket-Key1 头信息中的值,将数值型字符连接到一起放到一个临时字符串里,同时统计所有空格的数量;
2)将在第(1)步里生成的数字字符串转换成一个整型数字,然后除以第(1)步里统计出来的空格数量,将得到的浮点数转换成整数型;
3)将第(2)步里生成的整型值转换为符合网络传输的网络字节数组;
4)对 Sec-WebSocket-Key2 头信息同样进行第(1)到第(3)步的操作,得到另外一个网络字节数组;
5)将 [8-byte security key] 和在第(3)、(4)步里生成的网络字节数组合并成一个16字节的数组;
6)对第(5)步生成的字节数组使用MD5算法生成一个哈希值,这个哈希值就作为安全密钥返回给客户端,以表明服务器端获取了客户端的请求,同意创建websocket连接
也是目前见的最多的一种方式,这里的版本号目前是需要13以上的版本。
GET /ls HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: www.qixing318.com
Sec-WebSocket-Origin: http://www.qixing318.com
Sec-WebSocket-Key: 2SCVXUeP9cTjV+0mWB8J6A==
Sec-WebSocket-Version: 13
服务器返回:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: mLDKNeBNWz6T9SxU+o0Fy/HgeSw=
这个的原理,就是把客户端上报的key拼上一段GUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11″,拿这个字符串做SHA-1 hash计算,然后再把得到的结果通过base64加密,最后在返回给客户端。
WebScoket协议中,数据以帧序列的形式传输,具体的协议标准可以参考rfc6455
(1)客户端向服务器传输的数据帧必须进行掩码处理:服务器若接收到未经过掩码处理的数据帧,则必须主动关闭连接。
(2)服务器向客户端传输的数据帧一定不能进行掩码处理。客户端若接收到经过掩码处理的数据帧,则必须主动关闭连接。
针对上情况,发现错误的一方可向对方发送close帧(状态码是1002,表示协议错误),以关闭连接。
表示这是消息的最后一帧(结束帧),一个消息由一个或多个数据帧构成。若消息由一帧构成,起始帧即结束帧。
RSV1,RSV2,RSV3:各1位
MUST be 0 unless an extension is negotiated that defines meanings for non-zero values.
If a nonzero value is received and none of the negotiated extensions defines the meaning of such a nonzero value, the receiving endpoint MUST _Fail the WebSocket Connection_
大致意思是如果未定义扩展,各位是0;如果定义了扩展,即为非0值。如果接收的帧此处非0,扩展中却没有该值的定义,那么关闭连接。
解释PayloadData,如果接收到未知的opcode,接收端必须关闭连接。
0×0表示附加数据帧
0×1表示文本数据帧
0×2表示二进制数据帧
0×3-7暂时无定义,为以后的非控制帧保留
0×8表示连接关闭
0×9表示ping
0xA表示pong
0xB-F暂时无定义,为以后的控制帧保留
用于标识PayloadData是否经过掩码处理。如果是1,Masking-key域的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此位是1。
(1) 如果其值在0-125,则是payload的真实长度。
(2) 如果值是126,则后面2个字节形成的16位无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。
(3) 如果值是127,则后面8个字节形成的64位无符号整型数的值是payload的真实长度。注意,网络字节序,需要转换。
长度表示遵循一个原则,用最少的字节表示长度(我理解是尽量减少不必要的传输)。举例说,payload真实长度是124,在0-125之间,必须用前7位表示;不允许长度1是126或127,然后长度2是124,这样违反原则。
这种情况就比较复杂,具体可以参考rfc,一般在日常中用到的应该会比较少。
websocket的客户端api描述可以参考这里,一个简单的代码参考:
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript"> function WebSocketTest() { if ("WebSocket" in window) { alert("WebSocket is supported by your Browser!"); // Let us open a web socket var ws = new WebSocket("ws://localhost:9998/echo"); ws.onopen = function() { // Web Socket is connected, send data using send() ws.send("Message to send"); alert("Message is sent..."); }; ws.onmessage = function(evt) { var received_msg = evt.data; alert("Message is received..."); }; ws.onclose = function() { // websocket is closed. alert("Connection is closed..."); }; } else { // The browser doesn't support WebSocket alert("WebSocket NOT supported by your Browser!"); } } </script>
</head>
<body>
<div id="sse">
<a href="javascript:WebSocketTest()">Run WebSocket</a>
</div>
</body>
</html>
五、服务器处理
目前开源的websocket server的代码还是不少的。
自己也整理过一份c的,支持上面三种握手情况,并嵌在reactor框架下使用,在chrome下测试过。
https://github.com/gaccob/gbase/blob/master/net/wsconn.h
https://github.com/gaccob/gbase/blob/master/net/wsconn.c
六、目前主流的浏览器支持情况
Chrome | Supported in version 4+ |
Firefox | Supported in version 4+ |
Internet Explorer | Supported in version 10+ |
Opera | Supported in version 10+ |
Safari | Supported in version 5+ |
http://zh.wikipedia.org/wiki/WebSocket
http://www.ibm.com/developerworks/cn/web/1112_huangxa_websocket/
http://blog.csdn.net/fenglibing/article/details/7100070
http://www.cnblogs.com/caosiyang/archive/2012/08/14/2637721.html