websocket介紹

轉自:http://blog.csdn.net/ishallwin/article/details/10299815


衆所周知,socket是編寫網絡通信應用的基本技術,網絡數據交換大多直接或間接通過socket進行。對於直接使用socket的客戶端與服務端,一旦連接被建立則均可主動向對方傳送數據,而對於使用更上層的HTTP/HTTPS協議的應用,由於它們是非連接協議,所以通常只能由客戶端主動向服務端發送請求才能獲得服務端的響應並取得相關的數據。而當前越來越多的應用希望能夠及時獲取服務端提供的數據,甚至希望能夠達到接近實時的數據交換(例如很多網站提供的在線客戶系統)。爲達到此目的,通常採用的技術主要有輪詢、長輪詢、流等,而伴隨着HTML5的出現,相對更優異的WebSocket方案也應運而生。

一、            非WebSocket方案簡介

1.      輪詢

       輪詢是由客戶端定時向服務端發起查詢數據的請求的一種實現方式。早期的輪詢是通過不斷自動刷新頁面而實現的(在那個基本是IE統治瀏覽器的時代,那不斷刷新頁面產生的噪聲就難以讓人忍受),後來隨着技術的發展,特別是Ajax技術的出現,實現了無刷新更新數據。但本質上這些方式均是客戶端定時輪詢服務端,這種方式的最顯著的缺點是如果客戶端數量龐大並且定時輪詢間隔較短服務端將承受響應這些客戶端海量請求的巨大的壓力。

2.      長輪詢

       在數據更新不夠頻繁的情況下,使用輪詢方法獲取數據時客戶端經常會得到沒有數據的響應,顯然這樣的輪詢是一個浪費網絡資源的無效的輪詢。長輪詢則是針對普通輪詢的這種缺陷的一種改進方案,其具體實現方式是如果當前請求沒有數據可以返回,則繼續保持當前請求的網絡連接狀態,直到服務端有數據可以返回或者連接超時。長輪詢通過這種方式減少了客戶端與服務端交互的次數,避免了一些無謂的網絡連接。但是如果數據變更較爲頻繁,則長輪詢方式與普通輪詢在性能上並無顯著差異。同時,增加連接的等待時間,往往意味着併發性能的下降。

3.      流

      所謂流是指客戶端在頁面之下向服務端發起一個長連接請求,服務端收到這個請求後響應它並不斷更新連接狀態,以確保這個連接在客戶端與服務端之間一直有效。服務端可以通過這個連接將數據主動推送到客戶端。顯然,這種方案實現起來相對比較麻煩,而且可能被防火牆阻斷。

二、            WebSocket簡介

1.      WebSocket協議簡介

       WebSocket是爲解決客戶端與服務端實時通信而產生的技術。其本質是先通過HTTP/HTTPS協議進行握手後創建一個用於交換數據的TCP連接,此後服務端與客戶端通過此TCP連接進行實時通信。

WebSocket規範當前還沒有正式版本,草案變化也較爲迅速。Tomcat7(本文中的例程來自7.0.42)當前支持RFC 6455(http://tools.ietf.org/html/rfc6455)定義的WebSocket,而RFC 6455目前還未凍結,將來可能會修復一些Bug,甚至協議本身也可能會產生一些變化。

        RFC6455定義的WebSocket協議由握手和數據傳輸兩個部分組成。

    來自客戶端的握手信息類似如下:

[plain] view plaincopy
  1. GET /chat HTTP/1.1  
  2. Host: server.example.com  
  3. Upgrade: websocket  
  4. Connection: Upgrade  
  5. Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==  
  6. Origin: http://example.com  
  7. Sec-WebSocket-Protocol: chat, superchat  
  8. Sec-WebSocket-Version: 13  


        服務端的握手信息類似如下:

[plain] view plaincopy
  1. HTTP/1.1 101 Switching Protocols  
  2. Upgrade: websocket  
  3. Connection: Upgrade  
  4. Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  
  5. Sec-WebSocket-Protocol: chat  


 

        一旦客戶端和服務端都發送了握手信息並且成功握手,則數據傳輸部分將開始。數據傳輸對客戶端和服務端而言都是一個雙工通信通道,客戶端和服務端來回傳遞的數據稱之爲“消息”。

客戶端通過WebSocket URI發起WebSocket連接,WebSocket URIs模式定義如下:

 

[plain] view plaincopy
  1. ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]  
  2. wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]  


 

        ws是普通的WebSocket通信協議,而wss是安全的WebSocket通信協議(就像HTTPHTTPS之間的差異一樣)。在缺省情況下,ws的端口是80wss的端口是443

        關於WebSocke協議規範的完整詳盡說明,請參考RFC 6455

2.      Tomcat7提供的WebSocket包簡介

        Tomcat7提供的與WebSocket相關的類均位於包org.apache.catalina.websocket之中(包org.apache.catalina.websocket的實現包含於文件catalina.jar之中),它包含有類Constants、MessageInbound、StreamInbound、WebSocketServlet、WsFrame、WsHttpServletRequestWrapper、WsInputStream、WsOutbound。這些類的關係如圖 1所示。

 

                                                                 圖1

        包org.apache.catalina.websocket中的這些類爲WebSocket開發服務端提供了支持,這些類的主要功能簡述如下:

        Constants:包org.apache.catalina.websocket中用到的常數定義在這個類中,它只包含靜態常數定義,無任何邏輯實現。

        MessageInbound:基於消息的WebSocket實現類(帶內消息),應用程序應當擴展這個類並實現其抽象方法onBinaryMessageonTextMessage

       StreamInbound:基於流的WebSocket實現類(帶內流),應用程序應當擴展這個類並實現其抽象方法onBinaryDataonTextData

       WebSocketServlet:提供遵循RFC6455WebSocket連接的Servlet基本實現。客戶端使用WebSocket連接服務端時,需要將WebSocketServlet的子類作爲連接入口。同時,該子類應當實現WebSocketServlet的抽象方法createWebSocketInbound,以便創建一個inbound實例(MessageInboundStreamInbound)

       WsFrame:代表完整的WebSocket框架。

       WsHttpServletRequestWrapper:包裝過的HttpServletRequest對象。

       WsInputStream:基於WebSocket框架底層的socket的輸入流。

       WsOutbound:提供發送消息到客戶端的功能。它提供的所有向客戶端的寫方法都是同步的,可以防止多線程同時向客戶端寫入數據。

三、            基於Tomcat7WebSocket例程

        利用當前HTML5Tomcat7WebSocket提供的支持,基本只需要編寫簡單的代碼對不同的事件做相應的邏輯處理就可以實現利用WebSocket進行實時通信了。

        Tomcat7WebSocket提供了3個例程(echochatsnake),以下就其中的echochat分別做一簡要解析。

1.      echo例程

        echo例程主要演示以下功能:客戶端連接服務端、客戶端向服務端發送消息、服務端收到客戶端發送的消息後將其原樣返回給客戶端、客戶端收到消息後將其顯示在網頁之上。

       在客戶端頁面選擇streamsmessages作爲“Connectusing”,然後點擊“Connect”按鈕,可以在右側窗口看到WebSocket連接打開的消息。隨後點擊“Echo message”按鈕,客戶端將向服務端發送一條消息,在右側窗口,可以看到,消息發出的後的瞬間,客戶端已經收到了服務端原樣返回的消息。

        客戶端頁面及運行效果截圖如圖 2所示。

                                                                                                    圖2

       客戶端實現上述功能的核心腳本如下,其關鍵點通過註釋的形式加以說明:

[javascript] view plaincopy
  1. <script type="text/javascript">  
  2.         var ws = null;  
  3.         // 界面元素可用性控制  
  4.         function setConnected(connected) {  
  5.             document.getElementById('connect').disabled = connected;  
  6.             document.getElementById('disconnect').disabled = !connected;  
  7.             document.getElementById('echo').disabled = !connected;  
  8.         }  
  9.   
  10.         function connect() {  
  11.             // 取得WebSocket連接入口(WebSocket URI)  
  12.             var target = document.getElementById('target').value;  
  13.             if (target == '') {  
  14.                 alert('Please select server side connection implementation.');  
  15.                 return;  
  16.             }  
  17.             // 創建WebSocket  
  18.             if ('WebSocket' in window) {  
  19.                 ws = new WebSocket(target);  
  20.             } else if ('MozWebSocket' in window) {  
  21.                 ws = new MozWebSocket(target);  
  22.             } else {  
  23.                 alert('WebSocket is not supported by this browser.');  
  24.                 return;  
  25.             }  
  26.             // 定義Open事件處理函數  
  27.             ws.onopen = function () {  
  28.                 setConnected(true);  
  29.                 log('Info: WebSocket connection opened.');  
  30.             };  
  31.             // 定義Message事件處理函數(收取服務端消息並處理)  
  32.             ws.onmessage = function (event) {  
  33.                 log('Received: ' + event.data);  
  34.             };  
  35.             // 定義Close事件處理函數  
  36.             ws.onclose = function () {  
  37.                 setConnected(false);  
  38.                 log('Info: WebSocket connection closed.');  
  39.             };  
  40.         }  
  41.         // 關閉WebSocket連接  
  42.         function disconnect() {  
  43.             if (ws != null) {  
  44.                 ws.close();  
  45.                 ws = null;  
  46.             }  
  47.             setConnected(false);  
  48.         }  
  49.   
  50.         function echo() {  
  51.             if (ws != null) {  
  52.                 var message = document.getElementById('message').value;  
  53.                 log('Sent: ' + message);  
  54.                 // 向服務端發送消息  
  55.                 ws.send(message);  
  56.             } else {  
  57.                 alert('WebSocket connection not established, please connect.');  
  58.             }  
  59.         }  
  60.         // 生成WebSocket URI   
  61.         function updateTarget(target) {  
  62.             if (window.location.protocol == 'http:') {  
  63.                 document.getElementById('target').value =   
  64. 'ws://' + window.location.host + target;  
  65.             } else {  
  66.                 document.getElementById('target').value =   
  67. 'wss://' + window.location.host + target;  
  68.             }  
  69.         }  
  70.         // 在界面顯示log及消息  
  71.         function log(message) {  
  72.             var console = document.getElementById('console');  
  73.             var p = document.createElement('p');  
  74.             p.style.wordWrap = 'break-word';  
  75.             p.appendChild(document.createTextNode(message));  
  76.             console.appendChild(p);  
  77.             while (console.childNodes.length > 25) {  
  78.                 console.removeChild(console.firstChild);  
  79.             }  
  80.             console.scrollTop = console.scrollHeight;  
  81.         }  
  82.     </script>  


       注:完整代碼參見apache-tomcat-7.0.42\webapps\examples\websocket\echo.html

       以上客戶端可以根據“Connectusing”的不同選擇連接不同的服務端WebSocket。例如messages選項對應的服務端代碼如下,其核心邏輯是在收到客戶端發來的消息後立即將其發回客戶端。

[java] view plaincopy
  1. public class EchoMessage extends WebSocketServlet {  
  2.     private static final long serialVersionUID = 1L;  
  3.     private volatile int byteBufSize;  
  4.     private volatile int charBufSize;  
  5.   
  6.     @Override  
  7.     public void init() throws ServletException {  
  8.         super.init();  
  9.         byteBufSize = getInitParameterIntValue("byteBufferMaxSize"2097152);  
  10.         charBufSize = getInitParameterIntValue("charBufferMaxSize"2097152);  
  11.     }  
  12.   
  13.     public int getInitParameterIntValue(String name, int defaultValue) {  
  14.         String val = this.getInitParameter(name);  
  15.         int result;  
  16.         if(null != val) {  
  17.             try {  
  18.                 result = Integer.parseInt(val);  
  19.             }catch (Exception x) {  
  20.                 result = defaultValue;  
  21.             }  
  22.         } else {  
  23.             result = defaultValue;  
  24.         }  
  25.   
  26.         return result;  
  27.     }  
  28.   
  29.     // 創建Inbound實例,WebSocketServlet子類必須實現的方法  
  30.     @Override  
  31.     protected StreamInbound createWebSocketInbound(String subProtocol,  
  32.             HttpServletRequest request) {  
  33.         return new EchoMessageInbound(byteBufSize,charBufSize);  
  34.     }  
  35.     // MessageInbound子類,完成收到WebSocket消息後的邏輯處理  
  36.     private static final class EchoMessageInbound extends MessageInbound {  
  37.         public EchoMessageInbound(int byteBufferMaxSize, int charBufferMaxSize) {  
  38.             super();  
  39.             setByteBufferMaxSize(byteBufferMaxSize);  
  40.             setCharBufferMaxSize(charBufferMaxSize);  
  41.         }  
  42.         //  二進制消息響應  
  43.         @Override  
  44.         protected void onBinaryMessage(ByteBuffer message) throws IOException {  
  45.             getWsOutbound().writeBinaryMessage(message);  
  46.         }  
  47.         // 文本消息響應  
  48.         @Override  
  49.         protected void onTextMessage(CharBuffer message) throws IOException {  
  50.             // 將收到的消息發回客戶端  
  51.             getWsOutbound().writeTextMessage(message);  
  52.         }  
  53.     }  
  54. }  


        注:完整代碼參見apache-tomcat-7.0.42\webapps\examples\WEB-INF\classes\websocket\echo\EchoMessage.java。

2.      chat例程

        chat例程實現了通過網頁進行羣聊的功能。每個打開的聊天網頁都可以收到所有在線者發出的消息,同時,每個在線者也都可以(也只可以)向其它所有人發送消息。也就是說,chat實例演示瞭如何通過WebSocket實現對所有在線客戶端的廣播。

                                                                  圖3

        chat例程客戶端核心代碼如下,可以看到其實現方式與echo例程形式上稍有變化,本質依舊是對WebSocket事件進行響應與處理。

[javascript] view plaincopy
  1. <script type="text/javascript">  
  2.         var Chat = {};  
  3.   
  4.         Chat.socket = null;  
  5.   
  6.         Chat.connect = (function(host) {  
  7.             // 創建WebSocket  
  8.             if ('WebSocket' in window) {  
  9.                 Chat.socket = new WebSocket(host);  
  10.             } else if ('MozWebSocket' in window) {  
  11.                 Chat.socket = new MozWebSocket(host);  
  12.             } else {  
  13.                 Console.log('Error: WebSocket is not supported by this browser.');  
  14.                 return;  
  15.             }  
  16.             // 定義Open事件處理函數  
  17.             Chat.socket.onopen = function () {  
  18.                 Console.log('Info: WebSocket connection opened.');  
  19.                 document.getElementById('chat').onkeydown = function(event) {  
  20.                     if (event.keyCode == 13) {  
  21.                         Chat.sendMessage();  
  22.                     }  
  23.                 };  
  24.             };  
  25.             // 定義Close事件處理函數  
  26.             Chat.socket.onclose = function () {  
  27.                 document.getElementById('chat').onkeydown = null;  
  28.                 Console.log('Info: WebSocket closed.');  
  29.             };  
  30.             // 定義Message事件處理函數  
  31.             Chat.socket.onmessage = function (message) {  
  32.                 Console.log(message.data);  
  33.             };  
  34.         });  
  35.   
  36.         Chat.initialize = function() {  
  37.             if (window.location.protocol == 'http:') {  
  38.                 Chat.connect('ws://' +   
  39. window.location.host + '/examples/websocket/chat');  
  40.             } else {  
  41.                 Chat.connect('wss://' +   
  42. window.location.host + '/examples/websocket/chat');  
  43.             }  
  44.         };  
  45.         // 發送消息至服務端  
  46.         Chat.sendMessage = (function() {  
  47.             var message = document.getElementById('chat').value;  
  48.             if (message != '') {  
  49.                 Chat.socket.send(message);  
  50.                 document.getElementById('chat').value = '';  
  51.             }  
  52.         });  
  53.   
  54.         var Console = {};  
  55.   
  56.         Console.log = (function(message) {  
  57.             var console = document.getElementById('console');  
  58.             var p = document.createElement('p');  
  59.             p.style.wordWrap = 'break-word';  
  60.             p.innerHTML = message;  
  61.             console.appendChild(p);  
  62.             while (console.childNodes.length > 25) {  
  63.                 console.removeChild(console.firstChild);  
  64.             }  
  65.             console.scrollTop = console.scrollHeight;  
  66.         });  
  67.   
  68.         Chat.initialize();  
  69.   
  70.     </script>  

        注:完整代碼參見apache-tomcat-7.0.42\webapps\examples\websocket\chat.html
 
        chat例程服務端代碼如下:
[java] view plaincopy
  1. public class ChatWebSocketServlet extends WebSocketServlet {  
  2.   
  3.     private static final long serialVersionUID = 1L;  
  4.   
  5.     private static final String GUEST_PREFIX = "Guest";  
  6.   
  7.     private final AtomicInteger connectionIds = new AtomicInteger(0);  
  8.     private final Set<ChatMessageInbound> connections =  
  9.             new CopyOnWriteArraySet<ChatMessageInbound>();  
  10.     // 創建Inbound實例,WebSocketServlet子類必須實現的方法  
  11.     @Override  
  12.     protected StreamInbound createWebSocketInbound(String subProtocol,  
  13.             HttpServletRequest request) {  
  14.         return new ChatMessageInbound(connectionIds.incrementAndGet());  
  15.     }  
  16.     // MessageInbound子類,完成收到WebSocket消息後的邏輯處理  
  17.     private final class ChatMessageInbound extends MessageInbound {  
  18.   
  19.         private final String nickname;  
  20.   
  21.         private ChatMessageInbound(int id) {  
  22.             this.nickname = GUEST_PREFIX + id;  
  23.         }  
  24.         // Open事件  
  25.         @Override  
  26.         protected void onOpen(WsOutbound outbound) {  
  27.             connections.add(this);  
  28.             String message = String.format("* %s %s",  
  29.                     nickname, "has joined.");  
  30.             broadcast(message);  
  31.         }  
  32.         // Close事件  
  33.         @Override  
  34.         protected void onClose(int status) {  
  35.             connections.remove(this);  
  36.             String message = String.format("* %s %s",  
  37.                     nickname, "has disconnected.");  
  38.             broadcast(message);  
  39.         }  
  40.         // 二進制消息事件  
  41.         @Override  
  42.         protected void onBinaryMessage(ByteBuffer message) throws IOException {  
  43.             throw new UnsupportedOperationException(  
  44.                     "Binary message not supported.");  
  45.         }  
  46.         // 文本消息事件  
  47.         @Override  
  48.         protected void onTextMessage(CharBuffer message) throws IOException {  
  49.             // Never trust the client  
  50.             String filteredMessage = String.format("%s: %s",  
  51.                     nickname, HTMLFilter.filter(message.toString()));  
  52.             broadcast(filteredMessage);  
  53.         }  
  54.         // 向所有已連接的客戶端發送文本消息(廣播)  
  55.         private void broadcast(String message) {  
  56.             for (ChatMessageInbound connection : connections) {  
  57.                 try {  
  58.                     CharBuffer buffer = CharBuffer.wrap(message);  
  59.                     connection.getWsOutbound().writeTextMessage(buffer);  
  60.                 } catch (IOException ignore) {  
  61.                     // Ignore  
  62.                 }  
  63.             }  
  64.         }  
  65.     }  

        注:完整代碼參見apache-tomcat-7.0.42\webapps\examples\WEB-INF\classes\websocket\echo\ChatWebSocketServlet.java。

通過上述例程可以看到WebSocket廣播實際上是通過遍歷所有連接並通過每個連接向相應的客戶端發送消息實現的。

四、            WebSocket實戰

        實時向在線用戶推送通知是一個WebSocket應用的簡單場景,後臺提交通知信息以後,所在在線用戶均應很快收到這個通知。通過上述例程瞭解WebSocket後,可以嘗試編寫一個實現這個需求的WebSocket應用。

首先編寫一個用戶的Sample頁面,該頁面沒有實質的內容,但是在收到後臺發出的通知時要在右下角通過彈窗顯示通知的內容。其代碼如下:
[html] view plaincopy
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4.     <title>Receive Message</title>  
  5.     <style type="text/css">  
  6.         #winpop { width:200px; height:0px;   
  7.             position:absolute;   
  8.             right:0; bottom:0;   
  9.             border:1px solid #999999;   
  10.             margin:0;   
  11.             padding:1px;   
  12.             overflow:hidden;   
  13.             display:none;   
  14.             background:#FFFFFF}  
  15.         #winpop .con { width:100%; height:80px;   
  16.             line-height:80px;   
  17.             font-weight:bold;   
  18.             font-size:12px;   
  19.             color:#FF0000;   
  20.             text-align:center}  
  21.     </style>  
  22.     <script type="text/javascript">  
  23.         // 彈窗相關  
  24.         function tips_pop(){  
  25.             var MsgPop=document.getElementById("winpop");  
  26.             var popH=parseInt(MsgPop.style.height);  
  27.               
  28.             if(isNaN(popH)) {  
  29.                 popH = 0;  
  30.             }  
  31.               
  32.             if (popH==0){  
  33.                 MsgPop.style.display="block";  
  34.                 show=setInterval("changeH('up')",100);  
  35.             }  
  36.             else {  
  37.                 hide=setInterval("changeH('down')",100);  
  38.             }  
  39.         }  
  40.   
  41.         function changeH(str) {  
  42.             var MsgPop=document.getElementById("winpop");  
  43.             var popH=parseInt(MsgPop.style.height);  
  44.               
  45.             if(isNaN(popH)) {  
  46.                 popH = 0;  
  47.             }  
  48.               
  49.             if(str=="up"){     
  50.                 if (popH<=100){      
  51.                     MsgPop.style.height=(popH+4).toString()+"px";  
  52.                 }  
  53.                 else{    
  54.                     clearInterval(show);  
  55.                     setTimeout("tips_pop()", 5000);  
  56.                 }  
  57.             }  
  58.             if(str=="down"){   
  59.                 if (popH>=4){        
  60.                     MsgPop.style.height=(popH-4).toString()+"px";  
  61.                 }  
  62.                 else{          
  63.                     clearInterval(hide);      
  64.                     MsgPop.style.display="none";    
  65.                 }  
  66.             }  
  67.         }  
  68.           
  69.         // WebSocket相關  
  70.         var ws = null;  
  71.           
  72.         function connect() {  
  73.             var target = 'ws://' + window.location.host   
  74.                 + "/test/NotifyWebSocketServlet";  
  75.               
  76.             if ('WebSocket' in window) {  
  77.                 ws = new WebSocket(target);  
  78.             } else if ('MozWebSocket' in window) {  
  79.                 ws = new MozWebSocket(target);  
  80.             } else {  
  81.                 alert('WebSocket is not supported by this browser.');  
  82.                 return;  
  83.             }  
  84.               
  85.             ws.onopen = function () {  
  86.                  document.getElementById('msg').innerHTML =   
  87.                     "WebSocket has opened, Waiting message.......";  
  88.             };  
  89.               
  90.             ws.onmessage = function (event) {  
  91.                 document.getElementById('infomsg').innerHTML = event.data;  
  92.                 tips_pop();  
  93.             };  
  94.               
  95.             ws.onclose = function () {  
  96.                 document.getElementById('msg').innerHTML = "WebSocket has closed";  
  97.             };  
  98.         }  
  99.   
  100.         function disconnect() {  
  101.             if (ws != null) {  
  102.                 ws.close();  
  103.                 ws = null;  
  104.             }  
  105.         }         
  106.           
  107.         connect();          
  108.     </script>  
  109. </head>  
  110. <body>  
  111.     <h1 align="center" id="msg">Try to connect websocket.</h1>  
  112.     <div id="winpop">  
  113.         <div class="con" id="infomsg"></div>  
  114.     </div>  
  115. </body>  
  116. </html>  

        當用戶界面打開時,它會嘗試通過/test/NotifyWebSocketServlet建立與服務器的WebSocket連接,而NotifyWebSocketServlet的實現代碼則如下:
[java] view plaincopy
  1. package net.yanzhijun.example;  
  2.   
  3. import javax.servlet.ServletContext;  
  4. import javax.servlet.http.HttpServletRequest;  
  5.   
  6. import org.apache.catalina.websocket.StreamInbound;  
  7. import org.apache.catalina.websocket.WebSocketServlet;  
  8.   
  9. public class NotifyWebSocketServlet extends WebSocketServlet {  
  10.   
  11.     private static final long serialVersionUID = 1L;      
  12.   
  13.     @Override  
  14.     protected StreamInbound createWebSocketInbound(String subProtocol,  
  15.             HttpServletRequest request) {  
  16.         ServletContext application = this.getServletContext();  
  17.         return new NofityMessageInbound(application);  
  18.     }      
  19. }  

        與Tomcat給出的示例代碼不同的是,在NotifyWebSocketServlet中並未將繼承於MessageInboundNofityMessageInbound作爲一個內嵌類。前述示例代碼中發送消息和接收消息都是在同一組客戶端頁面和服務端響應Servlet間進行的,而當前需要實現是在一個頁面中提交通知,而在其它用戶的頁面上顯示通知信息,因此需要將所有客戶端與服務端的連接存儲一個全局域中,故而NofityMessageInbound將不只在當前Servlet中被使用,所以有必要將其獨立出來。

        NofityMessageInbound的完整代碼如下:
[java] view plaincopy
  1. package net.yanzhijun.example;  
  2.   
  3. import java.nio.CharBuffer;  
  4. import java.nio.ByteBuffer;  
  5. import java.io.IOException;  
  6. import java.util.Set;  
  7. import java.util.concurrent.CopyOnWriteArraySet;  
  8.   
  9. import javax.servlet.ServletContext;  
  10.   
  11. import org.apache.catalina.websocket.WsOutbound;  
  12. import org.apache.catalina.websocket.MessageInbound;  
  13.   
  14. public class NofityMessageInbound extends MessageInbound {  
  15.     private ServletContext application;  
  16.     private Set<NofityMessageInbound> connections = null;  
  17.       
  18.     public NofityMessageInbound(ServletContext application) {  
  19.         this.application = application;  
  20.         connections =   
  21.             (Set<NofityMessageInbound>)application.getAttribute("connections");  
  22.         if(connections == null) {  
  23.             connections =  
  24.                 new CopyOnWriteArraySet<NofityMessageInbound>();  
  25.         }  
  26.     }  
  27.       
  28.     @Override  
  29.     protected void onOpen(WsOutbound outbound) {  
  30.         connections.add(this);      
  31.         application.setAttribute("connections", connections);  
  32.     }  
  33.   
  34.     @Override  
  35.     protected void onClose(int status) {  
  36.         connections.remove(this);  
  37.         application.setAttribute("connections", connections);  
  38.     }  
  39.   
  40.     @Override  
  41.     protected void onBinaryMessage(ByteBuffer message) throws IOException {  
  42.         throw new UnsupportedOperationException(  
  43.                 "message not supported.");  
  44.     }  
  45.   
  46.     @Override  
  47.     protected void onTextMessage(CharBuffer message) throws IOException {  
  48.         throw new UnsupportedOperationException(  
  49.                 "message not supported.");  
  50.     }  
  51. }  

        後臺發送通知的頁面實現的相當簡單,只是一個表單提交一條通知信息。
[html] view plaincopy
  1. <span style="font-size:14px;"><%@ page contentType="text/html;charset=UTF-8" language="java" %>  
  2. <html>  
  3.     <head>  
  4.         <title>PushMessage</title>  
  5.     </head>  
  6.     <body>  
  7.         <h1 align="Center">Online Broadcast</h1>  
  8.         <form method="post" action="PushMessageServlet">  
  9.             <p>Message:<br/>  
  10.                 <textarea name="message" rows="5" cols="30"></textarea>  
  11.             </p>  
  12.             <p><input type="submit" value="Send">    
  13.                 <input type="reset" value="Reset">  
  14.             </p>  
  15.         </form>  
  16.     </body>  
  17. </html>  
  18. </span>  

       接收提交通知的Servlet是PushMessageServlet,它在收到後臺提交的通知後,就通過所有用戶的WebSocket連接將通知發送出去。
[java] view plaincopy
  1. package net.yanzhijun.example;  
  2.   
  3. import java.io.PrintWriter;  
  4. import java.nio.CharBuffer;  
  5. import java.util.Set;  
  6. import java.util.concurrent.CopyOnWriteArraySet;  
  7. import java.io.IOException;  
  8.   
  9. import javax.servlet.ServletContext;  
  10. import javax.servlet.ServletException;  
  11. import javax.servlet.http.HttpServlet;  
  12. import javax.servlet.http.HttpServletRequest;  
  13. import javax.servlet.http.HttpServletResponse;  
  14.   
  15. public class PushMessageServlet extends HttpServlet {  
  16.      private static final long serialVersionUID = 1L;  
  17.        
  18.     @Override  
  19.     public void doGet(HttpServletRequest request,  
  20.                       HttpServletResponse response)  
  21.         throws IOException, ServletException {  
  22.             doPost(request, response);  
  23.         }  
  24.           
  25.     @Override  
  26.     public void doPost(HttpServletRequest request,  
  27.                       HttpServletResponse response)  
  28.         throws IOException, ServletException {  
  29.               
  30.         request.setCharacterEncoding("UTF-8");  
  31.         response.setContentType("text/html;charset=UTF-8");  
  32.           
  33.         PrintWriter out = response.getWriter();  
  34.           
  35.         String message = request.getParameter("message");          
  36.         if(message == null || message.length() == 0) {              
  37.             out.println("The message is empty!");  
  38.             return;  
  39.         }  
  40.           
  41.         // 廣播消息  
  42.         broadcast(message);  
  43.           
  44.         out.println("Send success!");          
  45.     }  
  46.       
  47.     // 將參數中的消息發送至所有在線客戶端  
  48.     private void broadcast(String message) {  
  49.         ServletContext application=this.getServletContext();      
  50.         Set<NofityMessageInbound> connections =   
  51. (Set<NofityMessageInbound>)application.getAttribute("connections");  
  52.         if(connections == null){  
  53.             return;  
  54.         }  
  55.           
  56.         for (NofityMessageInbound connection : connections) {  
  57.             try {  
  58.                 CharBuffer buffer = CharBuffer.wrap(message);  
  59.                 connection.getWsOutbound().writeTextMessage(buffer);  
  60.             } catch (IOException ignore) {  
  61.                 // Ignore  
  62.             }  
  63.         }  
  64.     }   
  65. }  

        編譯相關文件並完成部署,嘗試在後臺發送消息,可以看到用戶界面右下角出現的彈窗中顯示了後臺所提交的內容。
                                                  圖4

五、            WebSocket總結

    通過以上例程和實例可以看出,從開發角度使用WebSocket相當容易,基本只需要創建WebSocket實例並對關心的事件進行處理就可以了;從應用角度WebSocket提供了優異的性能,圖 5是來自websocket.org的性能測試圖表(http://www.websocket.org/quantum.html),可以看到當併發和負載增加時輪詢與WebSocket的差異。
                                                                   圖5
         (以上例程客戶端在IE10.0和Chrom28.0下測試通過。)
 
         歡迎訪問夢斷酒醒的博客http://blog.csdn.net/ishallwn
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章