本文目錄一覽:
- 1、WebSocket斷開重連機制 實現demo,非採用onclose事件方式
- 2、如何避免websocket重複連接
- 3、websocket stomp連接一段時間後斷開
- 4、Nginx websocket proxy斷連問題
- 5、websocket
WebSocket斷開重連機制 實現demo,非採用onclose事件方式
WebSocket.onclose 事件監聽器 ,不可控性和斷網情況下不觸發問題,無法很好實現斷線重連功能。
我們解決方案是,根據服務端一定時間,自動給客戶端推送的心跳,心跳來判斷是否斷開,如果一定時間內沒有收到服務器發送的心跳,則會觸發重連。(很像醫生搶救重症病人,看心跳圖沒有波動,啟用心電復蘇)
如何避免websocket重複連接
你實現TextWebSocketHandler中的類中在 afterConnectionClosed();連接關閉後,裡面如果定義了用戶斷開連接就移除websocketsession就沒有問題。還有就是不要在多個頁面建立連接,盡量使用子夫頁面互調方法的方式實現數據傳遞。
websocket stomp連接一段時間後斷開
因為項目中存在頻繁的由服務器發起的數據交換,相比使用Ajax輪訓的方式,websocket長連接和雙向保持的特點能夠較好的提升數據交換的性能。
為了簡便,直接使用spring boot + shiro + stomp和socketJs作為構建的工具。
但是由於使用時,主要是由服務端進行數據的推送,通過stomp自行保持心跳,就會存在session過期導致連接斷開的情況,而且由於stomp本身不會提示錯誤原因,導致排查起來比較麻煩,因此記錄下整個糾錯過程以備忘。
在一開始打開時,一切正常,數據能推送成功,但是在大約10分鐘左右(時長不定)能通過chrome的前端調試工具發現stomp提示連接斷開。服務器端無任何異常提示。
因為服務器端無任何異常,且斷連時長不定,因此推測為可能是session導致的自動斷鏈或者心跳丟失導致的。因為stomp本身不會報錯誤原因,因此想要找到具體的錯誤原因比較麻煩。後來發現可以通過斷點到stomp的onclose時間就能夠獲取到具體的錯誤碼,這就為我們定位問題提供了很好的幫助(具體斷點位置如下圖所示)
最後通過錯誤碼發現,提示的錯誤碼是1008,reason部分提示的是http session被關閉,因此問題就比較清楚了,是由於session釋放導致的斷鏈。這一點就很有意思,因為在使用過程中為了避免不必要的數據傳輸就一直沒有發起websocket的send事件,只是一直在subscribe監聽服務器的推送。因此導致了服務器的session一直沒有被touch(),從而釋放。(其實個人覺得這個可能也是一個比較有意思的問題,為什麼心跳沒有被處理會觸發到session,具體沒有試驗是不是和我使用了shiro的session作為管理有關,有興趣的同學們可以嘗試一下)
這裡也順便貼上對於錯誤碼的描述和相關參考資料,便於大家解決其它的錯誤碼問題:
那麼問題找到了響應的解決方案就比較好處理了,但是究竟哪種方案比較好,還是得看具體的業務需求和對利弊的取捨。
問題本身倒是不複雜,就是對stomp不熟悉,確定具體的錯誤原因上花了很多時間,進行了很多白費的嘗試,所以希望能記錄下,做個以後的備忘,防止再走很多彎路。
Nginx websocket proxy斷連問題
由於項目中需要服務端向移動設備主動推送信令,於是引入websocket協議進行移動設備和服務端之間的通信。Demo程序很正常,服務端使用nodejs ws類庫起一個websocket服務器,android設備中使用Okhttp3-ws對接,一切都比較正常。但是集成到生產環境kong api gateway後出現了連接一段時間android設備出現java.io.EOFException錯誤,服務端ws服務器出現1006錯誤,websocket連接斷開的問題。
由於demo程序運行正常,生產環境與demo環境區別在於android設備和服務端之間使用kong api gateway工具進行了proxy,kong是基於Nginx的api管理工具,於是問題分析重點鎖定gateway轉發的過程。
首先抓包比對一下demo環境和生產環境區別。
demo環境wireshark包:
可以看到,GET請求包和協議切換響應包間隔很短。
kong 環境包:
嘗試修改了read timeout的時間,果然出現斷連的時間隨着timeout而變化,應該就是Nginx這個機制導致的。文檔中也明確說明了解決方法,在時間間隔小於read timeout的輪詢中不斷和服務器進行ping包發送來刷新timeout時間。
實際測試,用20秒的間隔不斷給服務器發送心跳,可以保持websocket連接不斷。
但是還有一個問題,http upgarde包發出去後應該要等http code 101 Switching Protocols這個包回來才算握手完成,才可以建立tcp長連接,但是在nginx環境下這個包60秒後才回來,但是中間數據交互都是正常的,讓人匪夷所思。
再仔細看下nginx轉發wireshark包,發現http code 101 Switching Protocols包是由3片TCP包組裝而成的:
第一幀序號23,找到後發現內容就是服務器響應握手的http報文內容
所以在25幀時實際已經握手完成了,並非在62秒才傳包回來。
第二幀:
傳遞的數據是connected.
這樣就和協議描述一致,先完成handshake再transfer data。但是經過nginx抓發的包一直沒有結束符,所以導致wireshark在斷連得時候才顯示該http報完成,此處不知是不是nginx websocket proxy的bug? 如果有大神知道還請指導。
websocket
建立連接(創建WebSocket對象):
var Socket =new WebSocket(url, [protocol] );// url:服務器端地址;protocol:可選,指定可接受的子協議。
WebSocket對象提供了兩個屬性、四個事件、兩個方法,分別是:
1.WebSocket.readyState屬性:表示連接狀態
0——(連接尚未建立);1——(連接已建立);2——(連接正在關閉);3——(連接已經關閉或連接不能打開)
2.WebSocket.bufferedAmount屬性:被send()放入傳輸隊列,還未發出的UTF-8文本字節數
ws.addEventListener(‘open’,function(event){
ws.send(‘Hello Server!’);
});
ws.onopen =function(){
ws.send(‘Hello Server!’);
}
3.open:WebSocket.onopen():連接建立時觸發的事件
4.message:WebSocket.onmessage(): 客戶端接收服務器端發送的信息時觸發
5.error:WebSocket.onerror():通信發生錯誤時觸發
6.close:WebSocket.onclose():連接關閉時觸發
7.WebSocket.send():發送信息的方法
8.WebSocket.close():關閉連接方法
SOCKJS
SOCKJS 是一個瀏覽器使用的js庫,它提供了一個類似網絡的對象,和連貫的,跨瀏覽器的jaApi,可以在瀏覽器和服務器之間創建一個低延遲,全雙工的跨域通信通道
SOCKJS實現了對瀏覽器的兼容,spring框架提供了很多透明的,回退選項,如果遇到低版本的瀏覽器會自動降級為輪詢,支持就用websocket
sockjs-client
sockjs-client 是從SOCKJS分離出來的客戶端使用的通信模塊
stompjs
STOMP(Simple Text-Orientated Messaging Protocol) 面向消息的簡單文本協議;
websocket只是一個消息架構,不強制使用任何的消息協議,為了更好的在瀏覽器和服務器之間傳遞消息,使用stomp協議 的stompjs
STOMP與WebSocket 的關係:就是沒使用http而使用stomp協議,在瀏覽器和服務器之間進行消息傳遞
1.HTTP協議解決了web瀏覽器發起請求以及web服務器響應請求的細節,假設HTTP協議不存在,只能使用TCP套接字來編寫web應用,你可能認為這是一件瘋狂的事情;
2.直接使用WebSocket(SockJS)就很類似於使用TCP套接字來編寫web應用,因為沒有高層協議,就需要我們定義應用間發送消息的語義,還需要確保連接的兩端都能遵循這些語義;
3同HTTP在TCP套接字上添加請求-響應模型層一樣,STOMP在WebSocket之上提供了一個基於幀的線路格式層,用來定義消息語義、
安裝
npm install sockjs-client –save
npm install stompjs –save
引入
import SockJS from ‘sockjs-client’;
import Stomp from ‘stompjs’;
export default {
data(){
return {
stompClient:”,
timer:”,
}
},
methods:{
initWebSocket() {
this.connection();
let that= this;
// 斷開重連機制,嘗試發送消息,捕獲異常發生時重連
this.timer = setInterval(() = {
try {
that.stompClient.send(“test”);
} catch (err) {
console.log(“斷線了: ” + err);
that.connection();
}
}, 5000);
},
connection() {
// 建立連接對象
let socket = new SockJS(‘execution-progress/info?t=1593744273983’);
var sockjs =new SockJS(url, _reserved, options);
//server(string):添加到url的字符串,默認為隨機的4位數
//transports (string OR array of strings):回退傳輸列表
// sessionId (number OR function):會話標識,函數必須返回一個隨機生成的字符串
// 獲取STOMP子協議的客戶端對象
this.stompClient = Stomp.over(socket);
// 定義客戶端的認證信息,按需求配置
let headers = {
Authorization:”
}
// 向服務器發起websocket連接
this.stompClient.connect(headers,() = {
this.stompClient.subscribe(‘/topic/public’, (msg) = { // 訂閱服務端提供的某個topic
console.log(‘廣播成功’)
console.log(msg); // msg.body存放的是服務端發送給我們的信息
},headers);
this.stompClient.send(“/app/chat.addUser”,
headers,
JSON.stringify({sender: ”,chatType: ‘JOIN’}),
) //用戶加入接口
}, (err) = {
// 連接發生錯誤時的處理函數
console.log(‘失敗’)
console.log(err);
});
}, //連接 後台
disconnect() {
if (this.stompClient) {
this.stompClient.disconnect();
}
}, // 斷開連接
},
mounted(){
this.initWebSocket();
},
beforeDestroy: function () {
// 頁面離開時斷開連接,清除定時器
this.disconnect();
clearInterval(this.timer);
}
}
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/200677.html