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框架。
二、HTML5 WebSockets API
1、瀏覽器支持情況檢測
- function loadDemo() {
- if (window.WebSocket) {
- //supported
- } else {
- // not supported
- }
- }
2、WebSocket對象的創建和服務器連接
要連接通信端點,只需要創建一個新的WebSocket實例,並提供希望連接的對端URL。ws://和wss://前綴分別表示WebSocket連接和安全的WebSocket連接。
- url = "ws://localhost:8080/echo";
- w = new WebSocket(url);
建立WebSocket連接時,可以列出Web應用能夠使用的協議。WebSocket構造函數的第二個參數既可以是字符串,也可以是字符串組。
- w = new WebSocket(url, ["proto1", "proto2"]);
假設proto1和proto2是定義明確、可能已註冊且標準化的協議名稱,它們能夠同時爲客戶端和服務器端所理解。服務器會從列表中選擇首選協議。
- onopen = function(e) {
- //確定服務器選擇的協議
- log(e.target.protocol);
- }
3、添加事件監聽器
WebSocket編程遵循異步編程模型;打開socket後,只需等待事件發生,而不需要主動向服務器輪詢,所以需要在WebSocket對象中添加回調函數來監聽事件。
WebSocket對象有三個事件:open、close和message。
- w.onopen = function() {
- log("open");
- w.send("send message");
- }
- w.onmessage = function(e) {
- log(e.data);
- }
- w.onclose = function(e) {
- log("closed");
- }
- w.onerror = function(e) {
- log("error");
- }
4、發送消息
當socket處於打開狀態(即onopen之後,onclose之前),可以用send方法來發送消息。消息發送完,可以調用close方法來終止連接,也可以不這麼做,讓其保持打開狀態。
- w.send();
你可能想測算在調用Send()函數之前,有多少數據備份在發送緩衝區中。bufferAmount屬性表示已在WebSocket上發送但尚未寫入網絡的字節數。它對於調節發送速率很有用。
- document.getElementById("sendButton").onclick = function() {
- if (w.bufferedAmount < bufferThreshold) {
- w.send(document.getElementById("inputMessage").value);
- }
- }
WebSocket API支持以二進制數據的形式發送Blob和ArrayBuffer實例
- var a = new Uint8Array([8, 6, 7, 5, 3, 0, 9]);
- 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基礎上增加一個服務端定時發送信息的功能。
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="UTF-8">
- <title>Test WebSocket</title>
- <script type="text/javascript">
- //顯示信息
- var log = function(s) {
- if (document.readyState !== "complete") {
- log.buffer.push(s);
- } else {
- document.getElementById("output").textContent += (s + "\n");
- document.getElementById("outputdiv").scrollTop = document.getElementById("outputdiv").scrollHeight;
- }
- }
- log.buffer = [];
- //顯示連接狀態
- function setConnected(status) {
- document.getElementById("socketstatus").innerHTML = status;
- }
- var ws = null;
- //連接
- function connect() {
- if (ws != null) {
- log("現已連接");
- return ;
- }
- url = "ws://localhost:8080/websocket/mywebsocket";
- if ('WebSocket' in window) {
- ws = new WebSocket(url);
- } else if ('MozWebSocket' in window) {
- ws = new MozWebSocket(url);
- } else {
- alert("您的瀏覽器不支持WebSocket。");
- return ;
- }
- ws.onopen = function() {
- log("open");
- setConnected("已連接");
- //設置發信息送類型爲:ArrayBuffer
- ws.binaryType = "arraybuffer";
- //發送一個字符串和一個二進制信息
- ws.send("thank you for accepting this WebSocket request");
- var a = new Uint8Array([8, 6, 7, 5, 3, 0, 9]);
- ws.send(a.buffer);
- }
- ws.onmessage = function(e) {
- log(e.data.toString());
- }
- ws.onclose = function(e) {
- log("closed");
- }
- ws.onerror = function(e) {
- log("error");
- }
- }
- //斷開連接
- function disconnect() {
- if (ws != null) {
- ws.close();
- ws = null;
- setConnected("已斷開");
- }
- }
- window.onload = function() {
- connect();
- log(log.buffer.join("\n"));
- //發送頁面上輸入框的信息
- document.getElementById("sendButton").onclick = function() {
- if (ws != null) {
- ws.send(document.getElementById("inputMessage").value);
- }
- }
- //停止心跳信息
- document.getElementById("stopButton").onclick = function() {
- if (ws != null) {
- var a = new Uint8Array([1, 9, 2, 0, 1, 5, 1, 6]);
- ws.send(a.buffer);
- }
- }
- }
- </script>
- </head>
- <body οnunlοad="disconnect();">
- <div>連接狀態:<span id="socketstatus"></span></div>
- <div>
- <input type="text" id="inputMessage" value="Hello, WebSocket!">
- <button id="sendButton">發送</button><button id="stopButton" style="margin-left:15px">停止心跳信息</button>
- </div>
- <div>
- <button id="connect" οnclick="connect();">連接</button>
- <button id="disconnect" οnclick="disconnect();">斷開</button>
- </div>
- <div style="height:300px; overflow:auto;" id="outputdiv">
- <pre id="output"></pre>
- </div>
- </body>
- </html>
服務端程序用註解方式驅動
- package com.test.wsocket;
- import java.io.IOException;
- import java.nio.ByteBuffer;
- import java.util.Random;
- import java.util.Timer;
- import java.util.TimerTask;
- import javax.websocket.OnClose;
- import javax.websocket.OnMessage;
- import javax.websocket.OnOpen;
- import javax.websocket.PongMessage;
- import javax.websocket.Session;
- import javax.websocket.server.ServerEndpoint;
- @ServerEndpoint("/mywebsocket")
- public class MyWebSocket {
- private Session session;
- private static final Random random = new Random();
- private Timer timer = null;
- //停止信息信息指令
- private static final ByteBuffer stopbuffer = ByteBuffer.wrap(new byte[]{1, 9, 2, 0, 1, 5, 1, 6});
- /**
- * 打開連接時執行
- * @param session
- */
- @OnOpen
- public void start(Session session) {
- this.session = session;
- try {
- System.out.println("open");
- if (session.isOpen()) {
- //設置心跳發送信息。每2秒發送一次信息。
- timer = new Timer(true);
- timer.schedule(task, 1000, 2000);
- }
- } catch (Exception e) {
- try {
- session.close();
- } catch (IOException e1) {}
- }
- }
- /**
- * 接收信息時執行
- * @param session
- * @param msg 字符串信息
- * @param last
- */
- @OnMessage
- public void echoTextMessage(Session session, String msg, boolean last) {
- try {
- if (session.isOpen()) {
- System.out.println("string:" + msg);
- session.getBasicRemote().sendText(msg, last);
- }
- } catch (IOException e) {
- try {
- session.close();
- } catch (IOException e1) {
- // Ignore
- }
- }
- }
- /**
- * 接收信息時執行
- * @param session
- * @param bb 二進制數組
- * @param last
- */
- @OnMessage
- public void echoBinaryMessage(Session session, ByteBuffer bb, boolean last) {
- try {
- if (session.isOpen()) {
- //如果是停止心跳指令,則停止心跳信息
- if (bb.compareTo(stopbuffer) == 0) {
- if (timer != null) {
- timer.cancel();
- }
- } else {
- session.getBasicRemote().sendBinary(bb, last);
- }
- }
- } catch (IOException e) {
- try {
- session.close();
- } catch (IOException e1) {
- // Ignore
- }
- }
- }
- /**
- * 接收pong指令時執行。
- *
- * @param pm Ignored.
- */
- @OnMessage
- public void echoPongMessage(PongMessage pm) {
- // 無處理
- }
- @OnClose
- public void end(Session session) {
- try {
- System.out.println("close");
- if (timer != null) {
- timer.cancel();
- }
- } catch(Exception e) {
- }
- }
- /*
- * 發送心跳信息
- */
- public void sendLong(long param) {
- try {
- if (session.isOpen()) {
- this.session.getBasicRemote().sendText(String.valueOf(param));
- }
- } catch (IOException e) {
- try {
- this.session.close();
- } catch (IOException e1) {}
- }
- }
- /**
- * 心跳任務。發送隨機數。
- */
- TimerTask task = new TimerTask() {
- public void run() {
- long param = random.nextInt(100);
- sendLong(param);
- }
- };
- }
- <?xml version="1.0" encoding="UTF-8"?>
- <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">
- <display-name>websocket</display-name>
- <welcome-file-list>
- <welcome-file>index.html</welcome-file>
- </welcome-file-list>
- <filter>
- <filter-name>Set Character Encoding</filter-name>
- <filter-class>org.apache.catalina.filters.SetCharacterEncodingFilter</filter-class>
- <init-param>
- <param-name>encoding</param-name>
- <param-value>UTF-8</param-value>
- </init-param>
- <init-param>
- <param-name>ignore</param-name>
- <param-value>true</param-value>
- </init-param>
- </filter>
- <filter-mapping>
- <filter-name>Set Character Encoding</filter-name>
- <url-pattern>/*</url-pattern>
- </filter-mapping>
- </web-app>
將以上程序部署到Tomcat8下,啓動服務。