如果說之前的 Socket.IO打造基礎聊天室 讓我明白了聊天室的原理,知道了如何實現羣聊(廣播)和私聊(單播)等,那麼對於 SockJS 的實踐讓我更加了解了websocket,因爲 Sock.IO 是自己封裝的接口,而 SockJS 則使用了跟 websocket 幾乎相同的 API。
01 SockJS簡介
- 與 Socket.io 不同,在傳輸方式的選擇方面,SockJS 會優先採用 websocket,然後再自動降級;
- 其 API 幾乎與 websocket API 的使用方式相同;
- 兼容跨瀏覽器,支持跨域。
02 即時通信關鍵點
(1)心跳檢測(業務層面)
(ws自身有心跳,但是如果有一些業務層面的需求,就需要自己實現心跳)
ws建立成功時便進行 心跳請求(每隔一段時間發送一個 PING),同時初始化 超時重連。如果在達到心跳規定次數後仍沒有返回 PONG,則判定心跳超時,前端主動關閉ws,觸發 ws重連。
如果期間收到了 PONG,則重新初始化超時重連。
【注意】:ws重連的時候,要清空之前的心跳定時器。
(2)關閉連接的3種情況:
- 心跳超時(連接層不斷)的情況,則前端可主動關閉 ws;
- 連接未建立成功(如TCP連接斷掉),ws 自動關閉;
- 服務端關閉(如多端剔除),這是要防止前端進行重連。
正常關閉(如心跳超時)
連接失敗(如TCP層沒有連接成功)
【注意】:鎖屏情況下,js 會停止工作,這時,ws 會自動關閉,當屏幕喚醒時,通過觸發
onclose
事件,ws 又會進行重連。在某些特殊業務場景下,需要注意下這種情況。
(3)websocket 自動重連
無論是前端主動關閉 ws,還是ws自動關閉,都會觸發 onclose
事件,可在其中進行重連。如果達到了重連次數或者後端返回了不可進行重連的標誌碼,則不進行重連。
【實現思路】:
var CONNECT_COUNT = 3;
var sockjs;
var sockjs_url = '/chat/sjs/';
var isClose = false;
var connectCount = 1;
new_conn = function() {
sockjs = new SockJS(sockjs_url);
sockjs.onopen = function() {
connectCount = 1; // 重置重連次數
this.checkHeartBeat(); // 心跳檢測
}
sockjs.onmessage = function(e) {
var data = JSON.parse(e.data);
// handleMessage(data);
};
sockjs.onclose = function(e) {
// 已經關閉的情況,不重連
if (isClose) {
return;
}
// 小於重連次數
if (connectCount < CONNECT_COUNT) {
setTimeout(function() {
new_conn();
}, (Math.random() * 3 + 1 )* 1000);
} else {
isClose = true;
}
};
}
(4)sockJS session_id:可用於多端剔除
url格式類似於:/resource/<server_number>/<session_id>/transport
;
請求URL
每個 sockJS 都有 session_id
,可通過它來判斷是否同一個用戶建立了2個ws,如多端登錄的情況下,可用於進行多端剔除。
var socket = new SockJS('/mqClient');
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
console.log(socket._transport.url);
//it contains ws://localhost:8080/mqClient/039/byxby3jv/websocket
//sessionId is byxby3jv
});
The SockJS constructor has an option parameter and there you can pass a custom session id generator as a function:
let sessionId = utils.random_string(8);
let socket = new SockJS('/socket', [], {
sessionId: () => {
return sessionId
}
});
03 sockJS readyState(暫時沒用到。。)
【sockjs-client 部分源碼】:
SockJS.CONNECTING = 0;
SockJS.OPEN = 1;
SockJS.CLOSING = 2;
SockJS.CLOSED = 3;
SockJS.prototype.close = function(code, reason) {
// Step 1
if (code && !userSetCode(code)) {
throw new Error('InvalidAccessError: Invalid code');
}
// Step 2.4 states the max is 123 bytes, but we are just checking length
if (reason && reason.length > 123) {
throw new SyntaxError('reason argument has an invalid length');
}
// Step 3.1
if (this.readyState === SockJS.CLOSING || this.readyState === SockJS.CLOSED) {
return;
}
// TODO look at docs to determine how to set this
var wasClean = true;
this._close(code || 1000, reason || 'Normal closure', wasClean);
};
參考
- 學習WebSocket協議—從頂層到底層的實現原理(修訂版)
- websocket RFC - EN
- websocket RFC(翻譯)
- sockjs chat room
- What happens if sockJS gets disconnected?