1. 程式人生 > >HTML5 WebSocket簡介

HTML5 WebSocket簡介

前言

作為下一代的 Web標準,HTML5 擁有許多引人注目的新特性,如 Canvas、本地儲存、多媒體程式設計介面、WebSocket等等。這其中有“Web TCP ”之稱的 WebSocket格外吸引開發人員的注意。WebSocket的出現使得瀏覽器提供對 Socket的支援成為可能,從而在瀏覽器和伺服器之間提供了一個基於 TCP連線的雙向通道。WebSocket提供了一個受歡迎的技術,以替代我們過去幾年一直在用的Ajax技術。Web開發人員可以非常方便地使用 WebSocket構建實時 web 應用,開發人員的手中從此又多了一柄神兵利器。本文介紹了 HTML5 WebSocket

的由來,運作機制及客戶端和服務端的 API實現,重點介紹服務端(基於 Tomcat7)及客戶端(基於瀏覽器原生 HTML5 API)實現的詳細步驟;並通過實際客戶案例描述了客戶端如何在 WebSocket 架構下使用 HTTP長連線與伺服器實現實時通訊及訊息推送的功能,讀者通過閱讀本文中案例示例程式碼的實現,能夠更深刻理解 WebSocket框架的技術原理和開發方法。

實時 Web 應用的窘境

Web 應用的資訊互動過程通常是客戶端通過瀏覽器發出一個請求,伺服器端接收和稽核完請求後進行處理並返回結果給客戶端,然後客戶端瀏覽器將資訊呈現出來,這種機制對於資訊變化不是特別頻繁的應用尚能相安無事,但是對於那些實時要求比較高的應用來說,比如說線上遊戲、線上證券、裝置監控、新聞線上播報、

RSS訂閱推送等等,當客戶端瀏覽器準備呈現這些資訊的時候,這些資訊在伺服器端可能已經過時了。所以保持客戶端和伺服器端的資訊同步是實時 Web應用的關鍵要素,對 Web開發人員來說也是一個難題。在 WebSocket規範出來之前,開發人員想實現這些實時的 Web應用,不得不採用一些折衷的方案,其中最常用的就是輪詢 (Polling) Comet 技術,而 Comet 技術實際上是輪詢技術的改進,又可細分為兩種實現方式,一種是長輪詢機制,一種稱為流技術。下面我們簡單介紹一下這幾種技術:

輪詢:

這是最早的一種實現實時 Web應用的方案。客戶端以一定的時間間隔向服務端發出請求,以頻繁請求的方式來保持客戶端和伺服器端的同步。這種同步方案的最大問題是,當客戶端以固定頻率向伺服器發起請求的時候,伺服器端的資料可能並沒有更新,這樣會帶來很多無謂的網路傳輸,所以這是一種非常低效的實時方案。

長輪詢:

長輪詢是對定時輪詢的改進和提高,目地是為了降低無效的網路傳輸。當伺服器端沒有資料更新的時候,連線會保持一段時間週期直到資料或狀態改變或者時間過期,通過這種機制來減少無效的客戶端和伺服器間的互動。當然,如果服務端的資料變更非常頻繁的話,這種機制和定時輪詢比較起來沒有本質上的效能的提高。

流:

流技術方案通常就是在客戶端的頁面使用一個隱藏的視窗向服務端發出一個長連線的請求。伺服器端接到這個請求後作出迴應並不斷更新連線狀態以保證客戶端和伺服器端的連線不過期。通過這種機制可以將伺服器端的資訊源源不斷地推向客戶端。這種機制在使用者體驗上有一點問題,需要針對不同的瀏覽器設計不同的方案來改進使用者體驗,同時這種機制在併發比較大的情況下,對伺服器端的資源是一個極大的考驗。

綜合這幾種方案,您會發現這些目前我們所使用的所謂的實時技術並不是真正的實時技術,它們只是在用 Ajax方式來模擬實時的效果,在每次客戶端和伺服器端互動的時候都是一次 HTTP的請求和應答的過程,而每一次的 HTTP請求和應答都帶有完整的 HTTP頭資訊,這就增加了每次傳輸的資料量,而且這些方案中客戶端和伺服器端的程式設計實現都比較複雜,在實際的應用中,為了模擬比較真實的實時效果,開發人員往往需要構造兩個 HTTP連線來模擬客戶端和伺服器之間的雙向通訊,一個連線用來處理客戶端到伺服器端的資料傳輸,一個連線用來處理伺服器端到客戶端的資料傳輸,這不可避免地增加了程式設計實現的複雜度,也增加了伺服器端的負載,制約了應用系統的擴充套件性。

WebSocket 的拯救

HTML5 WebSocket設計出來的目的就是要取代輪詢和 Comet技術,使客戶端瀏覽器具備像 C/S架構下桌面系統的實時通訊能力。瀏覽器通過 JavaScript向伺服器發出建立 WebSocket連線的請求,連線建立以後,客戶端和伺服器端就可以通過 TCP連線直接交換資料。因為 WebSocket連線本質上就是一個 TCP連線,所以在資料傳輸的穩定性和資料傳輸量的大小方面,和輪詢以及 Comet技術比較,具有很大的效能優勢。

WebSocket 規範

WebSocket 協議本質上是一個基於 TCP的協議。為了建立一個 WebSocket連線,客戶端瀏覽器首先要向伺服器發起一個 HTTP請求,這個請求和通常的 HTTP請求不同,包含了一些附加頭資訊,其中附加頭資訊”Upgrade: WebSocket”表明這是一個申請協議升級的 HTTP請求,伺服器端解析這些附加的頭資訊然後產生應答資訊返回給客戶端,客戶端和伺服器端的 WebSocket連線就建立起來了,雙方就可以通過這個連線通道自由的傳遞資訊,並且這個連線會持續存在直到客戶端或者伺服器端的某一方主動的關閉連線。下面是一個簡單的建立握手的時序圖:

這裡簡單說明一下WebSocket握手的過程。

Web應用程式呼叫new WebSocket(url)介面時,Browser就開始了與地址為urlWebServer建立握手連線的過程。

1)       BrowserWebSocket伺服器通過TCP三次握手建立連線,如果這個建立連線失敗,那麼後面的過程就不會執行,Web應用程式將收到錯誤訊息通知。

2)       TCP建立連線成功後,Browser/UA通過http協議傳送WebSocket支援的版本號,協議的字版本號,原始地址,主機地址等等一些列欄位給伺服器端。這很是有些類似於http的頭資訊,同樣每行都是以”\r\n”結尾的,這段格式無需我們去構造,WebSocket物件會自動傳送,對客戶端這是透明的。

例如:

GET /chatHTTP/1.1

Host:server.example.com

Upgrade:websocket

Connection:Upgrade

Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==

Origin:http://example.com

Sec-WebSocket-Protocol:chat,superchat

Sec-WebSocket-Version:13

3)       WebSocket伺服器收到Browser/UA傳送來的握手請求後,如果資料包資料和格式正確,客戶端和伺服器端的協議版本號匹配等等,就接受本次握手連線,並給出相應的資料回覆,同樣回覆的資料包也是採用http協議傳輸。從這裡我們太容易看出來,websocket協議的握手部分根本就是個類http的協議,所不同的是http每次都會有這樣子的頭資訊互動,這在某些時候不得不顯得很糟糕。而websocket只會執行一次這個過程,之後的傳輸資訊就變得異常簡潔了。

HTTP/1.1 101Switching Protocols

Upgrade:websocket

Connection:Upgrade

Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Sec-WebSocket-Protocol:chat

客戶端會發送一個“Sec-WebSocket-Key”的base64編碼的金鑰,要求服務端必須返回一個“Sec-WebSocket-Accept”,否則客戶端會丟擲一個“Error during WebSocket handshake: Sec-WebSocket-Acceptmismatch”錯誤之後,關閉連線,當然,這個Sec-WebSocket-Accept的值是計算出來的,胡亂的返回也是要遭到歷史唾棄的。Sec-WebSocket-Accept的演算法很簡單:將客戶端提交的Sec-WebSocket-Key+”258EAFA5-E914-47DA-95CA-C5AB0DC85B11″,然後sha1,然後base64_encode,以下列出我用nodejs寫的程式碼片段:

sha1 = crypto.createHash('sha1');

sha1.update(headers["Sec-WebSocket-Key"]+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");

ws_accept=sha1.digest('base64');

4)       Browser收到伺服器回覆的資料包後,如果資料包內容、格式都沒有問題的話,就表示本次連線成功,觸發onopen訊息,此時Web開發者就可以在此時通過send介面想伺服器傳送資料。否則,握手連線失敗,Web應用程式會收到onerror訊息,並且能知道連線失敗的原因。客戶端的WebSocket物件一共綁定了四個事件:1、onopen:連線建立時觸發;2、onmessage:收到服務端訊息時觸發;3、onerror:連接出錯時觸發;4、onclose:連線關閉時觸發;有了這4個事件,我們就可以很容易很輕鬆的駕馭websocket,並且需要說明的是websocket支援二進位制資料的傳輸.

websocket與TCP,HTTP的關係

 WebSockethttp協議一樣都是基於TCP的,所以他們都是可靠的協議,Web開發者呼叫的WebSocketsend函式在browser的實現中最終都是通過TCP的系統介面進行傳輸的。WebSocketHttp協議一樣都屬於應用層的協議,那麼他們之間有沒有什麼關係呢?答案是肯定的,WebSocket在建立握手連線時,資料是通過http協議傳輸的,正如我們上一節所看到的“GET/chatHTTP/1.1”,這裡面用到的只是http協議一些簡單的欄位。但是在建立連線之後,真正的資料傳輸階段是不需要http協議參與的。具體關係可以參考下圖:

WebSocket 實現

如上文所述,WebSocket的實現分為客戶端和服務端兩部分,客戶端(通常為瀏覽器)發出 WebSocket連線請求,服務端響應,實現類似 TCP握手的動作,從而在瀏覽器客戶端和 WebSocket服務端之間形成一條 HTTP長連線快速通道。兩者之間後續進行直接的資料互相傳送,不再需要發起連線和相應。

以下簡要描述 WebSocket服務端 API 及客戶端 API

WebSocket 服務端 API

WebSocket 服務端在各個主流應用伺服器廠商中已基本獲得符合 JEE JSR356標準規範 API 的支援(詳見),以下列舉了部分常見的商用及開源應用伺服器對 WebSocket Server端的支援情況:

表 1.WebSocket 服務端支援

廠商

應用伺服器

備註

IBM

WebSphere

WebSphere 8.0 以上版本支援,7.X之前版本結合 MQTT支援類似的 HTTP 長連線

甲骨文

WebLogic

WebLogic 12c 支援,11g 10g 版本通過 HTTP Publish 支援類似的 HTTP 長連線

微軟

IIS

IIS 7.0+支援

Apache

Tomcat

Tomcat 7.0.5+支援,7.0.2X 7.0.3X 通過自定義 API 支援

Jetty

Jetty 7.0+支援

以下我們使用 Tomcat7.0.5版本的服務端示例程式碼說明 WebSocket服務端的實現:

JSR356 WebSocket規範使用javax.websocket.* API,可以將一個普通 Java物件(POJO)使用 @ServerEndpoint註釋作為 WebSocket伺服器的端點,程式碼示例如下:

清單 3.WebSocket 服務端 API 示例

 @ServerEndpoint("/echo")
 public class EchoEndpoint {
 @OnOpen
 public void onOpen(Session session) throws IOException {
 //以下程式碼省略...
 }
 @OnMessage
 public String onMessage(String message) {
 //以下程式碼省略...
 }
 @Message(maxMessageSize=6)
 public void receiveMessage(String s) {
 //以下程式碼省略...
 } 
 @OnError
 public void onError(Throwable t) {
 //以下程式碼省略...
 }
 @OnClose
 public void onClose(Session session, CloseReason reason) {
 //以下程式碼省略...
 } 
 }

程式碼解釋:

上文的簡潔程式碼即建立了一個 WebSocket的服務端,@ServerEndpoint("/echo") annotation註釋端點表示將 WebSocket服務端執行在 ws://[Server IP 或域名]:[Server ]/websockets/echo的訪問端點,客戶端瀏覽器已經可以對 WebSocket客戶端 API 發起 HTTP 長連線了。

使用 ServerEndpoint註釋的類必須有一個公共的無引數建構函式,@onMessage註解的 Java 方法用於接收傳入的 WebSocket資訊,這個資訊可以是文字格式,也可以是二進位制格式。

OnOpen 在這個端點一個新的連線建立時被呼叫。引數提供了連線的另一端的更多細節。Session表明兩個 WebSocket端點對話連線的另一端,可以理解為類似 HTTPSession的概念。

OnClose 在連線被終止時呼叫。引數 closeReason可封裝更多細節,如為什麼一個 WebSocket連線關閉。

更高階的定製如 @Message註釋,MaxMessageSize屬性可以被用來定義訊息位元組最大限制,在示例程式中,如果超過 6個位元組的資訊被接收,就報告錯誤和連線關閉。

注意:早期不同應用伺服器支援的 WebSocket方式不盡相同,即使同一廠商,不同版本也有細微差別,如 Tomcat伺服器 7.0.5 以上的版本都是標準 JSR356 規範實現,而 7.0.2x/7.0.3X的版本使用自定義 APIWebSocketServlet StreamInbound前者是一個容器,用來初始化 WebSocket環境;後者是用來具體處理 WebSocket請求和響應,詳見案例分析部分),且 Tomcat7.0.3x 7.0.2x createWebSocketInbound方法的定義不同,增加了一個HttpServletRequest引數,使得可以從 request引數中獲取更多 WebSocket客戶端的資訊,如下程式碼所示:

清單 4.Tomcat7.0.3X 版本 WebSocket API

public class EchoServlet extends WebSocketServlet {
@Override
protected StreamInbound createWebSocketInbound(String subProtocol,
HttpServletRequest request) {
 //以下程式碼省略....
return new MessageInbound() {
 //以下程式碼省略....
}
protected void onBinaryMessage(ByteBuffer buffer)
throws IOException {
 //以下程式碼省略...
}
protected void onTextMessage(CharBuffer buffer) throws IOException {
 getWsOutbound().writeTextMessage(buffer);
 //以下程式碼省略...
}
};
}
}

因此選擇 WebSocket Server 端重點需要選擇其版本,通常情況下,更新的版本對 WebSocket的支援是標準 JSR 規範 API,但也要考慮開發易用性及老版本程式移植性等方面的問題,如下文所述的客戶案例,就是因為客戶要求統一應用伺服器版本所以使用的 Tomcat 7.0.3X版本的 WebSocketServlet實現,而不是 JSR356 @ServerEndpoint註釋端點。

WebSocket 客戶端 API

對於 WebSocket 客戶端,主流的瀏覽器(包括 PC 和移動終端)現已都支援標準的 HTML5 WebSocket API,這意味著客戶端的 WebSocket JavaScirpt指令碼具備良好的一致性和跨平臺特性,以下列舉了常見的瀏覽器廠商對 WebSocket的支援情況:

表 2.WebSocket 客戶端支援

瀏覽器

支援情況

Chrome

Chrome version 4+支援

Firefox

Firefox version 5+支援

IE

IE version 10+支援

Safari

IOS 5+支援

Android Brower

Android 4.5+支援

客戶端 WebSocket API基本上已經在各個主流瀏覽器廠商中實現了統一,因此使用標準 HTML5定義的 WebSocket客戶端的 JavaScript API即可,當然也可以使用業界滿足 WebSocket標準規範的開源框架,如 Socket.io

以下以一段程式碼示例說明 WebSocket的客戶端實現:

清單 5.WebSocket 客戶端 API 示例
var ws = new WebSocket(“ws://echo.websocket.org”); 
 ws.onopen = function(){ws.send(“Test!”); }; 
 ws.onmessage = function(evt){console.log(evt.data);ws.close();}; 
 ws.onclose = function(evt){console.log(“WebSocketClosed!”);}; 
 ws.onerror = function(evt){console.log(“WebSocketError!”);};

第一行程式碼是在申請一個 WebSocket物件,引數是需要連線的伺服器端的地址,同 HTTP協議開頭一樣,WebSocket協議的 URL 使用 ws://開頭,另外安全的 WebSocket協議使用 wss://開頭。

第二行到第五行為 WebSocket物件註冊訊息的處理函式,WebSocket物件一共支援四個訊息 onopen,onmessage, onclose onerror,有了這 4個事件,我們就可以很容易很輕鬆的駕馭 WebSocket

Browser WebSocketServer 連線成功後,會觸發 onopen 訊息;如果連線失敗,傳送、接收資料失敗或者處理資料出現錯誤,browser會觸發 onerror訊息;當 Browser接收到 WebSocketServer傳送過來的資料時,就會觸發 onmessage訊息,引數 evt 中包含 Server 傳輸過來的資料;當 Browser 接收到WebSocketServer端傳送的關閉連線請求時,就會觸發 onclose訊息。我們可以看出所有的操作都是採用非同步回撥的方式觸發,這樣不會阻塞 UI,可以獲得更快的響應時間,更好的使用者體驗。

WebSocket 案例分析

以下我們以一個真實的客戶案例來分析說明 WebSocket的優勢及具體開發實現(為保護客戶隱私,以下描述省去客戶名,具體涉及業務細節的程式碼在文中不再累述)。

案例介紹

該客戶為一個移動裝置製造商,移動裝置裝載的是 Android/IOS作業系統,裝置分兩類(以下簡稱 AB兩類),A 類裝置隨時處於移動狀態中,B 類裝置為 A 類裝置的管理控制裝置,客戶需要隨時在 B類裝置中看到所屬 A 類裝置的地理位置資訊及狀態資訊。如 A類裝置上線,離線的時候,B類裝置需要立即獲得訊息通知,A類裝置上報時,B 類裝置也需要實時獲得該上報 A 類裝置的地理位置資訊。

為降低跨平臺的難度及實施工作量,客戶考慮輕量級的 Web App的方式遮蔽 Android/IOS平臺的差異性,A 類裝置數量眾多,且在工作狀態下 A 類裝置處於不定時的移動狀態,而 B 類裝置對 A 類裝置狀態變化的感知實時性要求很高(秒級)。

根據以上需求,A/B 類裝置資訊存放在後臺數據庫中,A/B類裝置的互動涉及 Web客戶端/伺服器頻繁和高併發的請求-相應,如果使用傳統的 HTTP請求-響應模式,B類裝置的 Web App上需要對服務進行輪詢,勢必會對伺服器帶來大的負載壓力,且當 A類裝置沒有上線或者上報等活動事件時,B類裝置的輪詢嚴重浪費網路資源。

解決方案

綜上所述,專案採用 WebSocket技術實現實時訊息的通知及推送,每當 A類裝置/B 類裝置上線登入成功即開啟 WebSocket HTTP 長連線,新的 A 類裝置上線,位置變化,離線等狀態變化通過 WebSocket傳送實時訊息,WebSocket Server端處理 A 類裝置的實時訊息,並向所從屬的 B 類裝置實時推送。

WebSocket 客戶端使用 jQuery MobilejQuery Mobile移動端開發在本文中不再詳細描述,感興趣的讀者可以參考),使用原生 WebSocket API實現與服務端互動。

服務端沿用客戶已有的應用伺服器 Tomcat 7.0.33版本,使用 Apache自定義 API 實現 WebSocket Server端,為一個上線的 A 類裝置生成一個 WebSocket HTTP 長連線,每當 A類裝置有上線,位置更新,離線等事件的時候,客戶端傳送文字訊息,服務端識別並處理後,向所屬 B類裝置傳送實時訊息,B類裝置客戶端接收訊息後,識別到 A類裝置的相應事件,完成對應的 A類裝置位置重新整理以及其他業務操作。

其涉及的 A 類裝置,B類裝置及後臺伺服器互動時序圖如下:

圖 3:A/B 類裝置 WebSocket 互動圖

A/B 類裝置的 WebSocket客戶端封裝在 websocket.js JavaScript程式碼中,與 jQuery MobileApp一同打包為移動端 apk/ipa安裝包;WebSocket服務端實現主要為WebSocketDeviceServlet.java, WebSocketDeviceInbound.javaWebSocketDeviceInboundPool.java幾個類。下文我們一一介紹其具體程式碼實現。

程式碼實現

在下文中我們把本案例中的主要程式碼實現做解釋說明,讀者可以下載完整的程式碼清單做詳細瞭解。

WebSocketDeviceServlet

A 類裝置或者 B類裝置發起 WebSocket長連線後,服務端接受請求的是 WebSocketDeviceServlet類,跟傳統 HttpServlet不同的是,WebSocketDeviceServlet類實現 createWebSocketInbound方法,類似 SocketServer accept 方法,新生產的 WebSocketInbound例項對應客戶端 HTTP長連線,處理與客戶端互動功能。

WebSocketDeviceServlet 服務端程式碼示例如下:

清單 6.WebSocketDeviceServlet.java 程式碼示例

public class WebSocketDeviceServlet extends org.apache.catalina.websocket.WebSocketServlet {
private static final long serialVersionUID = 1L;
 @Override
 protected StreamInbound createWebSocketInbound(String subProtocol,HttpServletRequest request) {
 WebSocketDeviceInbound newClientConn = new WebSocketDeviceInbound(request);
 WebSocketDeviceInboundPool.addMessageInbound(newClientConn);
 return newClientConn;
 }
}

程式碼解釋:

WebSocketServlet WebSocket協議的後臺監聽程序,和傳統 HTTP請求一樣,WebSocketServlet類似 Spring/Struct中的 Servlet 監聽程序,只不過通過客戶端 ws 的字首指定了其監聽的協議為 WebSocket

WebSocketDeviceInboundPool實現了類似 JDBC資料庫連線池的客戶端 WebSocket連線池功能,並統一處理 WebSocket服務端對單個客戶端/多個客戶端(同組 A類裝置)的訊息推送,詳見 WebSocketDeviceInboundPool程式碼類解釋。

WebSocketDeviceInboundl

WebSocketDeviceInbound 類為每個 A類和 B 類裝置驗證登入後,客戶端建立的 HTTP長連線的對應後臺服務類,類似 Socket程式設計中的 SocketServer accept後的 Socket 程序,在 WebSocketInbound中接收客戶端傳送的實時位置資訊等訊息,並向客戶端(B類裝置)傳送下屬 A 類裝置實時位置資訊及位置分析結果資料,輸入流和輸出流都是 WebSocket協議定製的。WsOutbound負責輸出結果,StreamInbound WsInputStream負責接收資料:

清單 7.WebSocketDeviceInbound.java 類程式碼示例

public class WebSocketDeviceInbound extends MessageInbound {
private final HttpServletRequest request;
private DeviceAccount connectedDevice;
public DeviceAccount getConnectedDevice() {
return connectedDevice;
}
public void setConnectedDevice(DeviceAccount connectedDevice) {
this.connectedDevice = connectedDevice;
}
public HttpServletRequest getRequest() {
return request;
}
public WebSocketDeviceInbound(HttpServletRequest request) {
this.request = request;
DeviceAccount connectedDa = (DeviceAccount)request.getSession(true).getAttribute("connectedDevice");
if(connectedDa==null)
{
String deviceId = request.getParameter("id");
DeviceAccountDao deviceDao = new DeviceAccountDao();
connectedDa = deviceDao.getDaById(Integer.parseInt(deviceId));
}
this.setConnectedDevice(connectedDa);
}
@Override
protected void onOpen(WsOutbound outbound) {
 /
}
@Override
protected void onClose(int status) {
WebSocketDeviceInboundPool.removeMessageInbound(this);
}
@Override
protected void onBinaryMessage(ByteBuffer message) throws IOException {
throw new UnsupportedOperationException("Binary message not supported.");
}
@Override
protected void onTextMessage(CharBuffer message) throws IOException {
WebSocketDeviceInboundPool.processTextMessage(this, message.toString());
}
public void sendMessage(BaseEvent event)
{
String eventStr = JSON.toJSONString(event);
try {
this.getWsOutbound().writeTextMessage(CharBuffer.wrap(eventStr));
//…以下程式碼省略
} catch (IOException e) {
e.printStackTrace();
}
}
}

程式碼解釋:

connectedDevice 是當前連線的 A/B類客戶端裝置類例項,在這裡做為成員變數以便後續處理互動。

sendMessage 函式向客戶端傳送資料,使用 Websocket WsOutbound輸出流向客戶端推送資料,資料格式統一為 JSON

onTextMessage 函式為客戶端傳送訊息到伺服器時觸發事件,呼叫WebSocketDeviceInboundPoolprocessTextMessage統一處理 A 類裝置的登入,更新位置,離線等訊息。

onClose 函式觸發關閉事件,在連線池中移除連線。

WebSocketDeviceInbound 建構函式為客戶端建立連線後,WebSocketServlet createWebSocketInbound函式觸發,查詢 A /B 類裝置在後臺數據庫的詳細資料並例項化connectedDevice做為 WebSocketDeviceInbound的成員變數,WebSocketServlet類此時將新的WebSocketInbound例項加入自定義的WebSocketDeviceInboundPool連線池中,以便統一處理 A/B裝置組員關係及位置分佈資訊計算等業務邏輯。

WebSocketDeviceInboundPool

WebSocketInboundPool :由於需要處理大量 A B 類裝置的實時訊息,服務端會同時存在大量 HTTP長連線,為統一管理和有效利用 HTTP長連線資源,專案中使用了簡單的 HashMap實現記憶體連線池機制,每次裝置登入新建的 WebSocketInbound都放入 WebSocketInbound例項的連線池中,當裝置登出時,從連線池中 remove對應的 WebSocketInbound例項。

此外,WebSocketInboundPool類還承擔 WebSocket客戶端處理 A 類和 B 類裝置間訊息傳遞的作用,在客戶端傳送 A類裝置登入、登出及位置更新訊息的時候,服務端 WebSocketInboundPool進行位置分佈資訊的計算,並將計算完的結果向同時線上的 B類裝置推送。

清單 8.WebSocketDeviceInboundPool.java 程式碼示例

public class WebSocketDeviceInboundPool {
private static final ArrayList<WebSocketDeviceInbound> connections =
new ArrayList<WebSocketDeviceInbound>();
public static void addMessageInbound(WebSocketDeviceInbound inbound){
//新增連線
DeviceAccount da = inbound.getConnectedDevice();
System.out.println("新上線裝置 : " + da.getDeviceNm());
connections.add(inbound);
}
public static ArrayList<DeviceAccount> getOnlineDevices(){
ArrayList<DeviceAccount> onlineDevices = new ArrayList<DeviceAccount>();
for(WebSocketDeviceInbound webClient:connections)
{
onlineDevices.add(webClient.getConnectedDevice());
}
return onlineDevices;
}
public static WebSocketDeviceInbound getGroupBDevices(String group){
WebSocketDeviceInbound retWebClient =null;
for(WebSocketDeviceInbound webClient:connections)
{
if(webClient.getConnectedDevice().getDeviceGroup().equals(group)&&
webClient.getConnectedDevice().getType().e