一、基本概念
WebSocket 是一種網絡通信協議,如果服務器有連續的狀態變化,客戶端要獲知就非常麻煩。大多數 Web 應用程序將通過頻繁的異步請求實現長輪詢。輪詢的效率低,非常浪費資源(因爲必須不停連接,或者 HTTP 連接始終打開)所以這裏使用WebSocket 通過登錄後跳轉到首頁,向後臺WebSocket 建立長鏈接來達到"即使通訊",隨着用戶頁面打開或關閉後臺羣發消息來實時更改頁面顯示的人數,當然這裏目前不涉及登錄後的上線下線以及帳號登錄擠掉功能,如果需要可以通過發送消息來改變。
二、SpringBoot 後臺實現WebSocket
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
WebSocketConfig.java
package com.kero99.socket;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 開啓WebSocket支持
* @author ygc
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebSocketController.java
package com.kero99.socket;
import java.io.IOException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
* WebSocket服務器端推送消息示例Controller
*
* @author ygc
*
*/
@RestController
@RequestMapping("/scoket")
public class WebSocketController {
// @Autowired
// private RedisOperator redisOperator;
@RequestMapping(value="/sendAll", method=RequestMethod.GET)
/**
* 羣發消息內容
* @param message
* @return
*/
String sendAllMessage(@RequestParam(required=true) String message){
try {
WebSocketServer.BroadCastInfo(message);
} catch (IOException e) {
e.printStackTrace();
}
return "ok";
}
@RequestMapping(value="/sendOne", method=RequestMethod.GET)
/**
* 指定會話ID發消息
* @param message 消息內容
* @param id 連接會話ID
* @return
*/
String sendOneMessage(@RequestParam(required=true) String message,@RequestParam(required=true) String id){
try {
WebSocketServer.SendMessage(id,message);
} catch (IOException e) {
e.printStackTrace();
}
return "ok";
}
}
WebSocketServer.java
package com.kero99.socket;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* WebSocket服務端
* @author ygc
*/
@ServerEndpoint(value = "/websocket")
@Component
public class WebSocketServer {
private final static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
private static final AtomicInteger OnlineCount = new AtomicInteger(0);
// concurrent包的線程安全Set,用來存放每個客戶端對應的Session對象。
private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<Session>();
/**
* 連接建立成功調用的方法
* @throws IOException
*/
@OnOpen
public void onOpen(Session session) throws IOException {
SessionSet.add(session);
int personCount = OnlineCount.incrementAndGet(); // 在線數加1
System.out.println("有連接加入,當前連接數爲:"+personCount);
log.info("有連接加入,當前連接數爲:{}", personCount);
// SendMessage(session, "連接成功,當前連接人數爲:"+personCount);
// SendMessage(session,String.valueOf(personCount));
BroadCastInfo(String.valueOf(OnlineCount.get()));
}
/**
* 連接關閉調用的方法
* @throws IOException
*/
@OnClose
public void onClose(Session session) throws IOException {
int personCount = OnlineCount.decrementAndGet();
System.out.println("有連接關閉,當前連接數爲:"+personCount);
log.info("有連接關閉,當前連接數爲:{}", personCount);
SessionSet.remove(session);
}
/**
* 收到客戶端消息後調用的方法
*
* @param message
* 客戶端發送過來的消息
* @throws IOException
*/
@OnMessage
public void onMessage(String message, Session session) throws IOException {
log.info("來自客戶端的消息:{}",message);
// System.out.println("來自客戶端的消息:"+message);
// SendMessage(session, "收到消息,消息內容:"+message);
if(message.equals("管理平臺")) {
System.out.println("收到平臺類型:"+message);
}
// if(message.equals("新增人數")) {
// System.out.println("打開頁面:"+message);
// BroadCastInfo(String.valueOf(OnlineCount.get()+1));
// }
if(message.equals("關閉頁面")) {
System.out.println("收到關閉頁面:"+message);
//在線數加-1
BroadCastInfo(String.valueOf(OnlineCount.get()-1));
}
}
/**
* 出現錯誤
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("發生錯誤:{},Session ID: {}",error.getMessage(),session.getId());
System.out.println("發生錯誤:{},Session ID: "+error.getMessage()+session.getId());
error.printStackTrace();
}
/**
* 發送消息,實踐表明,每次瀏覽器刷新,session會發生變化。
* @param session
* @param message
*/
public static void SendMessage(Session session, String message) {
try {
session.getBasicRemote().sendText(message);
// session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId()));
} catch (IOException e) {
log.error("發送消息出錯:{}", e.getMessage());
System.out.println("發送消息出錯:{}"+e.getMessage());
e.printStackTrace();
}
}
/**
* 羣發消息
* @param message
* @throws IOException
*/
public static void BroadCastInfo(String message) throws IOException {
for (Session session : SessionSet) {
if(session.isOpen()){
SendMessage(session, message);
}
}
}
/**
* 指定Session發送消息
* @param sessionId
* @param message
* @throws IOException
*/
public static void SendMessage(String sessionId,String message) throws IOException {
Session session = null;
for (Session s : SessionSet) {
if(s.getId().equals(sessionId)){
session = s;
break;
}
}
if(session!=null){
SendMessage(session, message);
}
else{
log.warn("沒有找到你指定ID的會話:{}",sessionId);
System.out.println("沒有找到你指定ID的會話:"+sessionId);
}
}
}
三、React+Umi+Antd 實現前端 WebSocket 通訊
js 需要安裝 socket.io
命令: npm socket.io 或者 yarn add socket.io
componentDidMount() {
let ws = new WebSocket("ws://localhost:12935/20191108_V1.0_xdnx/websocket");
if (typeof (WebSocket) == "undefined") {
console.log("遺憾:您的瀏覽器不支持WebSocket");
} else {
console.log("恭喜:您的瀏覽器支持WebSocket");
ws.onopen = (evt)=> {
console.log("Connection open ...");
ws.send("管理平臺");
ws.send("新增人數");
};
ws.onmessage = (evt)=> {
console.log( "Received Message: " + evt.data);
// alert(evt.data)
//this.state.messageData 爲接受數據的變量
let messageData=this.state.messageData;
this.setState({
messageData:evt.data
})
// ws.close();
};
ws.onclose = (evt)=> {
// alert(evt.data)
console.log("Connection closed.");
// ws.close();
};
ws.onerror = (evt)=> {
console.log("error")
};
window.onbeforeunload = (event)=> {
console.log("關閉WebSocket連接!");
ws.send("關閉頁面");
event.close();
}
}
}
render html
這裏用的antd的統計數值控件
<div style={{
background: '#ececec',
padding: '10px',
width:'20%',
float:'left',marginTop:'20px'
}}>
<Row gutter={16}>
<Col span={12}>
<Card>
<Statistic
title="管理平臺當前在線人數"
value={this.state.messageData}
precision={0}
valueStyle={{ color: '#3f8600' }}
suffix="人"
/>
</Card>
</Col>
</Row>
</div>