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 CodeDetail
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看看是不是还有数据没有发送完成。
浏览器支持
FeatureChromeFirefox(Gecko)IEOperaSafari
Version -76 support(Obsolete)64.0 (2.0)--11.00(disabled)5.0.1
Protocol version 7 support--6.0(6.0)------
Protocol version 10 support147.0(7.0)Html5 Labs??
RFC 6455 Support (IETF Draft 17)1611.0(11.0)10??
在FireFox 6.0-10.0中,需要使用MozWebSocket对象,而之后则是WebSocket对象。
参考实现
Java
     参阅:WebSocketAPI
     Atmosphere
NodeJS
     Socket.io
     Websocket-Node
Ruby
       EM-Websocket
Reference
2. WebSocket API定义 http://dev.w3.org/html5/websockets/

相关推荐