首页 > 代码库 > Tomcat和Jetty对WebSocket的支持

Tomcat和Jetty对WebSocket的支持

公司项目需要,了解了下目前几种支持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可以轻松地和服务器建立连接,发送消息到服务器。

Tomcat和Jetty对WebSocket的支持