WebWorker與WebSocket實現前端消息總線

Web Worker讓JS有了多線程的能力,能夠將複雜耗時的操做都交付給Worker線程處理。WebSocket讓web端與服務端維持一個有效的長鏈接,實現服務端主動推送數據。將兩者一結合,業務系統信息流轉通知功能徹底就能夠剝離出來。javascript

架構圖

JS Worker

Worker工做在一個專用的做用域DedicatedWorkerGlobalScope,在這個做用域中,不能直接操做DOM節點,不能使用Window對象的默認方法和屬性。不過對於網絡的訪問是徹底沒有問題的。具體能使用那些對象和方法請點擊這裏查看html

從上圖中可明顯的看出,Worker在當前架構中實現一個橋樑的左右,上鍊接socket端中的數據,下負責分發socket中的數據。此處咱們先了解下Worker自己的功能實現。前端

  1. 主線程與Worker線程經過方法postMessage相互傳遞信息
  2. 主線程與Worker線程經過事件onmessage接收相互傳遞的消息
  3. Worker中引入第三方js使用方法importScripts([url,])
  4. 主線程調用worker.terminate()結束線程
  5. Worker線程經過調用this.close()結束自身線程

新建一個webworker.js文件,並在其中編寫以下代碼java

//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真是讓人充滿挫敗感的瀏覽器git

新建一個index.html頁面,在script塊中編寫如下代碼,實現與webworker.js通信github

//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一出現,一切都好辦了。web

接下來咱們開始創建一個WebSocket鏈接api

方法中的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處理事件,負責接收服務端推送的消息tomcat

//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地址,還有一點感悟,對於開發中的新知識點,首先你得學會怎麼用,其次在經過閱讀源碼,以及理論知識讓你使用的更順利,甚至改變它。

有喜歡聊技術朋友也歡迎入羣,若二維碼失效可加我微信回覆前端

相關文章
相關標籤/搜索