Spring Boot 集成 WebSocket挪捕,實現前后端即時通訊

一粗梭、什么是websocket?

WebSocket協議是基于TCP的一種新的網絡協議级零。它實現了客戶端與服務器全雙工通信断医,學過計算機網絡都知道,既然是全雙工奏纪,就說明了服務器可以主動發(fā)送信息給客戶端 鉴嗤。這與我們的推送技術或者是多人在線聊天的功能不謀而合。

為什么不使用HTTP 協議呢序调?這是因為HTTP是單工通信醉锅,通信只能由客戶端發(fā)起,客戶端請求一下发绢,服務器處理一下硬耍,這就太麻煩了。于是websocket應運而生边酒。

下面我們就直接開始使用Springboot開始整合经柴。以下案例都在我自己的電腦上測試成功,你可以根據自己的功能進行修改即可墩朦。我的項目結構如下:

二坯认、使用步驟

1.添加依賴

Maven依賴:

org.springframework.boot

spring-boot-starter-websocket

2.啟用Springboot對WebSocket的支持

啟用WebSocket的支持也是很簡單,幾句代碼搞定:

importorg.springframework.context.annotation.Bean;

importorg.springframework.context.annotation.Configuration;

importorg.springframework.web.socket.server.standard.ServerEndpointExporter;

/**

*?@?Auther:?馬超偉

*?@?Date:?2020/06/16/14:35

*?@?Description:?開啟WebSocket支持

*/

@Configuration

publicclassWebSocketConfig{

@Bean

publicServerEndpointExporterserverEndpointExporter(){

returnnewServerEndpointExporter();

}

}

3.核心配置:WebSocketServer

因為WebSocket是類似客戶端服務端的形式(采用ws協議)氓涣,那么這里的WebSocketServer其實就相當于一個ws協議的Controller

@ ServerEndpoint 注解是一個類層次的注解牛哺,它的功能主要是將目前的類定義成一個websocket服務器端, 注解的值將被用于監(jiān)聽用戶連接的終端訪問URL地址,客戶端可以通過這個URL來連接到WebSocket服務器端

新建一個ConcurrentHashMap webSocketMap 用于接收當前userId的WebSocket,方便傳遞之間對userId進行推送消息春哨。

下面是具體業(yè)務代碼:

packagecc.mrbird.febs.external.webScoket;

importcom.baomidou.mybatisplus.core.conditions.query.QueryWrapper;

importcom.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;

importlombok.extern.slf4j.Slf4j;

importorg.springframework.stereotype.Component;

importorg.springframework.stereotype.Service;

importjavax.websocket.*;

importjavax.websocket.server.PathParam;

importjavax.websocket.server.ServerEndpoint;

importjava.io.IOException;

importjava.time.LocalDateTime;

importjava.util.List;

importjava.util.concurrent.CopyOnWriteArraySet;

/**

*?Created?with?IntelliJ?IDEA.

*?@?Auther:?馬超偉

*?@?Date:?2020/06/16/14:35

*?@?Description:

*?@?ServerEndpoint?注解是一個類層次的注解荆隘,它的功能主要是將目前的類定義成一個websocket服務器端,

*?注解的值將被用于監(jiān)聽用戶連接的終端訪問URL地址,客戶端可以通過這個URL來連接到WebSocket服務器端

*/

@Component

@Slf4j

@Service

@ServerEndpoint("/api/websocket/{sid}")

publicclassWebSocketServer{

//靜態(tài)變量,用來記錄當前在線連接數赴背。應該把它設計成線程安全的椰拒。

privatestaticintonlineCount?=0;

//concurrent包的線程安全Set晶渠,用來存放每個客戶端對應的MyWebSocket對象。

privatestaticCopyOnWriteArraySet?webSocketSet?=newCopyOnWriteArraySet();

//與某個客戶端的連接會話燃观,需要通過它來給客戶端發(fā)送數據

privateSession?session;

//接收sid

privateString?sid?="";

/**

*?連接建立成功調用的方法

*/

@OnOpen

publicvoidonOpen(Session?session,?@PathParam("sid")String?sid){

this.session?=?session;

webSocketSet.add(this);//加入set中

this.sid?=?sid;

addOnlineCount();//在線數加1

try{

sendMessage("conn_success");

log.info("有新窗口開始監(jiān)聽:"+?sid?+",當前在線人數為:"+?getOnlineCount());

}catch(IOException?e)?{

log.error("websocket?IO?Exception");

}

}

/**

*?連接關閉調用的方法

*/

@OnClose

publicvoidonClose(){

webSocketSet.remove(this);//從set中刪除

subOnlineCount();//在線數減1

//斷開連接情況下褒脯,更新主板占用情況為釋放

log.info("釋放的sid為:"+sid);

//這里寫你?釋放的時候参咙,要處理的業(yè)務

log.info("有一連接關閉勒奇!當前在線人數為"+?getOnlineCount());

}

/**

*?收到客戶端消息后調用的方法

*?@?Param?message?客戶端發(fā)送過來的消息

*/

@OnMessage

publicvoidonMessage(String?message,?Session?session){

log.info("收到來自窗口"+?sid?+"的信息:"+?message);

//群發(fā)消息

for(WebSocketServer?item?:?webSocketSet)?{

try{

item.sendMessage(message);

}catch(IOException?e)?{

e.printStackTrace();

}

}

}

/**

*?@?Param?session

*?@?Param?error

*/

@OnError

publicvoidonError(Session?session,?Throwable?error){

log.error("發(fā)生錯誤");

error.printStackTrace();

}

/**

*?實現服務器主動推送

*/

publicvoidsendMessage(String?message)throwsIOException{

this.session.getBasicRemote().sendText(message);

}

/**

*?群發(fā)自定義消息

*/

publicstaticvoidsendInfo(String?message,?@PathParam("sid")String?sid)throwsIOException{

log.info("推送消息到窗口"+?sid?+",推送內容:"+?message);

for(WebSocketServer?item?:?webSocketSet)?{

try{

//這里可以設定只推送給這個sid的懈糯,為null則全部推送

if(sid?==null)?{

//????????????????????item.sendMessage(message);

}elseif(item.sid.equals(sid))?{

item.sendMessage(message);

}

}catch(IOException?e)?{

continue;

}

}

}

publicstaticsynchronizedintgetOnlineCount(){

returnonlineCount;

}

publicstaticsynchronizedvoidaddOnlineCount(){

WebSocketServer.onlineCount++;

}

publicstaticsynchronizedvoidsubOnlineCount(){

WebSocketServer.onlineCount--;

}

publicstaticCopyOnWriteArraySetgetWebSocketSet(){

returnwebSocketSet;

}

}

4.測試Controller

importorg.springframework.stereotype.Controller;

importorg.springframework.web.bind.annotation.GetMapping;

importorg.springframework.web.bind.annotation.PathVariable;

importorg.springframework.web.bind.annotation.RequestMapping;

importorg.springframework.web.bind.annotation.ResponseBody;

importorg.springframework.web.servlet.ModelAndView;

importjava.io.IOException;

importjava.util.HashMap;

importjava.util.Map;

/**

*?Created?with?IntelliJ?IDEA.

*

*?@?Auther:?馬超偉

*?@?Date:?2020/06/16/14:38

*?@?Description:

*/

@Controller("web_Scoket_system")

@RequestMapping("/api/socket")

publicclassSystemController{

//頁面請求

@GetMapping("/index/{userId}")

publicModelAndViewsocket(@PathVariable?String?userId){

ModelAndView?mav?=newModelAndView("/socket1");

mav.addObject("userId",?userId);

returnmav;

}

//推送數據接口

@ResponseBody

@RequestMapping("/socket/push/{cid}")

publicMappushToWeb(@PathVariable?String?cid,?String?message){

Map?result?=newHashMap<>();

try{

WebSocketServer.sendInfo(message,?cid);

result.put("code",?cid);

result.put("msg",?message);

}catch(IOException?e)?{

e.printStackTrace();

}

returnresult;

}

}

5.測試頁面index.html

Java后端WebSocket的Tomcat實現

Welcome

發(fā)送消息


關閉WebSocket連接


varwebsocket?=null;

//判斷當前瀏覽器是否支持WebSocket

if('WebSocket'inwindow)?{

//改成你的地址

websocket?=newWebSocket("ws://192.168.100.196:8082/api/websocket/100");

}else{

alert('當前瀏覽器?Not?support?websocket')

}

//連接發(fā)生錯誤的回調方法

websocket.onerror?=function(){

setMessageInnerHTML("WebSocket連接發(fā)生錯誤");

};

//連接成功建立的回調方法

websocket.onopen?=function(){

setMessageInnerHTML("WebSocket連接成功");

}

varU01data,?Uidata,?Usdata

//接收到消息的回調方法

websocket.onmessage?=function(event){

console.log(event);

setMessageInnerHTML(event);

setechart()

}

//連接關閉的回調方法

websocket.onclose?=function(){

setMessageInnerHTML("WebSocket連接關閉");

}

//監(jiān)聽窗口關閉事件脊框,當窗口關閉時颁督,主動去關閉websocket連接,防止連接還沒斷開就關閉窗口浇雹,server端會拋異常沉御。

window.onbeforeunload?=function(){

closeWebSocket();

}

//將消息顯示在網頁上

functionsetMessageInnerHTML(innerHTML){

document.getElementById('message').innerHTML?+=?innerHTML?+'<br/>';

}

//關閉WebSocket連接

functioncloseWebSocket(){

websocket.close();

}

//發(fā)送消息

functionsend(){

varmessage?=document.getElementById('text').value;

websocket.send('{"msg":"'+?message?+'"}');

setMessageInnerHTML(message?+"&#13;");

}

6.結果展示

后臺:如果有連接請求

前臺顯示:

總結

這中間我遇到一個問題,就是說WebSocket啟動的時候優(yōu)先于spring容器昭灵,從而導致在WebSocketServer中調用業(yè)務Service會報空指針異常

所以需要在WebSocketServer中將所需要用到的service給靜態(tài)初始化一下:如圖所示:

還需要做如下配置:

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末吠裆,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子烂完,更是在濱河造成了極大的恐慌试疙,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抠蚣,死亡現場離奇詭異祝旷,居然都是意外死亡,警方通過查閱死者的電腦和手機柱徙,發(fā)現死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進店門缓屠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奇昙,“玉大人护侮,你說我怎么就攤上這事〈⒛停” “怎么了羊初?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長什湘。 經常有香客問我长赞,道長,這世上最難降的妖魔是什么闽撤? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任得哆,我火速辦了婚禮,結果婚禮上哟旗,老公的妹妹穿的比我還像新娘贩据。我一直安慰自己栋操,他們只是感情好,可當我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布饱亮。 她就那樣靜靜地躺著矾芙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪近上。 梳的紋絲不亂的頭發(fā)上剔宪,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機與錄音壹无,去河邊找鬼葱绒。 笑死,一個胖子當著我的面吹牛斗锭,可吹牛的內容都是我干的哈街。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼拒迅,長吁一口氣:“原來是場噩夢啊……” “哼骚秦!你這毒婦竟也來了?” 一聲冷哼從身側響起璧微,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤作箍,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后前硫,有當地人在樹林里發(fā)現了一具尸體胞得,經...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年屹电,在試婚紗的時候發(fā)現自己被綠了阶剑。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡危号,死狀恐怖牧愁,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情外莲,我是刑警寧澤猪半,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站偷线,受9級特大地震影響磨确,放射性物質發(fā)生泄漏。R本人自食惡果不足惜声邦,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一乏奥、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧亥曹,春花似錦邓了、人聲如沸盏檐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽胡野。三九已至,卻和暖如春痕鳍,著一層夾襖步出監(jiān)牢的瞬間硫豆,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工笼呆, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留熊响,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓诗赌,卻偏偏與公主長得像汗茄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子铭若,可洞房花燭夜當晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內容