Web Worker讓JS有了多執行緒的能力,可以將複雜耗時的操作都交付給Worker執行緒處理。WebSocket讓web端與服務端維持一個有效的長連線,實現服務端主動推送資料。將二者一結合,業務系統資訊流轉通知功能完全就可以剝離出來。
架構圖
JS Worker
Worker工作在一個專用的作用域DedicatedWorkerGlobalScope
,在這個作用域中,不能直接操作DOM節點,不能使用Window
物件的預設方法和屬性。不過對於網路的訪問是完全沒有問題的。具體能使用那些物件和方法請點選這裡檢視
從上圖中可明顯的看出,Worker在當前架構中實現一個橋樑的左右,上連線socket端中的資料,下負責分發socket中的資料。此處我們先了解下Worker本身的功能實現。
- 主執行緒與Worker執行緒通過方法
postMessage
相互傳遞資訊 - 主執行緒與Worker執行緒通過事件
onmessage
接收相互傳遞的訊息 - Worker中引入第三方js使用方法
importScripts([url,])
- 主執行緒呼叫
worker.terminate()
結束執行緒 - Worker執行緒通過呼叫
this.close()
結束自身執行緒
新建一個webworker.js
檔案,並在其中編寫如下程式碼
//author:herbert qq:464884492
onmessage = function (event) {
if (event.data.code) {
var code = event.data.code.toLowerCase();
switch (code) {
case "init":
var userId = event.data.loggedUserId;
var sessionId = event.data.sessionid;
if (!sessionId) {
this.close();
return;
}
postMessage({ code: "codeone", msg: "你好,元件1" });
postMessage({ code: "codetwo", msg: "你好,元件2" });
break;
default:
break;
}
}
}
複製程式碼
注意:在 onmessage 前不能加var否則在IE下會接收不了訊息。IE真是讓人充滿挫敗感的瀏覽器
新建一個index.html頁面,在script塊中編寫以下程式碼,實現與webworker.js通訊
//author:herbert qq:464884492
var work = new Worker('webworker.js')
, textone = document.querySelector("#textone")
, textTwo = document.querySelector("#texttwo")
textAll = document.querySelector("#textAll");
work.onmessage = function (event) {
var data = event.data;
if (!!data.code) {
switch (data.code) {
case "close":
work.terminate();
case "codeone":
textone.value = textone.value + JSON.stringify(data) + "\r\n";
textAll.value = textAll.value + JSON.stringify(data) + "\r\n";
break;
case "codetwo":
textTwo.value = textTwo.value + JSON.stringify(data) + "\r\n";
textAll.value = textAll.value + JSON.stringify(data) + "\r\n";
break;
default:
textAll.value = textAll.value + JSON.stringify(data) + "\r\n";
}
}
};
work.postMessage({
code: "init",
loggedUserId: 'demo',
sessionid: 'demo'
});
複製程式碼
JS WebSocket
WebSocket和Http一樣都是基於Tcp協議。不同是WebSocket實現了服務端與客戶端的全雙工通訊。在Websocket未出現之前,要是實現一個資訊推送的功能,通過http來實現唯一方案就是輪訓,輪訓分長短,各有弊端。現在WebSocket一出現,一切都好辦了。
接下來我們開始建立一個WebSocket連線
方法中的root表示當前作用域,在主執行緒是root=window,在WebWorker執行緒root=DedicatedWorkerGlobalScope
//author:herbert qq:464884492
var root = this,socket =null;
function connect(wsurl) {
if ('WebSocket' in root) {
socket = new WebSocket(wsurl);
} else if ('MozWebSocket' in root) {
socket = new MozWebSocket(wsurl);
} else {
alert("您的瀏覽器版本過低,將不能接收系統訊息");
}
}
複製程式碼
wsurl格式為ws:\\
或者 wss:\\
,後者表示SSL加密傳輸。實際地址如: ws://localhost:8090/demo/demowebsocket
接下來,我們需要為socket處理事件,負責接收服務端推送的訊息
//author:herbert qq:464884492
function onOpen() {
postMessage({ code: "openConnect" });
}
function onClose() {
postMessage({ code: "closewsconnect" });
}
function onMessaage(event) {
postMessage(JSON.parse(event.data));
}
function onError(event) {
socket = null;
if (event.target.readyState == 3) {
//斷線重連
setTimeout(function () {
connect(event.target.url);
initMessageEvent();
}, 1000);
}
}
function sendMessage(msg) {
if (socket == null) return;
socket.send(msg);
}
function initMessageEvent() {
socket.onopen = onOpen; //socket連線成功處理事件
socket.onclose = onClose; //socket連線關閉處理事件
socket.onmessage = onMessaage; //socket接收到新訊息
socket.onerror = onError; //soket錯誤處理事件
}
複製程式碼
JAVA WebSocket
Tomcat7x已經實現了標準WebScoket介面,在專案中只需要編寫一個普通的實體bean配置註解就可以實現一個標準的WebSocket Api。開發中主要使用一些註解
- @ServerEndpoint 設定WebSocket連線地址,以及url引數
如: @ServerEndpoint(value = "/demowebsocket/{userId}/{sessionId}"),其中{userId}、{sessionId} 為
pathParam
可以在onOpen函式中通過函式引數 @PathParam 獲取 - @PathParam 獲取URL地址上對應的註解引數
- @OnOpen 建立連線註解
- @OnClose 關閉連線註解
- @OnMessage 接收訊息註解
- @OnError 錯誤註解
被註解約束的函式都可以任意選擇需要的引數,可選擇的引數有 Session、EndpointConfig 以及 @PathParam, 服務端Bean程式碼如下
//author:herbert qq:464884492
@ServerEndpoint(value = "/demowebsocket/{userId}/{sessionId}")
public class DemoWebSokcet {
private static final Set<DemoWebSokcet> connections = new CopyOnWriteArraySet<DemoWebSokcet>();
private Session session;
public DemoWebSokcet() {
}
@OnOpen
public void openConnection(Session session, EndpointConfig conf,
@PathParam("userId") String userId,
@PathParam("sessionId") String sessionId) {
this.session = session;
connections.add(this);
JSONObject jo = new JSONObject();
jo.put("code", "newuser");
jo.put("userid", userId);
jo.put("sessionid", sessionId);
jo.put("msg", "server:新連線使用者");
sendMessage(jo);
// 測試 程式碼
JSONObject jo1 = new JSONObject();
jo1.put("code", "codeone");
jo1.put("userid", userId);
jo1.put("sessionid", sessionId);
jo1.put("msg", "Server:元件1你好");
sendMessage(jo1);
JSONObject jo2 = new JSONObject();
jo2.put("code", "codetwo");
jo2.put("userid", userId);
jo2.put("sessionid", sessionId);
jo2.put("msg", "server:元件2你好");
sendMessage(jo2);
}
@OnClose
public void closeConnection(@PathParam("userId") String userId,
@PathParam("sessionId") String sessionId) {
connections.remove(this);
JSONObject jo = new JSONObject();
jo.put("code", "connectionClose");
jo.put("userid", userId);
jo.put("sessionid", sessionId);
jo.put("msg", "server:連線關閉");
sendMessage(jo);
}
// 處理文字訊息
@OnMessage
public void handleTextMsg(Session session, String message,
@PathParam("userId") String userId,
@PathParam("sessionId") String sessionId) {
System.out.println("userId=>" + userId + " sessionId=>" + sessionId);
// 原樣轉發客戶端訊息
sendMessage(JSONObject.parseObject(message));
}
// 處理二進位制訊息
@OnMessage
public void handleBinaryMsg(Session session, ByteBuffer msg,
@PathParam("userId") String userId,
@PathParam("sessionId") String sessionId) {
}
// 處理pong訊息
@OnMessage
public void handlePongMsg(Session session, PongMessage msg,
@PathParam("userId") String userId,
@PathParam("sessionId") String sessionId) {
JSONObject jo = new JSONObject();
jo.put("code", "pong");
jo.put("userid", userId);
jo.put("sessionid", sessionId);
jo.put("msg", msg.getApplicationData().toString());
sendMessage(jo);
}
@OnError
public void onError(Throwable t, @PathParam("userId") String userId,
@PathParam("sessionId") String sessionId) throws Throwable {
JSONObject jo = new JSONObject();
jo.put("code", "servererror");
jo.put("userid", userId);
jo.put("sessionid", userId);
jo.put("msg", t.getMessage());
sendMessage(jo);
}
private static void sendMessage(JSONObject msg) {
for (DemoWebSokcet client : connections) {
try {
synchronized (client) {
client.session.getBasicRemote()
.sendText(msg.toJSONString());
}
} catch (IOException e) {
JSONObject jo = new JSONObject();
jo.put("code", "servererror");
jo.put("userid",
client.session.getPathParameters().get("userid"));
jo.put("sessionid",
client.session.getPathParameters().get("sessionid"));
connections.remove(client);
try {
client.session.close();
} catch (IOException e1) {
}
jo.put("msg", "server:傳送訊息出現異常,連線已關閉" + e.getMessage());
sendMessage(jo);
}
}
}
}
複製程式碼
在測試程式碼編寫過程中,通過pom方式引入javax.websocket-api,啟動後始終出現 Error during WebSocket handshake: Unexpected response code: 404
連線錯誤,後來通過直接件tomcat/bin下對應的tomcat實現的jar複製到webapp對應的bin資料夾下解決問題。
Demo預覽
總結
篇幅比較長,讀到這裡也不容易!WebWorker和WebSocket我也是第一次將二者結合起來。感覺現在javascript功能真的是越來越豐富了。demo地址,還有一點感悟,對於開發中的新知識點,首先你得學會怎麼用,其次在通過閱讀原始碼,以及理論知識讓你使用的更順利,甚至改變它。