一、WebSocket服務(wù)端創(chuàng)建
1.創(chuàng)建WebSocket服務(wù)端方式(注解)
//通過該注解來創(chuàng)建WebSocket服務(wù)端蚕礼,括號(hào)里面是服務(wù)端路徑
@ServerEndpoint("/websocket/{userID}")
public class WebSocketServer {
}
/**
* 連接建立成功調(diào)用的方法
* @param session 可選的參數(shù)。session為與某個(gè)客戶端的連接會(huì)話菲饼,需要通過它來給客戶端發(fā)送數(shù)據(jù)
*/
@OnOpen
public void onOpen(@PathParam("userID")Long userID, Session session){
System.out.println("webSocket客戶端建立了連接!");
}
/**
* 連接關(guān)閉調(diào)用的方法
*/
@OnClose
public void onClose(){
System.out.println("webSocket客戶端關(guān)閉了連接");
}
/**
* 收到客戶端消息后調(diào)用的方法
* @param message 客戶端發(fā)送過來的消息
*/
@OnMessage
public static void onMessage(String message){
System.out.println("從客戶斷接受到的消息:"+message);
}
/**
* 發(fā)生錯(cuò)誤時(shí)調(diào)用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
System.out.println("連接出錯(cuò)恢共,錯(cuò)誤類型是:"+error);
}
注意:創(chuàng)建webSocket的服務(wù)端最好用tomcat8以上的服務(wù)器,如果用低版本的tomcat服務(wù)器,可能又造成沖突
2.創(chuàng)建WebSocket客戶端(前端)
var websocket = null;
//判斷當(dāng)前瀏覽器是否支持WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:8080/websocket/1")
}
else{//ws://localhost:8080/websocket
alert('Not support websocket');
}
//連接發(fā)生錯(cuò)誤的回調(diào)方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//連接成功建立的回調(diào)方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到消息的回調(diào)方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
//alert("收到傳遞的信息柬泽!");
}
//連接關(guān)閉的回調(diào)方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
3、WebSocket服務(wù)端線程安全問題
- WebSocket服務(wù)端@onMessage注解的方法嫁蛇,如果有多個(gè)線程同時(shí)連接了服務(wù)端锨并,向服務(wù)端傳輸數(shù)據(jù),可能會(huì)造成線程的安全問題睬棚,報(bào)錯(cuò)
/**
* 收到客戶端消息后調(diào)用的方法
* @param message 客戶端發(fā)送過來的消息
*/
@OnMessage
public static void onMessage(String message){
session.getBasicRemote().sendText("用戶ID:"+id+"已經(jīng)接受到消息第煮,并且向客戶端返回消息,返回的消息:"+message);
System.out.println("從客戶斷接受到的消息:"+message);
}
- 上圖中的方法是線程不安全的抑党,筆者通過Jmeter測(cè)試工具來進(jìn)行多線程壓力測(cè)試包警,果然出現(xiàn)了數(shù)據(jù)寫入出錯(cuò)的問題
Jmeter測(cè)試實(shí)例
后臺(tái)傳輸數(shù)據(jù)報(bào)錯(cuò)
- 報(bào)錯(cuò)信息是由于多線程同時(shí)向socket里面寫入數(shù)據(jù)時(shí)報(bào)錯(cuò),增加@OnMessage注解方法底靠,增加synchronized關(guān)鍵字使方法變得線程安全
@OnMessage
public synchronized static void onMessage(String message){
session.getBasicRemote().sendText("用戶ID:"+id+"已經(jīng)接受到消息害晦,并且向客戶端返回消息,返回的消息:"+message);
System.out.println("從客戶斷接受到的消息:"+message);
}
4.webSocket斷線重連機(jī)制
var lockReconnect = false;//避免重復(fù)連接
var wsUrl = "ws://localhost:8081/websocket/1";
var ws =null;
var time;
function createWebSocket() {
try {
ws = new WebSocket(wsUrl);
init();
} catch (e) {
console.log("e");
reconnect(wsUrl)
}
}
function init() {
//連接發(fā)生錯(cuò)誤的回調(diào)方法
ws.onerror = function () {
setMessageInnerHTML("error");
reconnect(wsUrl);
};
//連接成功建立的回調(diào)方法
ws.onopen = function (event) {
setMessageInnerHTML("open");
heartCheck.start();
}
//接收到消息的回調(diào)方法
ws.onmessage = function (event) {
setMessageInnerHTML(event.data);
//alert("收到傳遞的信息苛骨!");
heartCheck.start();
}
//連接關(guān)閉的回調(diào)方法
ws.onclose = function () {
setMessageInnerHTML("close");
reconnect(wsUrl);
}
}
//監(jiān)聽窗口關(guān)閉事件篱瞎,當(dāng)窗口關(guān)閉時(shí)苟呐,主動(dòng)去關(guān)閉websocket連接痒芝,防止連接還沒斷開就關(guān)閉窗口,server端會(huì)拋異常牵素。
window.onbeforeunload = function () {
ws.close();
}
function reconnect(url) {
if (lockReconnect) {
return;
}
;
lockReconnect == true;
//沒連接上會(huì)一直重連严衬,設(shè)置延遲避免請(qǐng)求過多
time && clearTimeout(time)
time = setTimeout(function () {
createWebSocket(url);
lockReconnect = false;
}, 4000)
}
//心跳檢測(cè)
var heartCheck = {
timeOut: 3000,
timeOutObj: null,
serverTimeOutObj: null,
start: function () {
var self = this;
//將關(guān)閉客戶端的延遲方法取消
self.serverTimeOutObj && clearTimeout(self.serverTimeOutObj);
self.timeOutObj = setTimeout(function () {
//這里發(fā)送一個(gè)心跳消息,后端收到心跳消息后返回一個(gè)心跳消息笆呆,采用延遲2秒發(fā)送避免造成服務(wù)端消息堵塞
ws.send("12345678");
//創(chuàng)建延遲方法请琳,如果2秒鐘后臺(tái)沒有響應(yīng)消息粱挡,客戶端就會(huì)關(guān)閉連接,觸發(fā)websocket重連
self.serverTimeOutObj = setTimeout(function() {
ws.close();
// createWebSocket();
}, 2000);
},2000)
}
}
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//頁面初始化時(shí)創(chuàng)建連接
createWebSocket(wsUrl);
-
斷線重連效果
image 上圖顯示了客戶端創(chuàng)建了WebSocket的連接之后俄精,向后臺(tái)定時(shí)發(fā)送心跳询筏,然后手動(dòng)關(guān)掉服務(wù),前臺(tái)頁面顯示重連竖慧,在服務(wù)重啟的過程中前臺(tái)重連報(bào)錯(cuò)嫌套,后臺(tái)服務(wù)啟動(dòng)成功之后前端重新創(chuàng)建WebSocket成功,同時(shí)繼續(xù)發(fā)送心跳服務(wù)