一粗梭、什么是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?+" ");
}
6.結果展示
后臺:如果有連接請求
前臺顯示:
總結
這中間我遇到一個問題,就是說WebSocket啟動的時候優(yōu)先于spring容器昭灵,從而導致在WebSocketServer中調用業(yè)務Service會報空指針異常
所以需要在WebSocketServer中將所需要用到的service給靜態(tài)初始化一下:如圖所示:
還需要做如下配置: