socket.io 服务器
今天的用户期望可以从Web访问快速,动态的应用程序。 本系列向您展示如何使用反向Ajax技术开发事件驱动的Web应用程序。 第1部分介绍了反向Ajax,轮询,流传输,Comet和长轮询。 您了解了Comet如何使用HTTP长轮询来可靠实现反向Ajax的最佳方法,因为现在所有浏览器都提供支持。 第2部分展示了如何使用WebSockets实现反向Ajax。 代码示例帮助说明了WebSocket,FlashSocket,服务器端的约束,请求范围的服务以及暂停的长期请求。
在本文中,深入研究有关在Web应用程序中针对不同的Web容器和API(Servlet 3.0和Jetty Continuations)使用Comet和WebSockets的细节。 通过使用抽象库(例如Socket.IO)来学习透明地使用Comet和WebSockets。 Socket.IO使用功能检测来确定是否将通过WebSocket,AJAX长轮询,Flash等建立连接。
先决条件
理想情况下,要充分利用本文,您应该了解JavaScript和Java。 本文创建的示例是使用Google Guice构建的,Google Guice是用Java编写的依赖项注入框架。 要继续阅读本文,您应该熟悉依赖项注入框架的概念,例如Guice,Spring或Pico。
要运行本文中的示例,您还将需要最新版本的Maven和JDK(请参阅参考资料 )。
Comet和WebSocket的服务器解决方案
您在第1部分中了解到,Comet(长轮询或流式传输)要求服务器能够暂停请求并在可能的长时间延迟后恢复或完成请求。 第2部分描述了服务器如何需要使用非阻塞I / O功能来处理许多连接,并且它们仅使用线程来服务请求(每个请求线程模型)。 您还了解到WebSocket的使用取决于服务器,并且并非所有服务器都支持WebSocket。
本节说明如何在Jetty,Tomcat和Grizzly Web服务器上使用Comet和WebSockets(如果适用)。 本文提供的源代码包含用于Jetty和Tomcat的示例聊天Web应用程序。 本节还讨论了以下应用程序服务器支持的API:Jboss,Glassfish和WebSphere。
码头
Jetty是一个Web服务器,支持Java Servlet规范3.0,WebSockets和许多其他集成规范。 码头是:
- 强大而灵活
- 易于嵌入
- 支持虚拟主机,会话群集以及许多可以通过Java代码或XML轻松配置的功能
- 用于Google App Engine的托管服务
核心Jetty项目由Eclipse Foundation托管。
从版本6开始,Jetty包含一个称为Jetty Continuations的异步API,该API允许暂停请求并在以后恢复。 表1显示了主要Jetty版本系列的支持规范和API的映射。
表1. Jetty版本和支持
支持 | 码头6 | 码头7 | 码头8 |
---|---|---|---|
非阻塞I / O | X | X | X |
Servlet 2.5 | X | X | X |
Servlet 3.0 | X | X | |
码头续航(彗星) | X | X | X |
Web套接字 | X | X |
要使用Comet实现反向Ajax,可以使用Jetty的Continuation API,如清单1所示:
清单1. Comet的Jetty Continuation API
// Pausing a request from a servlet's method (get, post, ...):
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Continuation continuation = ContinuationSupport.getContinuation(req);
// optionally set a timeout to avoid suspending requests for too long
continuation.setTimeout(0);
// suspend the request
continuation.suspend();
// then hold a reference for future usage from another thread
continuations.offer(continuation);
}
// Then, from another thread which wants to send an event to the client:
while (!continuations.isEmpty()) {
Continuation continuation = continuations.poll();
HttpServletResponse response =
(HttpServletResponse) continuation.getServletResponse();
// write to the response
continuation.complete();
}
本文随附的源代码中包含完整的Web应用程序。 跳船续集捆绑在JAR存档中。 您必须将此JAR文件放入Web应用程序的WEB-INF / lib文件夹中,才能使用Jetty的Comet功能。 码头续航将适用于码头6、7和8。
从Jetty 7开始,您还可以访问WebSockets功能。 将Jetty的WebSocket JAR文件放在Web应用程序的WEB-INF / lib文件夹中,以访问Jetty的WebSocket API,如清单2所示:
清单2. Jetty的WebSocket API
// Implement the doWebSocketConnect and returns an implementation of
// WebSocket:
public final class ReverseAjaxServlet extends WebSocketServlet {
@Override
protected WebSocket doWebSocketConnect(HttpServletRequest request,
String protocol) {
return [...]
}
}
// Sample implementation of WebSocket:
class Endpoint implements WebSocket {
Outbound outbound;
public void onConnect(Outbound outbound) {
this.outbound = outbound;
}
public void onMessage(byte opcode, String data) {
outbound.sendMessage("Echo: " + data);
if("close".equals(data))
outbound.disconnect();
}
public void onFragment(boolean more, byte opcode,
byte[] data, int offset, int length) {
}
public void onMessage(byte opcode, byte[] data,
int offset, int length) {
onMessage(opcode, new String(data, offset, length));
}
public void onDisconnect() {
outbound = null;
}
}
在可下载的源代码中 ,jetty-websocket文件夹中有一个聊天示例,演示了如何使用Jetty的WebSocket API。
Tomcat
Tomcat可能是最广为人知的Web服务器。 它已经使用了很多年,并且已作为Web容器集成到Jboss应用服务器的早期版本中。 Tomcat还用作Servlet规范的参考实现。 当人们开始考虑基于非阻塞I / O的替代方案(例如Jetty)时,它在servlet API 2.5中就不再使用了。 表2显示了两个最新的Tomcat版本系列的受支持的规范和API。
表2. Tomcat支持
支持 | Tomcat6 | Tomcat7 |
---|---|---|
非阻塞I / O | X | X |
Servlet 2.5 | X | X |
Servlet 3.0 | X | |
先进的I / O(Comet) | X | X |
Web套接字 |
如表2所示,Tomcat不支持WebSocket。 它等效于Jetty的Continuations,称为Advanced I / O,以支持Comet。 先进的I / O不仅仅是围绕NIO的低级包装,而不是用于促进Comet使用的良好API。 它的文档记录很少,并且很少有使用此API的应用程序示例。 清单3显示了一个servlet示例,该示例用于暂停和恢复聊天Web应用程序中的请求。 您可以在本文的源代码中找到完整的Web应用程序。
清单3.彗星的Tomcat API
public final class ChatServlet extends HttpServlet
implements CometProcessor {
private final BlockingQueue<CometEvent> events =
new LinkedBlockingQueue<CometEvent>();
public void event(CometEvent evt)
throws IOException, ServletException {
HttpServletRequest request = evt.getHttpServletRequest();
String user =
(String) request.getSession().getAttribute("user");
switch (evt.getEventType()) {
case BEGIN: {
if ("GET".equals(request.getMethod())) {
evt.setTimeout(Integer.MAX_VALUE);
events.offer(evt);
} else {
String message = request.getParameter("message");
if ("/disconnect".equals(message)) {
broadcast(user + " disconnected");
request.getSession().removeAttribute("user");
events.remove(evt);
} else if (message != null) {
broadcast("[" + user + "]" + message);
}
evt.close();
}
}
}
}
void broadcast(String message) throws IOException {
Queue<CometEvent> q = new LinkedList<CometEvent>();
events.drainTo(q);
while (!q.isEmpty()) {
CometEvent event = q.poll();
HttpServletResponse resp = event.getHttpServletResponse();
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType("text/html");
resp.getWriter().write(message);
event.close();
}
}
}
在Tomcat中,异步servlet必须实现CometProcessor
。 对于异步servlet,Tomcat不会调用标准的HTTP方法( doGet
, doPost
等)。 而是将事件发送到event(CometdEvent)
方法。 当请求第一次到达时,该示例检查以确认它是否是GET
以便将其挂起。 evt.close()
不被调用。 如果它是POST
,则意味着用户发送了一条消息,因此该消息被广播到另一个CometEvent
,并且evt.close()
来完成发布请求。 在客户端,广播将发出所有长轮询请求以完成发送的消息,并立即发送另一个长轮询请求以接收下一个事件。
灰熊和玻璃鱼
Grizzly不是Web容器,而是更多的NIO框架,可帮助开发人员构建可伸缩的应用程序。 它是Glassfish项目的一部分,但也可以独立或嵌入式使用。 Grizzly提供了充当HTTP / HTTPS服务器的组件以及Bayeux协议,Servlet,HttpService OSGi和Comet等组件。 Grizzly支持WebSockets,并在Glassfish中用于提供Comet和WebSocket支持。
Glassfish是Oracle的应用服务器,是J2EE 6规范的参考实现。 Glassfish是一个完整的套件,例如WebSphere和Jboss,它使用Grizzly来提供NIO,WebSocket和Comet支持。 它基于OSGI的模块化体系结构使其真正灵活地更改组件。 表3显示了Glassfish对Comet和WebSockets的支持。
表3. Glassfish支持
支持 | 玻璃鱼2 | 玻璃鱼3 |
---|---|---|
非阻塞I / O | X | X |
Servlet 2.5 | X | X |
Servlet 3.0 | X | |
彗星 | X | X |
Web套接字 | X |
Grizzly用法并非无关紧要,因为它打算嵌入或直接从Java代码中使用。 它被广泛用作支持Comet和WebSockets的框架,这些框架可以嵌入到较大的应用程序中,例如Glassfish,该应用程序提供Web部署功能和Servlet规范API。
请参阅相关信息的链接,在灰熊或是Glassfish的WebSockets和Comet的例子。 由于Glassfish使用Grizzly,因此这些示例都应适用于两者。 WebSocket API与Jetty中的API非常相似,但Comet API更复杂。
博斯
Jboss是基于Tomcat构建的应用程序服务器。 从版本5开始,它就支持Comet和NIO。Jboss 7仍在开发中,但包含在下面的表4中。
表4. Jboss支持
支持 | Jboss 5 | Jboss 6 | Jboss 7 |
---|---|---|---|
非阻塞I / O | X | X | X |
Servlet 2.5 | X | X | X |
Servlet 3.0 | X | X | |
彗星 | X | X | X |
Web套接字 |
的WebSphere
WebSphere是IBM应用程序服务器。 WebSphere的版本8中添加了对Servlet 3 API(包含Comet的标准化异步API)的支持(请参阅参考资料以阅读声明)。
表5. WebSphere支持
支持 | WebSphere 8 |
---|---|
非阻塞I / O | X |
Servlet 2.5 | X |
Servlet 3.0 | X |
彗星 | X |
Web套接字 |
拥有通用API呢?
每个服务器都为Comet和WebSocket带来自己的本机API。 您可能会猜到,编写可移植的Web应用程序可能很困难。 Servlet 3.0规范包括其他方法,可以在以后暂停和恢复请求,从而允许所有支持Servlet 3.0规范的Web容器都支持Comet长轮询请求。
Jetty团队提供了一个名为Jetty Continuation的库,该库独立于Jetty容器。 Jetty Continuation库足够聪明,可以检测可用的容器或规格。 如果在Jetty服务器上运行,将使用本机Jetty API。 如果在支持Servlet 3.0规范的容器上运行,则将使用此通用API。 否则,将使用不可扩展的实现。
关于WebSocket,Java还没有标准,因此,如果要使用WebSocket,则需要在Web应用程序中使用容器供应商API。
表6总结了各种服务器支持的技术。
表6.服务器支持的技术
容器 | 彗星 | WebSocket |
---|---|---|
码头6 | 码头延续 | 不适用 |
码头7 | Servlet 3.0 码头延续 | 本机Jetty API |
码头8 | Servlet 3.0 码头延续 | 本机Jetty API |
Tomcat6 | 进阶I / O | 不适用 |
Tomcat7 | Servlet 3.0 进阶I / O 码头延续 | 不适用 |
玻璃鱼2 | 原生Grizzly API | 不适用 |
玻璃鱼3 | Servlet 3.0 原生Grizzly API 码头延续 | 原生Grizzly API |
Jboss 5 | 本机Jboss API | 不适用 |
Jboss 6 | Servlet 3.0 本机Jboss API 码头延续 | 不适用 |
Jboss 7 | Servlet 3.0 本机Jboss API 码头延续 | 不适用 |
WebSphere 8 | Servlet 3.0 码头延续 | 不适用 |
除了使用容器API外,没有明显的WebSocket解决方案。 至于Comet,每个支持Servlet 3.0规范的容器都支持Comet。 这里的“跳船续航”的优点是为所有这些容器提供彗星支持。 因此,一些反向Ajax库(将在本系列的下一部分和下一篇文章中进行讨论)正在将Jetty Continuations用于其服务器端API。
本文的Jetty 示例中显示了Jetty Continuation API。 在本系列的第1部分的两个Comet示例中描述并使用了Servlet 3.0规范。
抽象库
考虑所有主要API(Servlet 3.0和Jetty Continuations),再加上服务器端的所有本机支持,以及在客户端(Comet和WebSocket)进行Reverse Ajax的两种主要方法,编写自己JavaScript和Java代码连接它们可能很困难。 您还必须考虑超时,连接失败,确认,排序,缓冲等等。
本文的其余部分将向您展示Socket.IO的运行情况。 本系列的第4部分将探讨Atmosphere和CometD。 这三个库都是开源的,它们在许多服务器上都支持Comet和WebSocket。
套接字
Socket.IO是一个JavaScript客户端库,提供与WebSocket类似的单个API,以连接到远程服务器以异步发送和接收消息。 通过提供一个通用的API,Socket.IO支持多种传输方式:WebSocket,Flash Sockets,长轮询,流传输,永远的Iframe和JSONP轮询。 Socket.IO检测浏览器功能,并尝试选择最佳的传输方式。 Socket.IO库几乎与所有浏览器(包括旧版本,例如IE 5.5)以及移动浏览器兼容。 它还具有诸如心跳,超时,断开连接和错误处理之类的功能。
该Socket.IO网站(见相关信息 )详细介绍了如何在库的工作原理以及浏览器和反向的Ajax技术的使用。 基本上,Socket.IO使用一种通信协议,该协议使客户端库可以与服务器端的端点进行通信,该端点可以理解Socket.IO协议。 Socket.IO最初是为Node JS开发的,Node JS是用于构建更快服务器JavaScript引擎。 许多项目带来了对其他语言(包括Java)的支持。
清单4显示了在客户端使用Socket.IO JavaScript库的示例。 Socket.IO网站上有文档和示例。
清单4. Socket.IO客户端库的用法
var socket = new io.Socket(document.domain, {
resource: 'chat'
});
socket.on('connect', function() {
// Socket.IO is connected
});
socket.on('disconnect', function(disconnectReason, errorMessage) {
// Socket.IO disconnected
});
socket.on('message', function(mtype, data, error) {
// The server sent an event
});
// Now that the handlers are defined, establish the connection:
socket.connect();
要使用Socket.IO JavaScript库,你将需要一个名为Socket.IO Java的(看到对应的Java部分相关主题 )。 该项目最初由Apache Wave团队启动,目的是在WebSockets可用之前将对Reverse Ajax的支持引入Wave。 Socket.IO Java由Ovea(一家专门从事事件驱动的Web开发的公司)创建和维护,然后被放弃。 由于要进行多种传输,因此开发后端以支持Socket.IO客户端库非常复杂。 本系列的第4部分将展示如何不需要客户端库中的许多传输来获得更好的可伸缩性和浏览器支持,因为长轮询和WebSockets足够了。 当WebSockets不可用时,Socket.IO确实是一个不错的选择。
Socket.IO Java使用Jetty Continuation API暂停和恢复请求。 它使用本地Jetty WebSockets API来支持WebSockets。 您可以使用Socket.IO Java确定哪台服务器可以与Web应用程序正常工作。
下面的清单5显示了如何在服务器上使用Socket.IO的示例。 您必须定义一个扩展SocketIOServlet
的servlet,并实现一个返回某种端点表示形式的方法。 该API与WebSockets API非常相似。 优点是,此API用于服务器端,而与客户端选择的传输方式无关。 Socket.IO将所有传输类型转换为服务器端的相同API。
清单5.用于聊天示例的Socket.IO Java库-servlet
public final class ChatServlet extends SocketIOServlet {
private final BlockingQueue<Endpoint> endpoints =
new LinkedBlockingQueue<Endpoint>();
@Override
protected SocketIOInbound doSocketIOConnect
(HttpServletRequest request) {
String user =
(String) request.getSession().getAttribute("user");
return user == null ? null : new Endpoint(this, user, request);
}
void broadcast(String data) {
for (Endpoint endpoint : endpoints) {
endpoint.send(data);
}
}
void add(Endpoint endpoint) {
endpoints.offer(endpoint);
}
void remove(Endpoint endpoint) {
endpoints.remove(endpoint);
}
}
清单6显示了如何返回端点。
清单6.聊天示例的Socket.IO Java库用法-端点
class Endpoint implements SocketIOInbound {
[...]
private SocketIOOutbound outbound;
[...]
@Override
public void onConnect(SocketIOOutbound outbound) {
this.outbound = outbound;
servlet.add(this);
servlet.broadcast(user + " connected");
}
@Override
public void onDisconnect(DisconnectReason reason,
String errorMessage) {
outbound = null;
request.getSession().removeAttribute("user");
servlet.remove(this);
servlet.broadcast(user + " disconnected");
}
@Override
public void onMessage(int messageType, String message) {
if ("/disconnect".equals(message)) {
outbound.close();
} else {
servlet.broadcast("[" + user + "] " + message);
}
}
void send(String data) {
try {
if (outbound != null
&& outbound.getConnectionState() == ConnectionState.CONNECTED) {
outbound.sendMessage(data);
}
} catch (IOException e) {
outbound.close();
}
}
}
Socket.IO的完整示例包含在socketio文件夹的源代码中。
结论
所有的Web容器都支持Comet,几乎所有的Web容器都支持WebSocket。 即使规范导致了几种不同的本机实现,您仍然可以使用带有通用API(Servlet 3.0或Jetty Continuations)的Comet开发Web应用程序。 而且,甚至更好的是,您可以使用Socket.IO之类的库来透明地利用Comet和WebSockets的功能。 本系列的下一篇文章将介绍另外两个库Atmosphere和CometD。 这三个库均提供多浏览器支持,出色的用户体验以及错误处理的好处,更轻松的API,超时和重新连接。
翻译自: https://www.ibm.com/developerworks/web/library/wa-reverseajax3/index.html
socket.io 服务器