使用WebSocket構建實時Web應用

使用WebSocket構建實時Web應用

隨着時代的進步,傳統的網頁技術已經無法滿足人民羣衆日益增長的物質文化需求(誤)。對於一些特定的需求,如消息推送、在線聊天等,往往受限於BS架構的特性而沒有完美的解決方案。好在現今HTML5標準已日漸成熟,現代瀏覽器大多也實現了對WebSocket 的支持。有了WebSocket,我們就可以構建真正意義上的Web App,實現客戶端到服務端的實時通信。

什麼是WebSocket?

WebSocket是一套基於TCP的協議,可用於在單個TCP連接上進行全雙工通信。雖然設計的時候是爲了在瀏覽器和服務端之間通信,但也可以單獨拿出來在任意的客戶端和服務端之間通信。WebSocket通過在Http頭上加入Upgrade:websocket來進行握手,但之後就和Http沒有任何關係了。

WebSocket握手——客戶端請求:

GET /chat HTTP/1.1

Host: server.example.com

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key:x3JJHMbDL1EzLkh9GBhXDw==

Sec-WebSocket-Protocol:chat, superchat

Sec-WebSocket-Version: 13

Origin: http://example.com

 

WebSocket握手——服務端響應:

HTTP/1.1 101 SwitchingProtocols

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Accept:HSmrc0sMlYUkAGmm5OPpG2HaGWk=

Sec-WebSocket-Protocol: chat

注:該例子摘自Wiki—— http://en.wikipedia.org/wiki/WebSocket

什麼時候用WebSocket

並非所有的場景都適合用WebSocket來解決問題。對大多數客戶端向服務端請求內容的需求來說,使用Http的Request-Response方式仍是最好的解決方案。

真正需要WebSocket的主要是一些是實時性要求較高的場景:如消息通知推送,即時通信等。在沒有WebSocket的時代,這類需求往往是通過輪詢或長連接來實現的。輪詢的問題是頻繁的請求會無意義地消耗大量網絡帶寬,而輪詢週期過長又會影響實時性。長連接則充斥了黑科技的味道,違背了HTTP協議的設計初衷,而且會佔用大量的服務器資源。有了WebSocket之後,這類問題終於迎刃而解。

NodeJS方案

雖然很多平臺都對WebSocket提供了支持,但是對於需要同時維持大量客戶端連接的場景來說,基於單進程異步調用的NodeJS是一個非常合適的解決方案。在服務器安裝了NodeJS環境以後,只需要在項目中執行npm install nodejs-websocket,就可以實現對該協議的支持。

首先,讓我們來看一個簡單的例子:

服務端代碼示例(摘自https://www.npmjs.com/package/nodejs-websocket):

var ws =require("nodejs-websocket");

var server =ws.createServer(function (conn) {

    console.log("New connection");

    conn.on("text", function (str) {

        console.log("Received " + str);

        conn.sendText(str.toUpperCase() + "!!!");

    })

    conn.on("close", function (code,reason) {

        console.log("Connectionclosed");

    })

}).listen(8001);

客戶端(網頁)代碼示例

<html>

<header>

</header>

<body>

<scripttype="text/javascript">

var wsServer ='ws://localhost:8001';

var webSocket = newWebSocket(wsServer);

webSocket.onopen = function(evt) { onOpen(evt) };

webSocket.onclose = function(evt) { onClose(evt) };

webSocket.onmessage =function (evt) { onMessage(evt) };

webSocket.onerror = function(evt) { onError(evt) };

function onOpen(evt) {

    console.log("Connected to WebSocketserver.");

   webSocket.send("hello");

}

function onClose(evt) {

    console.log("Disconnected");

}

function onMessage(evt) {

    console.log('Retrieved data from server: '+ evt.data);

}

function onError(evt) {

    console.log('Error occured: ' + evt.data);

}

</script>

</body>

</html>

該例子的執行邏輯如下:

客戶端頁面加載之後,便會同服務端建立WebSocket連接=》服務端收到連接後,便會觸發createServer的回調=》客戶端連接建立成功後,觸發onOpen事件,通過webSocket.send()向服務端發送文本=》服務端收到文本觸發conn.on(“text”)事件,並使用conn.sendText向客戶端推送文本=》客戶端收到該文本,觸發onMessage事件。

從本例可以看出,通過WebSocket,客戶端和服務端可以輕易地實現雙向實時通信。

注:服務端可以通過conn.path對url進行檢查,從而複用同一個端口實現多個接口。

一個簡單的通信框架

由於WebSocket提供的僅是最底層網絡通信的支持,直接在其之上編寫實際應用絕對不是一個好主意。以下的設計對其進行了簡單的封裝,從而將WebSocket的底層實現同業務邏輯進行分離,並實現了簡單的用戶及連接管理。

協議設計

使用JSON作爲消息格式。

服務端發往客戶端的消息:

{

    action: “alert”,

    message: “hello”

}

其中action是必選項,表明操作的類型,由客戶端註冊的相應的消息處理器來處理。

客戶端發往服務端的消息:

{

    action: “register”,

    user_id: 123456

}

其中action和user_id是必選項。action表明操作的類型,由服務端註冊的相應的消息處理器來處理。user_id爲識別用戶的唯一id,用於判斷消息來源。

客戶端在建立WebSocket連接後,需要向服務端發送register消息,並附上自己的userId。

服務端設計

模塊

ServiceModule

描述

封裝WebSocket底層邏輯

方法

startService

封裝NodeJS-WebSocket中的ws.createServer函數,用於啓動WebSocket監聽

sendMessage

通過指定WebSocket連接發送消息到客戶端

registerHandler

註冊消息處理器,格式爲handler(conn, request)

 

模塊

UserManager

描述

維護用戶/WebSocket連接列表

方法

getUsers

獲取當前活躍用戶

sendMessageToUser

向指定用戶發送消息

 

 

 

初始化時,UserManager會在ServiceModule中註冊action爲register的消息處理器。一旦有新用戶註冊,便會觸發該處理器,從而將該用戶及其對應的WebSocket連接加入列表維護。需要向某個用戶發送消息時,則通過該列表查找到對應的WebSocket連接,通過ServiceModule向客戶端發送消息。

注1:如果只是用於實現消息推送,服務端僅處理register消息便足夠了。若需要實現更復雜的邏輯,可以考慮添加一個用戶緯度的ServiceModule,封裝原ServiceModule和UserManager,支持形如handler(userId, request)的消息處理函數。

注2:如果不允許一個用戶建立多個連接,可以在register消息處理器中斷開之前的連接。

客戶端設計

模塊

WSAgent

描述

封裝WebSocket底層邏輯

方法

sendMessage

向服務端發送消息

registerHandler

註冊消息處理器,格式爲handler(request)

 

 

 

客戶端無需維護用戶列表,因此只需單個模塊即可提供所需的封裝。WSAgent初始化時需要輸入服務端url和userId作爲參數,從而同服務端建立WebSocket連接,併發送register消息。收到服務端消息後,則調用相應的消息處理器對其進行處理。

可以改進的點

上述框架僅提供了一個初級的雙向通信解決方案,無法得知消息發送是成功還是失敗。可以根據具體需求進行進一步優化。

對於推送服務來說,只需在此基礎上添加推送相關的消息即可。如果需要推送到達通知,可以爲推送消息指定唯一id。客戶端收到推送消息後,向服務端發送帶有該id的回執。服務端即可更新消息推送狀態。

對於即時通信來說則更爲複雜一些,需要分別在客戶端和服務端跟蹤消息的發送、接收情況。

另外就是需要獲取服務端信息的場景。一種做法是直接在WebSocket上實現類似傳統Http協議的Request-Response機制;還有一種做法對於這種情況通通走Http。前者可自定義協議,且無需重新建立連接,性能更好;後者則更爲通用,實現起來較簡單。

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