郭晨 软件151 1531610114
WebSocket
在HTML5中出现的WebSocket是一种比Comet还要新的反向Ajax技术,WebSocket启用了双向的全双工通信信道,许多浏览器(Firefox、Google Chrome和Safari)都已对此做了支持。连接是通过一个被称为WebSocket握手的HTTP请求打开的,其用到了一些特殊的报头。连接会保持在活动状态,你可以使用JavaScript来写入和接收数据,就像是在使用一个原始的TCP套接口一样。
WebSocket URL的起始输入是ws://或是wss://(在SSL上)。
图1中的时间线说明了使用WebSocket的通信。一个带有特定报头的HTTP握手被发送到了服务器端,接着在服务器端或是客户端就可以通过JavaScript来使用某种套接口(socket)了,这一套接口可被用来通过事件句柄异步地接收数据。
图1. 使用WebSocket的反向Ajax
本文可下载的源代码中有一个WebSocket例子,在运行该例子时,你应该会看到类似清单1的输出。其说明了客户端的事件是如何发生的,以及如何会立即在客户端显示出来。当客户端发送一些数据时,服务器端回应客户端的发送行为。
清单1. JavaScript中的WebSocket例子
[client] WebSocket connection opened
[server] 1 events
[event] ClientID =0
[server] 1 events
[event] At Fri Jun 1721:12:01 EDT 2011
[server] 1 events
[event] From 0 : qqq
[server] 1 events
[event] At Fri Jun 1721:12:05 EDT 2011
[server] 1 events
[event] From 0 : vv
通常情况下,在JavaScript中你会如清单2所说明的那样来使用WebSocket,如果你的浏览器支持它的话。
清单2. JavaScript客户端例子
var ws = new WebSocket(‘ws://127.0.0.1:8080/async‘);
ws.onopen = function() {
// 连接被打开时调用
};
ws.onerror = function(e) {
// 在出现错误时调用,例如在连接断掉时
};
ws.onclose = function() {
// 在连接被关闭时调用
};
ws.onmessage = function(msg) {
// 在服务器端向客户端发送消息时调用
// msg.data包含了消息
};
// 这里是如何给服务器端发送一些数据
ws.send(‘some data‘);
// 关闭套接口
ws.close();
发送和接收的数据可以是任意类型的,WebSocket可被看成是TCP套接口,因此这取决于客户端和服务器端知道要来回发送的数据是哪种类型的。这里的例子发送的是JSON串。
在JavaScript WebSocket对象被创建后,如果在浏览器的控制台(或是Firebug)中仔细看一下HTTP请求的话,你应该会看到WebSocket特有的报头。清单3给出了一个例子。
清单3. HTTP请求和相应报头示例
Request URL:ws://127.0.0.1:8080/async
Request Method:GET
Status Code:101 WebSocket Protocol Handshake
Request Headers
Connection:Upgrade
Host:127.0.0.1:8080
Origin:http://localhost:8080
Sec-WebSocket-Key1:1 &1~ 33188Yd]r8dp W75q
Sec-WebSocket-Key2:17; 229 *043M 8
Upgrade:WebSocket
(Key3):B4:BB:20:37:45:3F:BC:C7
Response Headers
Connection:Upgrade
Sec-WebSocket-Location:ws://127.0.0.1:8080/async
Sec-WebSocket-Origin:http://localhost:8080
Upgrade:WebSocket
(Challenge Response):AC:23:A5:7E:5D:E5:04:6A:B5:F8:CC:E7:AB:6D:1A:39
WebSocket握手使用所有的这些报头来验证并设置一个长生存期的连接,WebSocket的JavaScript对象还包含了两个有用的属性:
ws.url:返回WebSocket服务器的URL
ws.readyState:返回当前连接状态的值
1. CONNECTING = 0
2. OPEN = 1
3. CLOSED = 2
服务器端对WebSocket的处理要稍加复杂一些,现在还没有某个Java规范以一种标准的方式来支持WebSocket。要使用web容器(例如Tomcat或是Jetty)的WebSocket功能的话,你得把应用代码和容器特定的库紧密耦合在一起才能访问WebSocket的功能。
示例代码的websocket文件夹中的例子使用的是Jetty的WebSocket API,因为我们使用的是Jetty容器。清单4 给出了WebSocket的处理程序。(本系列的第3部分会使用不同的后端WebSocket API。)
清单4. Jetty容器的WebSocket处理程序
public final class ReverseAjaxServlet extends WebSocketServlet {
@Override
protected WebSocket doWebSocketConnect(HttpServletRequest request,String protocol) {
return [...]
}
}
就Jetty来说,有几种处理WebSocket握手的方式,比较容易的一种方式是子类化Jetty的WebSocketServlet并实现doWebSocketConnect方法。该方法要求你返回Jetty的WebSocket接口的一个实例,你必须要实现该接口并返回代表了WebSocket连接的某种端点(endpoint)。清单5提供了一个例子。
清单5. WebSocket实现示例
class Endpoint implements WebSocket {
Outbound outbound;
@Override
publicvoid onConnect(Outbound outbound) {
this.outbound = outbound;
}
@Override
publicvoid onMessage(byte opcode, String data) {
// 在接收到消息时调用
// 你通常用到的就是这一方法
}
@Override
publicvoid onFragment(boolean more, byte opcode,byte[] data, int offset, int length) {
// 在完成一段内容时,onMessage被调用
// 通常不在这一方法中写入东西
}
@Override
publicvoid onMessage(byte opcode, byte[] data,int offset, int length) {
onMessage(opcode, new String(data, offset, length));
}
@Override
publicvoid onDisconnect() {
outbound =null;
}
}
若要向客户端发送消息的话,你要向outbound中写入消息,如果清单6所示:
清单6. 发送消息给客户端
if (outbound != null && outbound.isOpen()) {
outbound.sendMessage(‘Hello World !‘);
}
要断开并关闭到客户端的WebSocket连接的话,使用outbound.disconnect()。
WebSocket是一种实现无延迟双向通信的非常强大的方法,Firefox、Google Chrome、Opera和其他的现代浏览器都支持这种做法。根据jWebSocket网站的说法:
1. Chrome从4.0.249版本开始包含本地化的WebSocket。
2. Safari 5.x包含了本地化的WebSocket。
3. Firefox 3.7a6和4.0b1+包含了本地化的WebSocket。
4. Opera从10.7.9.67开始包含了本地化的WebSocket。
欲了解更多关于jWebSocket方面的内容,请查阅参考资料。
优点
WebSocket功能强大、双向、低延迟,且易于处理错误,其不会像Comet长轮询那样有许多的连接,也没有Comet流所具有的一些缺点。它的API也很容易使用,无需另外的层就可以直接使用,而Comet则需要一个很好的库来处理重连接、超时、Ajax请求、确认以及选择不同的传输(Ajax长轮询和jsonp轮询)。
缺点
WebSocket的缺点有这些:
1. 是一个来自HTML5的新规范,还没有被所有的浏览器支持。
2. 没有请求作用域(request scope),因为WebSocket是一个TCP套接口而不是一个HTTP请求,有作用域的请求服务,比如说Hibernate的SessionInViewFilter,就不太容易使用。Hibernate是一个持久性框架,其在HTTP请求的外围提供了一个过滤器。在请求开始时,其在请求线程中设定了一个上下文(包括事务和JDBC连接)边界;在请求结束时,过滤器销毁这一上下文。
FlashSocket
对于不支持WebSocket的浏览器来说,有些库能够回退到FlashSocket(经由Flash的套接口)上。这些库通常会提供同样的官方WebSocket API,但他们是通过把调用委托给一个包含在网站中的隐藏的Flash组件来实现的。
优点
FlashSocket透明地提供了WebSocket的功能,即使是在不支持HTML5 WebSocket的浏览器上也是如此。
缺点
FlashSocket有着下面的这些缺点:
1. 其需要安装Flash插件(通常情况下,所有浏览器都会有该插件)。
2. 其要求防火墙的843端口是打开的,这样Flash组件才能发出HTTP请求来检索包含了域授权的策略文件。如果843端口是不可到达的话,则库应该有回退动作或是给出一个错误,所有的这些处理都需要一些时间(最多3秒,这取决于库),而这会降低网站的速度。
3. 如果客户端处在某个代理服务器的后面的话,到端口843的连接可能会被拒绝。
WebSocketJS项目提供了一种桥接方式,其要求一个至少是10版本的Flash来为Firefox 3、Inernet Explorer 8和Internet Explorer 9提供WebSocket支持。