Springboot中使用websocket傳送資訊給指定使用者和群發
websocket是一種長連線協議,相較於傳統的http短連線,websocket不僅可以由客戶端向伺服器傳送訊息,可以主動向客戶端發起資訊,經常用於及時聊天,遊戲和伺服器向客戶端推送資訊。
主要優點:
1. 節約頻寬。 不停地輪詢服務端資料這種方式,使用的是http協議,head資訊很大,有效資料佔比低, 而使用WebSocket方式,頭資訊很小,有效資料佔比高。
2. 無浪費。 輪詢方式有可能輪詢10次,才碰到服務端資料更新,那麼前9次都白輪詢了,因為沒有拿到變化的資料。 而WebSocket是由伺服器主動回發,來的都是新資料。
3. 實時性,考慮到伺服器壓力,使用輪詢方式不可能很短的時間間隔,否則伺服器壓力太多,所以輪詢時間間隔都比較長,好幾秒,設定十幾秒。 而WebSocket是由伺服器主動推送過來,實時性是最高。
接下來我們看看怎麼在springboot中使用websocket和html頁面互動:
首先新建springboot專案,匯入需要的websocket和springboot依賴,pom.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.lk</groupId> <artifactId>websocketdemo</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>websocketdemo</name> <description>Demo project for Spring Boot</description> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
application.properties中配置tomcat埠,我配置的是80:
server.port=80
然後就可以開始配置websocket了,首先建立WebSocket配置檔案:WebSocketConfig.class
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
再建立WebSocket伺服器類:WebSocketServer.class,具體程式碼說明註釋都寫得很清楚了:
@Component
//訪問服務端的url地址
@ServerEndpoint(value = "/websocket/{id}")
public class WebSocketServer {
private static int onlineCount = 0;
private static ConcurrentHashMap<String, WebSocketServer> webSocketSet = new ConcurrentHashMap<>();
//與某個客戶端的連線會話,需要通過它來給客戶端傳送資料
private Session session;
private static Logger log = LogManager.getLogger(WebSocketServer.class);
private String id = "";
/**
* 連線建立成功呼叫的方法*/
@OnOpen
public void onOpen(@PathParam(value = "id") String id, Session session) {
this.session = session;
this.id = id;//接收到傳送訊息的人員編號
webSocketSet.put(id, this); //加入set中
addOnlineCount(); //線上數加1
log.info("使用者"+id+"加入!當前線上人數為" + getOnlineCount());
try {
sendMessage("連線成功");
} catch (IOException e) {
log.error("websocket IO異常");
}
}
/**
* 連線關閉呼叫的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //從set中刪除
subOnlineCount(); //線上數減1
log.info("有一連線關閉!當前線上人數為" + getOnlineCount());
}
/**
* 收到客戶端訊息後呼叫的方法
*
* @param message 客戶端傳送過來的訊息*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("來自客戶端的訊息:" + message);
//可以自己約定字串內容,比如 內容|0 表示資訊群發,內容|X 表示資訊發給id為X的使用者
String sendMessage = message.split("[|]")[0];
String sendUserId = message.split("[|]")[1];
try {
if(sendUserId.equals("0"))
sendtoAll(sendMessage);
else
sendtoUser(sendMessage,sendUserId);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("發生錯誤");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 傳送資訊給指定ID使用者,如果使用者不線上則返回不線上資訊給自己
* @param message
* @param sendUserId
* @throws IOException
*/
public void sendtoUser(String message,String sendUserId) throws IOException {
if (webSocketSet.get(sendUserId) != null) {
if(!id.equals(sendUserId))
webSocketSet.get(sendUserId).sendMessage( "使用者" + id + "發來訊息:" + " <br/> " + message);
else
webSocketSet.get(sendUserId).sendMessage(message);
} else {
//如果使用者不線上則返回不線上資訊給自己
sendtoUser("當前使用者不線上",id);
}
}
/**
* 傳送資訊給所有人
* @param message
* @throws IOException
*/
public void sendtoAll(String message) throws IOException {
for (String key : webSocketSet.keySet()) {
try {
webSocketSet.get(key).sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
這時我們的webSocket後端已經建立好了,接下來寫兩個前端程式碼:
<!DOCTYPE HTML>
<html>
<head>
<title>WebSocket</title>
</head>
<body>
Welcome<br/>
<input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
//判斷當前瀏覽器是否支援WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:80/websocket/2");
}
else{
alert('Not support websocket')
}
//連線發生錯誤的回撥方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//連線成功建立的回撥方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到訊息的回撥方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//連線關閉的回撥方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。
window.onbeforeunload = function(){
websocket.close();
}
//將訊息顯示在網頁上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//關閉連線
function closeWebSocket(){
websocket.close();
}
//傳送訊息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
<!DOCTYPE HTML>
<html>
<head>
<title>WebSocket</title>
</head>
<body>
Welcome<br/>
<input id="text" type="text" /><button onclick="send()">Send</button> <button onclick="closeWebSocket()">Close</button>
<div id="message">
</div>
</body>
<script type="text/javascript">
var websocket = null;
//判斷當前瀏覽器是否支援WebSocket
if('WebSocket' in window){
websocket = new WebSocket("ws://localhost:80/websocket/1");
}
else{
alert('Not support websocket')
}
//連線發生錯誤的回撥方法
websocket.onerror = function(){
setMessageInnerHTML("error");
};
//連線成功建立的回撥方法
websocket.onopen = function(event){
setMessageInnerHTML("open");
}
//接收到訊息的回撥方法
websocket.onmessage = function(event){
setMessageInnerHTML(event.data);
}
//連線關閉的回撥方法
websocket.onclose = function(){
setMessageInnerHTML("close");
}
//監聽視窗關閉事件,當視窗關閉時,主動去關閉websocket連線,防止連線還沒斷開就關閉視窗,server端會拋異常。
window.onbeforeunload = function(){
websocket.close();
}
//將訊息顯示在網頁上
function setMessageInnerHTML(innerHTML){
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//關閉連線
function closeWebSocket(){
websocket.close();
}
//傳送訊息
function send(){
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
兩個前端程式碼都是一樣的,不同的地方在於一個訪問是new WebSocket("ws://localhost:80/websocket/1");一個是new WebSocket("ws://localhost:80/websocket/2");即訪問id傳參不同,這時啟動springboot後臺服務,再開啟兩個前端html檔案:
根據之前在程式碼中定義的輸入格式:“資訊內容|接受者Id|”進行輸入,當輸入id為0時表示群發: