`
chriszeng87
  • 浏览: 741740 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

WebSocket初探【转】

阅读更多

众所周知,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
分享到:
评论
1 楼 wahahachuang8 2018-03-02  
我觉得这种东西自己开发太麻烦了,就别自己捣鼓了,找个第三方,方便,GoEasy就挺不错的,我昨天试了一下,代码简洁易懂,几分钟我就洗了一个自己的实时推送功能;官网: http://goeasy.io/

相关推荐

    详解Tomcat7中WebSocket初探

    本文将深入探讨在Tomcat 7中如何实现WebSocket,并介绍WebSocket的基本原理和编程接口。 首先,WebSocket协议的建立基于HTTP的Upgrade机制,通过一次特殊的HTTP请求(通常称为WebSocket握手)来完成。在握手过程中...

    GWT通信机制初探

    **GWT(Google Web Toolkit)通信机制初探** GWT,全称为Google Web Toolkit,是Google推出的一个开源JavaScript开发框架,允许开发者使用Java语言来编写Web应用。它提供了丰富的组件库、编译器以及强大的客户端-...

    grizzly初探

    **Grizzly 框架初探** Grizzly 是一个由 Sun Microsystems 开发并维护的开源框架,主要用于构建高性能、轻量级的网络应用服务器。它是一个 Java NIO(非阻塞 I/O)库,用于创建高并发、低延迟的网络服务。在 Java ...

    Android开发常用技术初探.pdf

    《Android开发常用技术初探》 在移动互联网的飞速发展中,Android系统占据了智能手机市场的主要份额,成为开发者和用户关注的焦点。Android以其开源、灵活的特性吸引了众多开发者,不断推动着应用程序的创新和发展...

    Spring Boot 初探 | 第一篇:第一个Spring Boot程序(示例程序)

    **Spring Boot 初探:构建你的第一个应用** Spring Boot 是由 Pivotal 团队提供的全新框架,旨在简化 Spring 应用程序的初始搭建以及开发过程。它集成了大量常用的第三方库配置,如 JDBC、MongoDB、JPA、RabbitMQ、...

    Examples-Node.js_20140424:Node.js开发实务:初探JavaScript 应用程式设计书本范例

    《Node.js开发实务:初探JavaScript 应用程式设计》是一本专为开发者介绍Node.js技术的书籍。这本书的核心在于帮助读者理解如何利用JavaScript进行服务器端编程,通过一系列的实例来深入浅出地讲解Node.js的基本概念...

    Java Springboot学习资料.rar

    初探RabbitMQ消息队列 RabbitMQ延迟队列 actuator 服务监控与管理 actuator与spring-boot-admin 定时任务详解 文件上传 重复提交(分布式锁) 重复提交(本地锁) WebSocket 安全框架(Shiro) 分布式限流 集成...

    经典坦克大战游戏html5 2个

    此外,WebSocket技术的支持使得多人在线对战成为可能,带来了更多合作与竞争的机会,极大地丰富了游戏的社交元素。 当我们打开游戏的压缩包,可能会发现“坦克”文件夹里包含着游戏的主体代码,这些代码用HTML、CSS...

    Node.js-first-projects

    《Node.js初探:从项目实践中学习》 Node.js,作为JavaScript在服务器端的运行环境,自2009年诞生以来,凭借其高效的非阻塞I/O模型、丰富的生态系统和与前端技术的无缝对接,迅速赢得了开发者们的青睐。本项目...

    研究Web chat libraryh3lp第一天

    常见的实现方式包括WebSocket、Server-Sent Events(SSE)和Long Polling等。 2. **Web聊天库**:这些是预构建的代码库,为开发人员提供了创建在线聊天功能的快捷方式。它们通常提供消息传递、用户认证、实时同步等...

    led-socket-hello-world:一个小型的现场演示,展示了如何将硬件连接到您的浏览器和Web

    《LED插座与Web世界的初次握手:JavaScript驱动的硬件交互初探》 在现代科技日新月异的时代,硬件与软件的结合已经不再局限于传统的桌面应用,而是深入到了我们的日常生活,甚至是互联网的每一个角落。"led-socket-...

    mmo_game_zinxV1.0

    《mmo_game_zinxV1.0:Golang构建大型多人在线游戏服务器框架初探》 在当前的游戏开发领域,高效、稳定、可扩展的服务器架构是关键所在,尤其是对于大型多人在线(MMO)游戏而言。"mmo_game_zinxV1.0"项目就是这样一...

    socke编程初级教程

    **袜子编程初探:构建网络通信的基础** 在IT领域,Socket编程是构建网络应用程序的核心技术之一,尤其对于网络通信的实现至关重要。本教程旨在为初学者提供一个基础的Socket编程学习路径,帮助理解网络通信的基本...

    NachikuAssventurePrototype

    《 NachikuAssventurePrototype:C#游戏开发初探》 NachikuAssventurePrototype是一个基于C#编程语言的游戏原型项目,它展示了如何利用C#来构建一个互动式冒险游戏。通过这个项目,我们可以深入理解C#在游戏开发中...

    jetty 6 指南书

    2. **Jetty 初探** - **下载与安装**:提供Jetty的下载地址和简单安装步骤,包括Windows和Linux下的操作。 - **启动与停止**:通过java命令快速启动和停止Jetty服务,以及使用start.jar的深入解析。 - **Windows...

    《Jetty6_指南书》

    - **WebSocket支持**: 支持WebSocket协议,可用于构建实时通信应用。 - **安全性**: 提供了多种安全机制,包括SSL/TLS加密支持。 - **高性能**: 通过Continuations机制支持异步处理,减少资源消耗。 - **轻量级**: ...

    LFG:寻找群组Web应用程序

    《构建寻找群组Web应用程序——基于JavaScript的LFG平台初探》 在互联网技术日新月异的今天,游戏社区的交流方式也在不断进化。"LFG:寻找群组Web应用程序"是一个旨在为通用视频游戏玩家提供便捷组队服务的在线平台...

    Car-Racing-Game-Stage-1:通过键入不同名称在播放按钮中创建:多人

    《多人赛车游戏开发初探:基于JavaScript的Car-Racing-Game-Stage-1》 在当前的数字时代,游戏开发已经成为一个热门且充满创新的领域,尤其在多人在线游戏方面,开发者们不断尝试新的技术和设计理念。"Car-Racing-...

    PinguinDive:这是我为文凭而制作的第一款HTML5游戏

    《企鹅潜水:一款基于HTML5与enchant.js的游戏开发初探》 HTML5作为现代网页技术的重要组成部分,以其强大的跨平台性和丰富的媒体支持能力,为游戏开发开辟了新的道路。"PinguinDive"是一款由开发者用HTML5语言构建...

Global site tag (gtag.js) - Google Analytics