使用WebSocket构建实时WEB

为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处。LaplaceDemon/SJQ。javascript

http://www.cnblogs.com/shijiaqi1066/p/3795075.htmlhtml

 

 

1 WebSocket与传统Web实时通讯技术前端

1.1 WebSockethtml5

HTTP是一种典型的单工模式。即基于Request/Response的方式与服务器进行交互。HTML5提供了浏览器与服务端的双工通讯协议WebSocket。java

 

 

1.2传统Web实时通讯技术git

  • 轮询
  • Comet
  • 长轮询
  • Flash XML Socket

轮询,对服务器压力较大,实时性差,效率低下。github

移动端对Flash支持的很是差。Flash常常全局性的崩溃,很不稳定。web

Comet与长轮询应该是传统方案中最好的Web实时交互的技术。这两种技术本质是一种Hack技术。Comet与长轮询相比有许多缺点。但这不影响它们在传统实时Web中的应用。这两项技术最大的问题在于每次交互的HTTP包的header内容过多,真正有实际意义的信息可能不多,从而致使网络利用率很是低。算法

对于长轮询技术,因为离不开请求/响应的模式,因此服务器压力也会较大。浏览器

 

 

 

2 WebSocket协议

WebSocket与HTTP协议都是基于TCP的,都是可靠的协议。WebSocket和Http协议同样都属于应用层的协议。WebSocket使用标准的80和443端口,这两个端口都是防火墙的友好端口因此不须要防火墙的容许。

WebSocket协议的格式为 "ws://IP:Port" 或者"wss://IP:Port"。其中wss表示进行加密传输的WebSocket协议。

WebSocket协议须要进行"握手"。该"握手"阶段是经过HTTP协议进行的,"握手"行为经过Request/Response的Header完成,只须要交换不多的数据,即可以建立基于TCP/IP协议的双工通道。

wps_clip_image-32524

该图引用自http://blog.sina.com.cn/s/blog_acddf95d0101beuj.html

 

浏览器与服务器经过TCP三次握手创建链接,若是链接创建失败,则浏览器将收到错误消息通知。

TCP创建链接成功后,浏览器经过HTTP协议传送WebSocket支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端。

 

 

2.1 WebSocket 握手

WebSocket协议握手协议很是简单。

实例:运行Tomcat7提供的WebSocket Echo示例程序。当发生Connect时,Chrome中拦截到信息与截包工具中所得的报文以下所示。

 

Chrome所示

wps_clip_image-25577

 

截包工具所示

请求报文

wps_clip_image-27367

说明:

Connection:Upgrade与Upgrade:WebSocket 表示本次请求是要进行WebSocket的握手动做。

Sec-WebSocket-Version首部的值,表示浏览器支持的WebSocket版本信息。

Sec-WebSocket-Key首部的值,是一个由客户端随机生成的字符串。

 

响应报文

wps_clip_image-13404

说明:

在服务器响应的握手信息中Sec-WebSocket-Accept的值为服务器经过客户端Header的Sec-WebSocket-Key的值进行计算并加密的结果。

服务器的响应状态为101,表示服务器端已经理解了客户端的需求,而且客户端须要根据Upgrade中的协议类型,切换为新的协议来完成后续的通讯。

通过以上报文的通讯候,基于WebSocket的TCP/IP双工通道就已经创建了。

 

 

2.2 握手验证

最老的WebSocket草案标准中是没有安全key。草案7.五、7.6中有两个安全key。草案10中只有一个安全key。

7.五、7.6中HTTP头中的"Sec-WebSocket-Key1"与"Sec-WebSocket-Key2"在10中合并为了一个"Sec-WebSocket-Key"。HTTP头中Upgrade的值由"WebSocket"修改成了"websocket"。HTTP头中的"-Origin"修改成了"Sec-WebSocket-Origin"。

增长了HTTP头"Sec-WebSocket-Accept",用来返回原来草案7.五、7.6服务器返回给客户端的握手验证,原来是之内容的形式返回,如今是放到了HTTP头中。

服务器生成验证的方式变化较大。

旧版WebSocket

wps_clip_image-32548

旧版生成Token的方法以下:

取出Sec-WebSocket-Key1中的全部数字字符造成一个数值,这里是1427964708,而后除以Key1中的空格数目,获得一个数值,保留该数值整数位,获得数值N1;对Sec-WebSocket-Key2采起一样的算法,获得第二个整数N2;把N1和N2按照Big-Endian字符序列链接起来,而后再与另一个Key3链接,获得一个原始序列ser_key。Key3是指在握手请求最后,有一个8字节的奇怪的字符串“;”######”,这个就是Key3。而后对ser_key进行一次md5运算得出一个16字节长的digest,这就是老版本协议须要的token,而后将这个token附在握手消息的最后发送回Client,便可完成握手。

新版WebSocket

wps_clip_image-8713

新版生成Token的方法以下:

首先服务器将key(长度24)截取出来,如4tAjitqO9So2Wu8lkrsq3w==,用它和自定义的一个字符串(长度36)258EAFA5-E914-47DA-95CA-C5AB0DC85B11链接起来,而后把这一字符串进行SHA-1算法加密,获得长度为20字节的二进制数据,再将这些数据通过Base64编码,最终获得服务端的密钥,也就是ser_key。服务器将ser_key附在返回值Sec-WebSocket-Accept后,至此握手成功。

 

 

2.3 数据报文格式

旧版协议比较简单,仅仅是在原始数据前加了个’\x00′,在最后面加了个’\xFF’,即假如Client发送一个字符串’test’,实际上WebSocket Server收到的数据是:’x00test\xFF’,因此只须要剥离掉首尾那两个字符就能够了。

新版的协议对这部分规定比较复杂,如下是其格式标准:(下图在Firefox可能会出现错乱,请换用Chrome)

wps_clip_image-1427

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位,任意的应用数据,放在扩展数据以后,应用数据的长度=负载数据的长度-扩展数据的长度。

 

 

2.4 WebSocket版本与浏览器支持

wps_clip_image-13432

表格来自于:http://zh.wikipedia.org/wiki/WebSocket

 

 

 

3 Web前端相关的WebSocket

3.1 W3C API定义

Websocket的API非法简单,如下是W3C的定义

wps_clip_image-8369

enum BinaryType { "blob", "arraybuffer" };
[Constructor(DOMString url, optional (DOMString or DOMString[]) protocols)]
interface WebSocket : EventTarget {
  readonly attribute DOMString url;
  // ready state
  const unsigned short CONNECTING = 0;
  const unsigned short OPEN = 1;
  const unsigned short CLOSING = 2;
  const unsigned short CLOSED = 3;
readonly attribute unsigned short readyState; readonly attribute unsigned long bufferedAmount; // networking attribute EventHandler onopen; attribute EventHandler onerror; attribute EventHandler onclose; readonly attribute DOMString extensions; readonly attribute DOMString protocol; void close([Clamp] optional unsigned shortcode, optional DOMString reason); // messaging attribute EventHandler onmessage; attribute BinaryType binaryType; void send(DOMString data); void send(Blob data); void send(ArrayBuffer data); voidsend(ArrayBufferView data); };

 

 

3.2 实际使用的简单例子

例1

var wsServer = 'ws://localhost:8888/Demo';
var websocket = new WebSocket(wsServer);

websocket.onopen = function (evt) { onOpen(evt) };
websocket.onclose = function (evt) { onClose(evt) };
websocket.onmessage = function (evt) { onMessage(evt) };
websocket.onerror = function (evt) { onError(evt) };

function onOpen(evt) {
    console.log("Connected to WebSocket server.");
}

function onClose(evt) {
    console.log("Disconnected");
}

function onMessage(evt) {
    console.log('Retrieved data from server: ' + evt.data);
}

function onError(evt) {
    console.log('Error occured: ' + evt.data);
}

 

 

例2

<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript"type="text/javascript">

    var wsUri ="ws://echo.websocket.org/";
    var output;
function init() { output = document.getElementById("output"); testWebSocket(); } function testWebSocket() { websocket = new WebSocket(wsUri); websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose
= function(evt) { onClose(evt) }; websocket.onmessage = function(evt) { onMessage(evt) }; websocket.onerror = function(evt) { onError(evt) }; } function onOpen(evt) { writeToScreen("CONNECTED"); doSend("WebSocket rocks"); } function onClose(evt) { writeToScreen("DISCONNECTED"); } function onMessage(evt) { writeToScreen('<span style="color: blue;">RESPONSE: '+ evt.data+'</span>'); websocket.close(); } function onError(evt) { writeToScreen('<span style="color: red;">ERROR:</span> '+ evt.data); } function doSend(message) { writeToScreen("SENT: " + message); websocket.send(message); } function writeToScreen(message) { var pre = document.createElement("p"); pre.style.wordWrap = "break-word"; pre.innerHTML = message; output.appendChild(pre); } window.addEventListener("load", init, false); </script> <h2>WebSocket Test</h2> <div id="output"></div> </html>

 

代码简述

使用new建立WebSocket对象。

var wsUri ="ws://echo.websocket.org/";
websocket = new WebSocket(wsUri);

 

 

WebSocket对象一共支持四个事件响应方法 onopen, onmessage, oncloseonerror。这样不会阻塞UI,获得更好的用户体验。

当浏览器和服务器链接成功后,会触发onopen事件。

websocket.onopen = function(evt) {};

 

若是链接失败,发送、接收数据失败或者处理数据出现错误,会触发onerror事件。

websocket.onerror = function(evt) {};

 

当浏览器接收到服务器发送过来的数据时,就会触发onmessage事件。参数evt中包含服务器传输过来的数据;

websocket.onmessage = function(evt) {};

 

当浏览器接收到服务器发送的关闭链接请求时,就会触发onclose事件。

websocket.onclose = function(evt) {};

 

 

在实际应用中还须要考虑心跳包的问题。

 

 

 

4 WebSocket的缺点

最大的问题就是浏览器兼容性问题。低版本IE浏览器不支持该技术,直到IE10才开始支持WebSocket技术。

固然,解决方案是对于低版本浏览器可使用Flash来模拟WebSocket。

如:web-socket-js 地址:https://github.com/gimite/web-socket-js/

 

 

 

5 WebSocket的服务器实现

WebSocket在网上有很是多实现的例子。如下列出我的会学习研究的服务器实现。

  • Netty实现
  • Tomcat7实现
  • JavaEE7+Tomcat8实现
  • Node.js实现

 

 

 

 

参考资料

http://zh.wikipedia.org/wiki/WebSocket

http://www.web-tinker.com/search/websocket/1.html

http://www.ibm.com/developerworks/cn/web/1112_huangxa_websocket/

http://hackecho.com/2012/04/new-hybi-10-protocol-of-websocket/

http://www.zendstudio.net/archives/web-socket-heartbeat-package/

 

 

 

为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处。LaplaceDemon/SJQ。

http://www.cnblogs.com/shijiaqi1066/p/3795075.html

相关文章
相关标签/搜索