公司项目需要,了解了下目前几种支持WebSocket的框架。以前用jWebSocket做过一些项目,相对来说,改jWebSocket的源码略复杂,也不是一天两天能搞定的。一调研才发现,现在很多主流的web框架都已经开始支持WebSocket了,不得不感慨时间太快,科技进步太快,在微策略的几年真的荒废了。不多说,先记录下今天的研究。
Tomcat:
J2EE下面用的最多的容器应该就是tomcat了。说到tomcat对WebSocket的支持,不得不先提一下,目前的WebSocket协议已经经过了好几代的演变,不同浏览器对此协议的支持程度也不同,因此,如果作为服务器,最理想的是支持尽可能多的WebSocket协议版本。
tomcat8真正支持jsr-356(包含对websocket的支持), tomcat7支持部分版本的websocket实现不兼容jsr-356。因此,能用tomcat8的话,还是尽量用。
代码实现相当简单,以下是一个列子,只需要tomcat8的基本库,不需要其他依赖。
import java.io.IOException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/websocket")
public class WebSocketTest {
@OnMessage
public void onMessage(String message, Session session) throws IOException,
InterruptedException {
// Print the client message for testing purposes
System.out.println("Received: " + message);
// Send the first message to the client
session.getBasicRemote().sendText("This is the first server message");
// Send 3 messages to the client every 5 seconds
int sentMessages = 0;
while (sentMessages < 3) {
Thread.sleep(5000);
session.getBasicRemote().sendText("This is an intermediate server message. Count: " + sentMessages);
sentMessages++;
}
// Send a final message to the client
session.getBasicRemote().sendText("This is the last server message");
}
@OnOpen
public void onOpen() {
System.out.println("Client connected");
}
@OnClose
public void onClose() {
System.out.println("Connection closed");
}
}
Jetty:
Jetty和Tomcat一样,也是一个Servlet的容器。如果说不同之处,那么最大的不同应该是Tomcat采用的是BIO处理方式,也就是说一个request会用一个线程去处理,即使是WebSocket这种长连接,也是会独立开一个线程。作为一个普通的Web服务器,tomcat可以轻松应对耗时比较短的Request/Response。但是如果换成是长连接的WebSocket,那麻烦就来了,对于上万用户的聊天和推送,总不能开上万个线程去处理吧。此时,Jetty的性能就体现出来了,Jetty采用的是NIO,一个线程可以处理多个WebSocket的长链接,如果你的需求是大量耗时比较长的request或者大量长连接,那么建议采用Jetty。
Jetty对WebSocket的实现有点绕,Servlet不再是继承原来的HttpServlet,而是继承WebSocketServlet。此处要注意导入jetty-util.jar和jetty-websocket.jar两个包,否则可能会有class not found错误。
ReverseAjaxServlet.java:
import java.io.IOException;
import java.util.Date;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.codehaus.jettison.json.JSONArray;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketServlet;
/**
* @author Mathieu Carbou (mathieu.carbou@gmail.com)
*/
public final class ReverseAjaxServlet extends WebSocketServlet {
private final Endpoints endpoints = new Endpoints();
private final Random random = new Random();
private final Thread generator = new Thread("Event generator") {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
try {
Thread.sleep(random.nextInt(5000));
endpoints.broadcast(new JSONArray().put("At " + new Date()).toString());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
};
@Override
public void init() throws ServletException {
super.init();
generator.start();
}
@Override
public void destroy() {
generator.interrupt();
super.destroy();
}
@Override
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
return endpoints.newEndpoint();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.getWriter().write("11111");
}
}
Endpoints.java:
package com.cn.test.chapter2.websocket;
import org.eclipse.jetty.websocket.WebSocket;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* @author Mathieu Carbou (mathieu.carbou@gmail.com)
*/
final class Endpoints {
private final Queue<Endpoint> endpoints = new ConcurrentLinkedQueue<Endpoint>();
void broadcast(String data) {
// for (Endpoint endpoint : endpoints) {
// endpoint.onMessage(data);
// }
}
void offer(Endpoint endpoint) {
endpoints.offer(endpoint);
}
void remove(Endpoint endpoint) {
endpoints.remove(endpoint);
}
public WebSocket newEndpoint() {
return new Endpoint(this);
}
}
Endpoint.java
import java.io.IOException;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.codehaus.jettison.json.JSONArray;
import org.eclipse.jetty.websocket.WebSocket;
/**
* @author Mathieu Carbou (mathieu.carbou@gmail.com)
*/
class Endpoint implements WebSocket.OnTextMessage {
protected Connection _connection;
private Endpoints endpoints;
private static int clientCounter = 0;
private int clientId = clientCounter++;
public Endpoint(Endpoints endpoints) {
this.setEndpoints(endpoints);
}
@Override
public void onClose(int code, String message) {
System.out.println("Client disconnected");
this.endpoints.remove(this);
}
@Override
public void onOpen(Connection connection) {
System.out.println("Client connected");
_connection = connection;
try {
this._connection.sendMessage(new JSONArray().put("ClientID = " + clientId).toString());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
endpoints.offer(this);
}
@Override
public void onMessage(final String data) {
System.out.println("Received data: " + data);
this.endpoints.broadcast(data);
}
public Endpoints getEndpoints() {
return endpoints;
}
public void setEndpoints(Endpoints endpoints) {
this.endpoints = endpoints;
}
}
辅助工具:
在编写服务器最麻烦的是要写对应的客户端来测试,还好Chrome为我们解决了这个问题。下载Chrome插件WebSocket Clinet可以轻松地和服务器建立连接,发送消息到服务器。