WebSocket

HTTP 協議有一個缺陷:通訊只能由客戶端發起。
HTTP 協議作不到服務器主動向客戶端推送信息。(這種單向請求的特色,註定了若是服務器有連續的狀態變化,客戶端要獲知就很是麻煩。咱們只能使用「輪詢」:每隔一段時候,就發出一個詢問,瞭解服務器有沒有新的信息。最典型的場景就是聊天室)。javascript

WebSocket 協議的最大特色就是,服務器能夠主動向客戶端推送信息,客戶端也能夠主動向服務器發送信息,是真正的雙向平等對話,屬於服務器推送技術的一種。html

容許服務器端與客戶端進行全雙工(full-duplex)的通訊。舉例來講,HTTP 協議有點像發電子郵件,發出後必須等待對方回信;WebSocket 則是像打電話,服務器端和客戶端能夠同時向對方發送數據,它們之間存着一條持續打開的數據通道。java

特色包括:
(1)創建在 TCP 協議之上,服務器端的實現比較容易。
(2)與 HTTP 協議有着良好的兼容性。默認端口也是80和443,而且握手階段採用 HTTP 協議,所以握手時不容 易屏蔽,能經過各類 HTTP 代理服務器。
(3)數據格式比較輕量,性能開銷小,通訊高效。
(4)能夠發送文本,也能夠發送二進制數據。
(5)沒有同源限制,客戶端能夠與任意服務器通訊,徹底能夠取代 Ajax。
(6)協議標識符是ws(若是加密,則爲wss,對應 HTTPS 協議),服務器網址就是 URL。web

WebSocket 握手

瀏覽器發出的 WebSocket 握手請求:canvas

GET / HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Host: example.com
Origin: null
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

Upgrade表示將通訊協議從HTTP/1.1轉向該字段指定的協議
Connection字段表示瀏覽器通知服務器,若是能夠的話,就升級到 WebSocket 協議後端

Origin字段用於提供請求發出的域名,供服務器驗證是否許可的範圍內(服務器也能夠不驗證)api

Sec-WebSocket-Key則是用於握手協議的密鑰,是 Base64 編碼的16字節隨機字符串瀏覽器

服務器的 WebSocket 迴應:安全

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Origin: null
Sec-WebSocket-Location: ws://example.com/

Connection字段通知瀏覽器,須要改變協議服務器

Sec-WebSocket-Accept字段是服務器在瀏覽器提供的Sec-WebSocket-Key字符串後面,添加「258EAFA5-E914-47DA-95CA-C5AB0DC85B11」字符串,而後再取 SHA-1 的哈希值。瀏覽器將對這個值進行驗證,以證實確實是目標服務器迴應了 WebSocket 請求

Sec-WebSocket-Location字段表示進行通訊的 WebSocket 網址

完成握手之後,WebSocket 協議就在 TCP 協議之上,開始傳送數據。

客戶端的簡單示例

var ws = new WebSocket('wss://echo.websocket.org');

ws.onopen = function(evt) {
  console.log('Connection open ...');
  ws.send('Hello WebSockets!');
};

ws.onmessage = function(evt) {
  console.log('Received Message: ' + evt.data);
  ws.close();
};

ws.onclose = function(evt) {
  console.log('Connection closed.');
};

客戶端 API

創建鏈接和斷開鏈接
發送數據和接收數據
處理錯誤

WebSocket對象做爲一個構造函數,用於新建WebSocket實例
var ws = new WebSocket('ws://localhost:8080');

webSocket.readyState

webSocket.readyState屬性返回實例對象的當前狀態,共有四種

CONNECTING:值爲0,表示正在鏈接。
OPEN:值爲1,表示鏈接成功,能夠通訊了。
CLOSING:值爲2,表示鏈接正在關閉。
CLOSED:值爲3,表示鏈接已經關閉,或者打開鏈接失敗。

例子:

switch (ws.readyState) {
  case WebSocket.CONNECTING:
    // do something
    break;
  case WebSocket.OPEN:
    // do something
    break;
  case WebSocket.CLOSING:
    // do something
    break;
  case WebSocket.CLOSED:
    // do something
    break;
  default:
    // this never happens
    break;
}

webSocket.onopen

實例對象的onopen屬性,用於指定鏈接成功後的回調函數。

ws.onopen = function () {
  ws.send('Hello Server!');
}

若是要指定多個回調函數,可使用addEventListener方法。

ws.addEventListener('open', function (event) {
  ws.send('Hello Server!');
});

webSocket.onclose

實例對象的onclose屬性,用於指定鏈接關閉後的回調函數。

ws.onclose = function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
};

ws.addEventListener("close", function(event) {
  var code = event.code;
  var reason = event.reason;
  var wasClean = event.wasClean;
  // handle close event
});

webSocket.onmessage

實例對象的onmessage屬性,用於指定收到服務器數據後的回調函數。

ws.onmessage = function(event) {
  var data = event.data;
  // 處理數據
};

ws.addEventListener("message", function(event) {
  var data = event.data;
  // 處理數據
});

服務器數據多是文本,也多是二進制數據(blob對象或Arraybuffer對象)

ws.onmessage = function(event){
  if(typeOf event.data === String) {
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}

可使用binaryType屬性,顯式指定收到的二進制數據類型

/ 收到的是 blob 數據
ws.binaryType = "blob";
ws.onmessage = function(e) {
  console.log(e.data.size);
};

// 收到的是 ArrayBuffer 數據
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
  console.log(e.data.byteLength);
};

webSocket.send()

實例對象的send()方法用於向服務器發送數據

ws.send('your message');

// Blob對象表示一個不可變的, 原始數據的相似文件對象。Blob表示的數據不必定是一個JavaScript原生格式 blob對象本質上是js中的一個對象,裏面能夠儲存大量的二進制編碼格式的數據。

發送 Blob 對象的例子。

var file = document
  .querySelector('input[type="file"]')
  .files[0];
ws.send(file);

發送 ArrayBuffer 對象的例子。

// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
ws.send(binary.buffer);

webSocket.bufferedAmount

實例對象的bufferedAmount屬性,表示還有多少字節的二進制數據沒有發送出去。它能夠用來判斷髮送是否結束

var data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
  // 發送完畢
} else {
  // 發送還沒結束
}

webSocket.onerror

實例對象的onerror屬性,用於指定報錯時的回調函數

socket.onerror = function(event) {
  // handle error event
};

socket.addEventListener("error", function(event) {
  // handle error event
});

經過javaweb實現聊天室功能:

WebSocketTest.java:

package me.gacl.websocket;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

/**
 * @ServerEndpoint 註解是一個類層次的註解,它的功能主要是將目前的類定義成一個websocket服務器端,
 * 註解的值將被用於監聽用戶鏈接的終端訪問URL地址,客戶端能夠經過這個URL來鏈接到WebSocket服務器端
 */
@ServerEndpoint("/websocket")
public class WebSocketTest {
    //靜態變量,用來記錄當前在線鏈接數。應該把它設計成線程安全的。
    private static int onlineCount = 0;

    //concurrent包的線程安全Set,用來存放每一個客戶端對應的MyWebSocket對象。若要實現服務端與單一客戶端通訊的話,可使用Map來存放,其中Key能夠爲用戶標識
    private static CopyOnWriteArraySet<WebSocketTest> webSocketSet = new CopyOnWriteArraySet<WebSocketTest>();

    //與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據
    private Session session;

    /**
     * 鏈接創建成功調用的方法
     * @param session  可選的參數。session爲與某個客戶端的鏈接會話,須要經過它來給客戶端發送數據
     */
    @OnOpen
    public void onOpen(Session session){
        this.session = session;
        webSocketSet.add(this);     //加入set中
        addOnlineCount();           //在線數加1
        System.out.println("有新鏈接加入!當前在線人數爲" + getOnlineCount());
    }

    /**
     * 鏈接關閉調用的方法
     */
    @OnClose
    public void onClose(){
        webSocketSet.remove(this);  //從set中刪除
        subOnlineCount();           //在線數減1
        System.out.println("有一鏈接關閉!當前在線人數爲" + getOnlineCount());
    }

    /**
     * 收到客戶端消息後調用的方法
     * @param message 客戶端發送過來的消息
     * @param session 可選的參數
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("來自客戶端的消息:" + message);
        //羣發消息
        for(WebSocketTest item: webSocketSet){
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
                continue;
            }
        }
    }

    /**
     * 發生錯誤時調用
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error){
        System.out.println("發生錯誤");
        error.printStackTrace();
    }

    /**
     * 這個方法與上面幾個方法不同。沒有用註解,是根據本身須要添加的方法。
     * @param message
     * @throws IOException
     */
    public void sendMessage(String message) throws IOException{
        this.session.getBasicRemote().sendText(message);
        //this.session.getAsyncRemote().sendText(message);
    }

    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    public static synchronized void addOnlineCount() {
        WebSocketTest.onlineCount++;
    }

    public static synchronized void subOnlineCount() {
        WebSocketTest.onlineCount--;
    }
}

pom.xml:

<dependencies>
      <dependency>
        <groupId>javax</groupId>
        <artifactId>javaee-api</artifactId>
        <version>7.0</version>
        <scope>provided</scope>
      </dependency>
  </dependencies>

index.jsp:

<%@ page language="java" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <title>Java後端WebSocket的Tomcat實現</title>
</head>
<body>
    Welcome<br/><input id="text" type="text"/>
    <button onclick="send()">發送消息</button>
    <hr/>
    <button onclick="closeWebSocket()">關閉WebSocket鏈接</button>
    <hr/>
    <div id="message"></div>
</body>

<script type="text/javascript">
    var websocket = null;
    //判斷當前瀏覽器是否支持WebSocket
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://localhost:8080/websocket");
    }
    else {
        alert('當前瀏覽器 Not support websocket')
    }

    //鏈接發生錯誤的回調方法
    websocket.onerror = function () {
        setMessageInnerHTML("WebSocket鏈接發生錯誤");
    };

    //鏈接成功創建的回調方法
    websocket.onopen = function () {
        setMessageInnerHTML("WebSocket鏈接成功");
    }

    //接收到消息的回調方法
    websocket.onmessage = function (event) {
        setMessageInnerHTML(event.data);
    }

    //鏈接關閉的回調方法
    websocket.onclose = function () {
        setMessageInnerHTML("WebSocket鏈接關閉");
    }

    //監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket鏈接,防止鏈接還沒斷開就關閉窗口,server端會拋異常。
    window.onbeforeunload = function () {
        closeWebSocket();
    }

    //將消息顯示在網頁上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    //關閉WebSocket鏈接
    function closeWebSocket() {
        websocket.close();
    }

    //發送消息
    function send() {
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>
相關文章
相關標籤/搜索