公司項目需要,瞭解了下眼下幾種支持WebSocket的框架。曾經用jWebSocket作過一些項目。相對來講。改jWebSocket的源代碼略複雜,也不是一天兩天能搞定的。一調研才發現,現在很是多主流的web框架都已經開始支持WebSocket了,不得不感慨時間太快,科技進步太快,在微策略的幾年真的荒廢了。很少說,先記錄下今天的研究。java
Tomcat:web
J2EE如下用的最多的容器應該就是tomcat了。說到tomcat對WebSocket的支持,不得不先提一下,眼下的WebSocket協議已經通過了好幾代的演變。不一樣瀏覽器對此協議的支持程度也不一樣。所以,假設做爲server。最理想的是支持儘量多的WebSocket協議版本號。json
tomcat8真正支持jsr-356(包括對websocket的支持)。 tomcat7支持部分版本號的websocket實現不兼容jsr-356。所以,能用tomcat8的話,仍是儘可能用。瀏覽器
代碼實現至關簡單,下面是一個列子,僅僅需要tomcat8的基本庫,不需要其它依賴。tomcat
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:websocket
Jetty和Tomcat同樣,也是一個Servlet的容器。假設說不一樣之處,那麼最大的不一樣應該是Tomcat採用的是BIO處理方式,也就是說一個request會用一個線程去處理,即便是WebSocket這樣的長鏈接,也是會獨立開一個線程。session
做爲一個普通的Webserver,tomcat可以輕鬆應對耗時比較短的Request/Response。但是假設換成是長鏈接的WebSocket。那麻煩就來了,對於上萬用戶的聊天和推送,總不能開上萬個線程去處理吧。此時,Jetty的性能就體現出來了。Jetty採用的是NIO,一個線程可以處理多個WebSocket的長連接,假設你的需求是大量耗時比較長的request或者大量長鏈接,那麼建議採用Jetty。框架
Jetty對WebSocket的實現有點繞,Servlet再也不是繼承原來的HttpServlet。而是繼承WebSocketServlet。此處要注意導入jetty-util.jar和jetty-websocket.jar兩個包,不然可能會有class not found錯誤。dom
ReverseAjaxServlet.java:eclipse
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"); } }
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); } }
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; } }
輔助工具:
在編寫server最麻煩的是要寫相應的client來測試,還好Chrome爲咱們攻克了這個問題。下載Chrome插件WebSocket Clinet可以輕鬆地和server創建鏈接,發送消息到server。