java websocket原理及其簡易實現

java websocket原理及其簡易實現


     WebSocket協議是基於TCP的一種新的網絡協議。它實現了瀏覽器與服務器全雙工(full-duplex)通信——允許服務器主動發送信息給客戶端。
    WebSocket協議支持(在受控環境中運行不受信任的代碼的)客戶端與(選擇加入該代碼的通信的)遠程主機之間進行全雙工通信。用於此的安全模型是Web瀏覽器常用的基於原始的安全模式。 協議包括一個開放的握手以及隨後的TCP層上的消息幀。 該技術的目標是爲基於瀏覽器的、需要和服務器進行雙向通信的(服務器不能依賴於打開多個HTTP連接(例如,使用XMLHttpRequest或<iframe>和長輪詢))應用程序提供一種通信機制。
    長久以來, 創建實現客戶端和用戶端之間雙工通訊的web app都會造成HTTP輪詢的濫用: 客戶端向主機不斷髮送不同的HTTP呼叫來進行詢問。
這會導致一系列的問題:
1.服務器被迫爲每個客戶端使用許多不同的底層TCP連接:一個用於向客戶端發送信息,其它用於接收每個傳入消息。
2.有線協議有很高的開銷,每一個客戶端和服務器之間都有HTTP頭。
3.客戶端腳本被迫維護從傳出連接到傳入連接的映射來追蹤回覆。
一個更簡單的解決方案是使用單個TCP連接雙向通信。 這就是WebSocket協議所提供的功能。 結合WebSocket API ,WebSocket協議提供了一個用來替代HTTP輪詢實現網頁到遠程主機的雙向通信的方法。
WebSocket協議被設計來取代用HTTP作爲傳輸層的雙向通訊技術,這些技術只能犧牲效率和可依賴性其中一方來提高另一方,因爲HTTP最初的目的不是爲了雙向通訊。
  在HTML5中內置有一些API,用於響應應用程序發起的請求。基本API語句如下:
創建對象
var ws = new WebSocket(url,name);
url爲WebSocket服務器的地址,name爲發起握手的協議名稱,爲可選擇項。
發送文本消息
ws.send(msg);
msg爲文本消息,對於其他類型的可以通過二進制形式發送。
接收消息
ws.onmessage = (function(){...})();
錯誤處理
ws.onerror = (function(){...})();
關閉連接
ws.close();

簡易實現:客戶端:

<%@ page language="java" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<title>Java後端WebSocket的Tomcat實現</title>
</head>
<body>
    WebSocket的Tomcat實現
    <br />
    <input id="text" type="text" />
    <button onclick="send()">發送消息</button><br />
    <button onclick="link()">建立連接</button><br />
    <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/WebSoket/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.onopen = function () { 

            setMessageInnerHTML("WebSocket建立連接");  
        } 

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

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

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

        //建立WebSocket連接  
        function link() {  
            websocket.onopen();
            websocket = new WebSocket("ws://localhost:8080/WebSoket/websocket"); 
        }  
        //發送消息  
        function send() {  
            var message = document.getElementById('text').value;  
            websocket.send(message);  
        }  
    </script>
</html>

簡易實現:服務器端

package com.soket;

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

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

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

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

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

    //用戶名
    private final static String GUIST_NAME = "遊客";

    //用戶編號
    private static final AtomicInteger connectionIds = new AtomicInteger(0); 

    //用戶暱稱
    private final String nickname;

    /**
     * 連接建立成功調用的方法
     * 
     * @param session
     *            可選的參數。session爲與某個客戶端的連接會話,需要通過它來給客戶端發送數據
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;     
        webSocketSet.add(this); // 加入set中
        addOnlineCount(); // 在線數加1
        String message = String.format("* %s %s", nickname, "已連接");
        try {
            sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("有新連接加入!當前在線人數爲" + getOnlineCount());
    }

      public WebSoketEndpoint() {  
            nickname = GUIST_NAME + connectionIds.getAndIncrement();  
        }  


    /**
     * 連接關閉調用的方法
     */
    @OnClose
    public void onClose() {
        String message = String.format("* %s %s", nickname, "已關閉連接");
        try {
            sendMessage(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
        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 (WebSoketEndpoint item : webSocketSet) {
            try {
                item.sendMessage(nickname+":"+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() {
        WebSoketEndpoint.onlineCount++;
    }

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

代碼運行結果(基於tomcat8):

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章