WebSocket(二)

HTML5 WebSockets是HTML5中最强大的通信功能,它定义了一个全双工通信信道,仅通过Web上的一个Socket即可进行通信。

目前实时Web应用的实现方式,大部分是围绕轮询和其他服务器端推送技术展开的,Comet、轮询、长轮询、流(streaming)解决方案,所有这些提供实时数据的方式包含有大量额外的、不必要的报头数据,会造成传输延迟。最重要的是为了在半双工HTTP的基础上模拟全双工通信,目前的许多解决方案都是使用了两个连接:一个用于下行数据流,另一个用于上行数据流。这两个连接的保持和协作也会造成大量的资源消耗,并增加了复杂度。

WebSockets就是解决以上问题的方案。为了建立WebSocket通信,客户端和服务器在初始握手时,将HTTP协议升级到WebSocket协议。

 

现在WebSocket服务器有很多,还在开发中的更多。有一下几种:

  • Kaazing WebSocket Gateway:一种基于Java的WebSocket网关。
  • mod_pywebsocket:一种基于Python的Apache HTTP服务器扩展。
  • Netty:一种包含WebSocket的Java框架。
  • node.js:一种驱动多个WebSocket服务器的服务器端JavaScript框架。
对于非原生支持WebSocket的浏览器来说,Kazzing的WebSocket网关包含了完整的客户端浏览器WebSocket模拟支持。

二、HTML5 WebSockets API

1、浏览器支持情况检测

检测浏览器支持情况代码  收藏代码
  1. function loadDemo() {  
  2.     if (window.WebSocket) {  
  3.         //supported  
  4.     } else {  
  5.         // not supported  
  6.     }  
  7. }  

2、WebSocket对象的创建和服务器连接

要连接通信端点,只需要创建一个新的WebSocket实例,并提供希望连接的对端URL。ws://和wss://前缀分别表示WebSocket连接和安全的WebSocket连接。

Js代码  收藏代码
  1. url = "ws://localhost:8080/echo";  
  2. w = new WebSocket(url);  

 

建立WebSocket连接时,可以列出Web应用能够使用的协议。WebSocket构造函数的第二个参数既可以是字符串,也可以是字符串组。

Js代码  收藏代码
  1. w = new WebSocket(url, ["proto1""proto2"]);  

假设proto1和proto2是定义明确、可能已注册且标准化的协议名称,它们能够同时为客户端和服务器端所理解。服务器会从列表中选择首选协议。

Js代码  收藏代码
  1. onopen = function(e) {  
  2.     //确定服务器选择的协议  
  3.     log(e.target.protocol);  
  4. }  

 

3、添加事件监听器

WebSocket编程遵循异步编程模型;打开socket后,只需等待事件发生,而不需要主动向服务器轮询,所以需要在WebSocket对象中添加回调函数来监听事件。

WebSocket对象有三个事件:open、close和message。

Js代码  收藏代码
  1. w.onopen = function() {  
  2.     log("open");  
  3.     w.send("send message");  
  4. }  
  5. w.onmessage = function(e) {  
  6.     log(e.data);  
  7. }  
  8. w.onclose = function(e) {  
  9.     log("closed");  
  10. }  
  11. w.onerror = function(e) {  
  12.     log("error");  
  13. }  

 

4、发送消息

当socket处于打开状态(即onopen之后,onclose之前),可以用send方法来发送消息。消息发送完,可以调用close方法来终止连接,也可以不这么做,让其保持打开状态。

Js代码  收藏代码
  1. w.send();  

 

你可能想测算在调用Send()函数之前,有多少数据备份在发送缓冲区中。bufferAmount属性表示已在WebSocket上发送但尚未写入网络的字节数。它对于调节发送速率很有用。

Js代码  收藏代码
  1. document.getElementById("sendButton").onclick = function() {  
  2.     if (w.bufferedAmount < bufferThreshold) {  
  3.         w.send(document.getElementById("inputMessage").value);  
  4.     }  
  5. }  

WebSocket API支持以二进制数据的形式发送Blob和ArrayBuffer实例

Js代码  收藏代码
  1. var a = new Uint8Array([8, 6, 7, 5, 3, 0, 9]);  
  2. w.send(a.buffer);  

 

三、例子

书中介绍了一个用Python写的Echo服务,书中的代码可以在http://www.apress.com/9781430238645 的“Source Code/Downloads”中下载下载。

对于JAVA开发人员Tomcat是最熟悉的,在Tomcat8中已经实现了WebSocket API 1.0。Tomcat7也会在不久实现(现在的实现不是WebSocket API 1.0)。

在这里写一下在Tomcat8下执行的例子。

例子由客户端页面和WebSocket服务程序组成,功能在Echo基础上增加一个服务端定时发送信息的功能。

 

页面程序 ws.html代码  收藏代码
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head>  
  4. <meta charset="UTF-8">  
  5. <title>Test WebSocket</title>  
  6. <script type="text/javascript">  
  7.     //显示信息  
  8.     var log = function(s) {  
  9.         if (document.readyState !== "complete") {  
  10.             log.buffer.push(s);  
  11.         } else {  
  12.             document.getElementById("output").textContent += (s + "\n");  
  13.             document.getElementById("outputdiv").scrollTop = document.getElementById("outputdiv").scrollHeight;  
  14.         }  
  15.     }  
  16.     log.buffer = [];  
  17.     //显示连接状态  
  18.     function setConnected(status) {  
  19.         document.getElementById("socketstatus").innerHTML = status;  
  20.     }  
  21.     var ws = null;  
  22.       
  23.     //连接  
  24.     function connect() {  
  25.         if (ws != null) {  
  26.             log("现已连接");  
  27.             return ;  
  28.         }  
  29.         url = "ws://localhost:8080/websocket/mywebsocket";  
  30.         if ('WebSocket' in window) {  
  31.             ws = new WebSocket(url);  
  32.         } else if ('MozWebSocket' in window) {  
  33.             ws = new MozWebSocket(url);  
  34.         } else {  
  35.             alert("您的浏览器不支持WebSocket。");  
  36.             return ;  
  37.         }  
  38.         ws.onopen = function() {  
  39.             log("open");  
  40.             setConnected("已连接");  
  41.             //设置发信息送类型为:ArrayBuffer  
  42.             ws.binaryType = "arraybuffer";  
  43.               
  44.             //发送一个字符串和一个二进制信息  
  45.             ws.send("thank you for accepting this WebSocket request");  
  46.             var a = new Uint8Array([8675309]);  
  47.             ws.send(a.buffer);  
  48.         }  
  49.         ws.onmessage = function(e) {  
  50.             log(e.data.toString());  
  51.         }  
  52.         ws.onclose = function(e) {  
  53.             log("closed");  
  54.         }  
  55.         ws.onerror = function(e) {  
  56.             log("error");  
  57.         }  
  58.     }  
  59.       
  60.     //断开连接  
  61.     function disconnect() {  
  62.         if (ws != null) {  
  63.             ws.close();  
  64.             ws = null;  
  65.             setConnected("已断开");  
  66.         }  
  67.     }  
  68.       
  69.     window.onload = function() {  
  70.         connect();  
  71.         log(log.buffer.join("\n"));  
  72.         //发送页面上输入框的信息  
  73.         document.getElementById("sendButton").onclick = function() {  
  74.             if (ws != null) {  
  75.                 ws.send(document.getElementById("inputMessage").value);   
  76.             }  
  77.         }  
  78.         //停止心跳信息  
  79.         document.getElementById("stopButton").onclick = function() {  
  80.             if (ws != null) {  
  81.                 var a = new Uint8Array([19201516]);  
  82.                 ws.send(a.buffer);   
  83.             }  
  84.         }  
  85.     }  
  86. </script>  
  87. </head>  
  88. <body οnunlοad="disconnect();">  
  89.     <div>连接状态:<span id="socketstatus"></span></div>  
  90.     <div>  
  91.         <input type="text" id="inputMessage" value="Hello, WebSocket!">  
  92.         <button id="sendButton">发送</button><button id="stopButton" style="margin-left:15px">停止心跳信息</button>  
  93.     </div>  
  94.     <div>  
  95.         <button id="connect" οnclick="connect();">连接</button>  
  96.         <button id="disconnect" οnclick="disconnect();">断开</button>  
  97.     </div>  
  98.     <div style="height:300px; overflow:auto;" id="outputdiv">  
  99.         <pre id="output"></pre>  
  100.     </div>  
  101. </body>  
  102. </html>  

 

服务端程序用注解方式驱动

 

Websocket服务端程序代码  收藏代码
  1. package com.test.wsocket;  
  2.   
  3. import java.io.IOException;  
  4. import java.nio.ByteBuffer;  
  5. import java.util.Random;  
  6. import java.util.Timer;  
  7. import java.util.TimerTask;  
  8.   
  9. import javax.websocket.OnClose;  
  10. import javax.websocket.OnMessage;  
  11. import javax.websocket.OnOpen;  
  12. import javax.websocket.PongMessage;  
  13. import javax.websocket.Session;  
  14. import javax.websocket.server.ServerEndpoint;  
  15.   
  16. @ServerEndpoint("/mywebsocket")  
  17. public class MyWebSocket {  
  18.       
  19.     private Session session;  
  20.     private static final Random random = new Random();  
  21.     private Timer timer = null;  
  22.     //停止信息信息指令  
  23.     private static final ByteBuffer stopbuffer  = ByteBuffer.wrap(new byte[]{19201516});  
  24.       
  25.     /**  
  26.      * 打开连接时执行  
  27.      * @param session  
  28.      */  
  29.     @OnOpen  
  30.     public void start(Session session) {  
  31.         this.session = session;  
  32.         try {  
  33.             System.out.println("open");  
  34.             if (session.isOpen()) {  
  35.                 //设置心跳发送信息。每2秒发送一次信息。  
  36.                 timer = new Timer(true);  
  37.                 timer.schedule(task, 10002000);  
  38.             }  
  39.         } catch (Exception e) {  
  40.             try {  
  41.                 session.close();  
  42.             } catch (IOException e1) {}  
  43.         }  
  44.     }  
  45.   
  46.     /**  
  47.      * 接收信息时执行  
  48.      * @param session  
  49.      * @param msg 字符串信息  
  50.      * @param last  
  51.      */  
  52.     @OnMessage  
  53.     public void echoTextMessage(Session session, String msg, boolean last) {  
  54.         try {  
  55.             if (session.isOpen()) {  
  56.                 System.out.println("string:" + msg);  
  57.                 session.getBasicRemote().sendText(msg, last);  
  58.             }  
  59.         } catch (IOException e) {  
  60.             try {  
  61.                 session.close();  
  62.             } catch (IOException e1) {  
  63.                 // Ignore  
  64.             }  
  65.         }  
  66.     }  
  67.   
  68.     /**  
  69.      * 接收信息时执行  
  70.      * @param session  
  71.      * @param bb 二进制数组  
  72.      * @param last  
  73.      */  
  74.     @OnMessage  
  75.     public void echoBinaryMessage(Session session, ByteBuffer bb, boolean last) {  
  76.         try {  
  77.             if (session.isOpen()) {  
  78.                 //如果是停止心跳指令,则停止心跳信息  
  79.                 if (bb.compareTo(stopbuffer) == 0) {  
  80.                     if (timer != null) {  
  81.                         timer.cancel();  
  82.                     }  
  83.                 } else {  
  84.                     session.getBasicRemote().sendBinary(bb, last);  
  85.                 }  
  86.             }  
  87.         } catch (IOException e) {  
  88.             try {  
  89.                 session.close();  
  90.             } catch (IOException e1) {  
  91.                 // Ignore  
  92.             }  
  93.         }  
  94.     }  
  95.       
  96.     /**  
  97.      * 接收pong指令时执行。  
  98.      *  
  99.      * @param pm    Ignored.  
  100.      */  
  101.     @OnMessage  
  102.     public void echoPongMessage(PongMessage pm) {  
  103.         // 无处理  
  104.     }  
  105.       
  106.     @OnClose  
  107.     public void end(Session session) {  
  108.         try {  
  109.             System.out.println("close");  
  110.             if (timer != null) {  
  111.                 timer.cancel();  
  112.             }  
  113.         } catch(Exception e) {  
  114.         }  
  115.     }  
  116.       
  117.     /*  
  118.      * 发送心跳信息  
  119.      */  
  120.     public void sendLong(long param) {  
  121.         try {  
  122.             if (session.isOpen()) {  
  123.                 this.session.getBasicRemote().sendText(String.valueOf(param));  
  124.             }  
  125.         } catch (IOException e) {  
  126.             try {  
  127.                 this.session.close();  
  128.             } catch (IOException e1) {}  
  129.         }  
  130.     }  
  131.       
  132.     /**  
  133.      * 心跳任务。发送随机数。  
  134.      */  
  135.     TimerTask task = new TimerTask() {  
  136.         public void run() {     
  137.             long param = random.nextInt(100);  
  138.             sendLong(param);  
  139.         }     
  140.     };  
  141.   
  142. }  

 

 

Web.xml代码  收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2. <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" id="WebApp_ID" version="3.0">  
  3.   <display-name>websocket</display-name>  
  4.   <welcome-file-list>  
  5.     <welcome-file>index.html</welcome-file>  
  6.   </welcome-file-list>  
  7.     
  8.   <filter>  
  9.         <filter-name>Set Character Encoding</filter-name>  
  10.         <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>  
  11.         <init-param>  
  12.             <param-name>encoding</param-name>  
  13.             <param-value>UTF-8</param-value>  
  14.         </init-param>  
  15.         <init-param>  
  16.             <param-name>ignore</param-name>  
  17.             <param-value>true</param-value>  
  18.         </init-param>  
  19.     </filter>  
  20.       
  21.     <filter-mapping>  
  22.         <filter-name>Set Character Encoding</filter-name>  
  23.         <url-pattern>/*</url-pattern>  
  24.     </filter-mapping>  
  25. </web-app>  

 

将以上程序部署到Tomcat8下,启动服务。


发布了35 篇原创文章 · 获赞 9 · 访问量 6万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章