当前位置: 代码迷 >> Web前端 >> WebSockets浅探及实践 - 一
  详细解决方案

WebSockets浅探及实践 - 一

热度:429   发布时间:2012-07-18 12:05:41.0
WebSockets浅探及实践 - 1

?

1 起源

在WebSocket没有出现之前,如果我们要实现从服务器向客户端推送数据的功能,就必须采取某些work around,诸如 Http polling(Comet),BOSH(XMPP)等。 但是他们都基于HTTP协议之上,频繁建立连接的开销以及冗余的HTTP头信息,都带来了一定的延迟。由此,WebSockets也就应运而生了。

2 规范 规范细节请详阅这里

连接建立过程 当打开了到服务器的连接后,客户端必须向服务器发送一次opening握手--它由HTTP Upgrade请求以及一系列必须和可选的头部数据组成。

  • 请求必须是Get方法,且HTTP协议版本至少为1.1。
  • 必须有Host,Upgrade(值必须包含websocket关键字),Connection(值必须包含"Upgrade"), Sec-WebSocket-Key 请求头。
  • 如果请求者是浏览器,必须包含 Origin头。
  • 必须包含 Sec-WebSocket-Version头,且其值为13。
  • 可以包含 Sec-WebSocket-Protocol以及Sec-WebSocket-Extensions头
  • 请求可以包含任何其他的头部域,譬如cookies或认证相关的头如 Authorization。
而后客户端就在等待服务器端的响应并进行验证:
  • 确定状态码是不是101,如果不是则遵循HTTP的响应处理过程。否则往下走
  • 确保响应头中必须包含Upgrade域,其中包含"websocket"值,否则连接失败;
  • 确保响应头包含Connect域,且有值"Upgrade",否则连接失败;
  • 确保响应头包含Sec-WebSocket-Accept域,且其值应该等于Base64(SHA-1( |Sec-WebSocket- Key|)+" 258EAFA5- E914-47DA-95CA-C5AB0DC85B11");否则连接失败;
  • 如果响应头中包含了 Sec-WebSocket-Extensions域,而请求头并未包含,那么客户端必须中断连接;
  • 如果响应头中包含Sec-WebSocket-Protocol域,但在请求头中并没有包含此子协议值,客户端必须中断连接.
如果以上条件俱都满足,那么这个连接就建立了起来,并且处于open状态,可以进行后续的数据发送/接收等操作。

典型的请求头结构如下:
GET /socket.io/1/websocket/1166156958668058955 HTTP/1.1
Host: localhost:8077
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:12.0) Gecko/20100101 Firefox/12.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive, Upgrade
Sec-WebSocket-Version: 13
Origin: http://localhost:8077
Sec-WebSocket-Key: xQelQMxXk7J8q1whTHKPJA==
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
?响应头结构如下:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: V6OpbVjbdllb6RNrRdFmG+pIzXk=



数据传输格式
在Spec中,数据传输格式的定义如下:
0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-------+-+-------------+-------------------------------+
     |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
     |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
     |N|V|V|V|       |S|             |   (if payload len==126/127)   |
     | |1|2|3|       |K|             |                               |
     +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
     |     Extended payload length continued, if payload len == 127  |
     + - - - - - - - - - - - - - - - +-------------------------------+
     |                               |Masking-key, if MASK set to 1  |
     +-------------------------------+-------------------------------+
     | Masking-key (continued)       |          Payload Data         |
     +-------------------------------- - - - - - - - - - - - - - - - +
     :                     Payload Data continued ...                :
     + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
     |                     Payload Data continued ...                |
     +---------------------------
Close状态码
Status Code Detail
1000 正常关闭 - Normal Closure
1001 表示一个端点已经不可用;譬如服务器down了,或者浏览器已经离开当前页面 - Going away
1002 表示因为协议错误某个端点正在终止连接 - Protocol error
1003 表示某个端点因为接收到不合法的数据类型而终止连接 - Unsupported Data
1004 保留字 -- Reserved
1005 保留值。它一定不能被设为Close控制frame中的状态码 - No Status Rcvd
1006
保留值。它一定不能被设为Close控制frame中的状态码 - Abnormal Closure
1007 表示某端因为接收到的数据与其消息类型不一致而中断连接 - Invalid frame payload data
1008 表示某端因为接收到违背其策略的消息而中断连接;这是一个通用的状态码 - 当没有合适的状态码时就返回它 - Policy Vialation
1009 表示某端因为接收到太大的消息包以致不能处理而中断连接 - Message too big
1010 客户端终止连接,因为它希望服务器端返回一个乃至多个extension,但服务器端在WebSocket握手的返回消息里没有包含它 - Mandatory Ext.
1011 表示服务器因为遇到了不可预期的情形以致不能响应请求所以中断连接 - Internal Server Error
1015
保留值。它一定不能被设为Close控制frame中的状态码 - TLS handshake

3. 实现

构建器:
[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
  [TreatNonCallableAsNull] attribute Function? onopen;
  [TreatNonCallableAsNull] attribute Function? onerror;
  [TreatNonCallableAsNull] attribute Function? onclose;
  readonly attribute DOMString extensions;
  readonly attribute DOMString protocol;
  void close([Clamp] optional unsigned short code, optional DOMString reason);

  // messaging
  [TreatNonCallableAsNull] attribute Function? onmessage;
           attribute DOMString binaryType;
  void send(DOMString data);
  void send(ArrayBufferView data);
  void send(Blob data);
};
?
readyState有四个取值:
  • CONNECTING(0): 连接尚未建立
  • OPEN(1): WebSocket连接已建立,可以进行通信
  • CLOSING(2): 连接将要进行closing握手
  • CLOSED(3): 连接已关闭或不能打开
websocket基于事件驱动,它包含以下四类事件:
onopen
onmessage
onclose
onerror
当有消息过来时,会触发onmessage事件。
exampleSocket.onmessage =  function  (event) {  
      console.log(event.data);  
}  
如何关闭连接:
exampleSocket.close();
但是在关闭连接前最好检查下 bufferedAmount看看是不是还有数据没有发送完成。

浏览器支持

Feature Chrome Firefox(Gecko) IE Opera Safari
Version -76 support(Obsolete) 6 4.0 (2.0) -- 11.00(disabled) 5.0.1
Protocol version 7 support -- 6.0(6.0) -- -- --
Protocol version 10 support 14 7.0(7.0) Html5 Labs ? ?
RFC 6455 Support (IETF Draft 17) 16 11.0(11.0) 10 ? ?
在FireFox 6.0-10.0中,需要使用MozWebSocket对象,而之后则是WebSocket对象。

参考实现
Java
?????参阅:WebSocketAPI
?????Atmosphere
NodeJS
?????Socket.io
?????Websocket-Node
Ruby
? ? ? ?EM-Websocket

Reference
1. ?WebSocket规范?http://tools.ietf.org/html/rfc6455
2. WebSocket API定义? http://dev.w3.org/html5/websockets/
3. ? https://developer.mozilla.org/en/WebSockets/Writing_WebSocket_client_applications
4.? http://www.infoq.com/presentations/WebSockets-The-Web-Communication-Revolution
5. WebSocket Vs. Rest?http://nbevans.wordpress.com/2011/12/16/websockets-versus-rest-fight/
6. JAVA websocket实现?http://java.net/projects/websocket-spec/pages/WebSocketAPIs