分析HTML5中WebSocket的原理

时间:2023-03-08 16:25:00
分析HTML5中WebSocket的原理
目录结构

一、什么是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握手协议

websocket是一种全新的协议,不属于http无状态协议,协议名为"ws",这意味着一个websocket连接地址会是这样的写法:ws://**。

websocket协议本质上是一个基于tcp的协议。建立连接需要握手,客户端(浏览器)首先向服务器(web server)发起一条特殊的http请求,web server解析后生成应答到浏览器,这样子一个websocket连接就建立了,直到某一方关闭连接。

由于是草案的原因,前前后后就出现了多个版本的握手协议,分情况说明一下:

(1) 基于flash的握手协议

使用场景是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

(2)基于md5加密方式的握手协议

客户端请求:

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连接

(3) 基于sha加密方式的握手协议

也是目前见的最多的一种方式,这里的版本号目前是需要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加密,最后在返回给客户端。

三、Websocket数据帧

WebScoket协议中,数据以帧序列的形式传输,具体的协议标准可以参考rfc6455

(1)客户端向服务器传输的数据帧必须进行掩码处理:服务器若接收到未经过掩码处理的数据帧,则必须主动关闭连接。

(2)服务器向客户端传输的数据帧一定不能进行掩码处理。客户端若接收到经过掩码处理的数据帧,则必须主动关闭连接。
针对上情况,发现错误的一方可向对方发送close帧(状态码是1002,表示协议错误),以关闭连接。

分析HTML5中WebSocket的原理

FIN:1位

表示这是消息的最后一帧(结束帧),一个消息由一个或多个数据帧构成。若消息由一帧构成,起始帧即结束帧。

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,扩展中却没有该值的定义,那么关闭连接。

OPCODE:4位

解释PayloadData,如果接收到未知的opcode,接收端必须关闭连接。

0×0表示附加数据帧

0×1表示文本数据帧

0×2表示二进制数据帧

0×3-7暂时无定义,为以后的非控制帧保留

0×8表示连接关闭

0×9表示ping

0xA表示pong

0xB-F暂时无定义,为以后的控制帧保留

MASK:1位

用于标识PayloadData是否经过掩码处理。如果是1,Masking-key域的数据即是掩码密钥,用于解码PayloadData。客户端发出的数据帧需要进行掩码处理,所以此位是1。

Payload length:7位,7+16位,7+64位, PayloadData的长度(以字节为单位)。

(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,一般在日常中用到的应该会比较少。

四、客户端API

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