WebSocket 實現伺服器訊息推送客戶端
一、背景
專案需要做一個訊息能夠實時獲取的功能,系統日活躍量達到10000,產生的訊息是活躍量的數倍,如果採用 Http 的方式輪詢後端服務,會使得後端服務壓力過大而奔潰,因此需要一種新的技術方式來改變 “拉” 的方式。
二、解決方案
經過各種 Google、百度 後發現可以使用 html5 的新技術 WebSocket ,將現有 “拉”訊息的方式改變成 “推” 的模式,大大的減少伺服器壓力。
三、具體實現
例項採用 Spring Boot 框架,
引入 pom 依賴
org.springframework.boot
spring-boot-starter-websocket
org.springframework.boot
spring-boot-starter-undertow
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
WebSocket 服務可採用 websocket-api 或 spring-websocket 開發,我們採用 websocket-api 的註解開發方式:
package com.gridsum.techpub.systemhistory.api.server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* @author ouyangrongtao
* @version 1.0
* @description WebSocketServer
* @date 2019/12/23 10:16
**/
@ServerEndpoint("/websocket/{sid}")
@Service
public class WebSocketServer {
private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);
private ClientInfo clientInfo;
/**
* 存放每個客戶端對應的 ClientInfo 物件。
*/
private static final Set WEB_SOCKET_SET = new CopyOnWriteArraySet<>();
/**
* 連線建立成功呼叫的方法
*
* @param session 會話
* @param sid 客戶端
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
//加入set中
this.clientInfo = new ClientInfo(sid, session);
WEB_SOCKET_SET.add(clientInfo);
logger.info("有新視窗開始監聽:[{}],當前線上人數為[{}]", sid, WEB_SOCKET_SET.size());
try {
this.sendMessage(session, "連線成功");
} catch (IOException e) {
logger.error("websocket IO異常");
}
}
/**
* 連線關閉呼叫的方法
*/
@OnClose
public void onClose() {
//從set中刪除
WEB_SOCKET_SET.remove(this.clientInfo);
logger.info("有一連線關閉!當前線上人數為:[{}]", WEB_SOCKET_SET.size());
}
/**
*
* @param message 客戶端傳送過來的訊息
*/
@OnMessage
public void onMessage(String message) {
logger.info("收到來自視窗[{}]的資訊:[{}]", this.clientInfo.getSid(), message);
//發訊息
for (ClientInfo item : WEB_SOCKET_SET) {
try {
this.sendMessage(item.getSession(), message);
} catch (IOException ignored) {
}
}
}
/**
* 錯誤時呼叫
* @param session 會話
* @param error 錯誤資訊
*/
@OnError
public void onError(Session session, Throwable error) {
logger.error("發生錯誤", error);
}
/**
* 給 sid 傳送訊息
* @param message 訊息
* @param sid sid
*/
public void sendMessage(String message, String sid) {
logger.info("推送訊息到視窗[{}],推送內容:[{}]", sid, message);
ClientInfo client = WEB_SOCKET_SET.parallelStream()
.filter(item -> item.getSid().equals(sid)).findFirst().orElse(null);
if (client != null) {
try {
this.sendMessage(client.getSession(), message);
} catch (IOException ignored) {
}
}
}
/**
* 實現伺服器主動推送
* @param session session
* @param message message
* @throws IOException IOException
*/ 鄭州哪個婦科醫院好
private void sendMessage(Session session, String message) throws IOException {
session.getBasicRemote().sendText(message);
}
class ClientInfo {
/**
* 接收sid
*/
private String sid = "";
/**
* 客戶端
*/
private Session session;
public ClientInfo() { }
private ClientInfo(String sid, Session session) {
this.sid = sid;
this.session = session;
}
private String getSid() {
return sid;
}
private Session getSession() {
return session;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClientInfo that = (ClientInfo) o;
return Objects.equals(sid, that.sid);
}
@Override
public int hashCode() {
return Objects.hash(sid);
}
}
}
前端程式碼
執行 WebSocketClient1000001
來一個發訊息的介面
/**
* 傳送訊息給客戶端
* @author ouyangrongtao
*/
@RestController
public class WebSocketController {
private WebSocketServer webSocketServer;
@Autowired
public WebSocketController(WebSocketServer webSocketServer) {
this.webSocketServer = webSocketServer;
}
@PostMapping("/socket/push")
public boolean pushToWeb(@RequestBody Map content) {
webSocketServer.sendMessage(content.get("message"), content.get("cid"));
return true;
}
}
到此已經基本寫完。使用 Postman 呼叫發訊息的介面,發現客戶端可以收到傳送的訊息。
四、問題記錄
在做的時候,因為專案用的 Tomcat 容器,導致 Tomcat 相關包與 WebSocket 依賴有衝突,最終專案不能啟動,解決方式只需要將 Tomcat 容器改為 Undertow 。
org.springframework.boot
spring-boot-starter-undertow
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
異常資訊:
Caused by: java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available
at org.springframework.util.Assert.state(Assert.java:73)
at org.springframework.web.socket.server.standard.ServerEndpointExporter.afterPropertiesSet(ServerEndpointExporter.java:106)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1753)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1690)
... 16 common frames omitted
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69945560/viewspace-2670447/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Spring Boot 整合 WebSocket 實現服務端推送訊息到客戶端Spring BootWeb服務端客戶端
- springboot2整合websocket,實現服務端推送訊息到客戶端Spring BootWeb服務端客戶端
- Java實現QQ郵件傳送客戶端Java客戶端
- 如何實現從 Redis 中訂閱訊息轉發到 WebSocket 客戶端RedisWeb客戶端
- Flutter websocket 實現訊息推送FlutterWeb
- IM撤回訊息-iOS客戶端實現iOS客戶端
- WebSocket實現服務端推送訊息和聊天會話Web服務端會話
- SseEmitter 伺服器向客戶端推送訊息MIT伺服器客戶端
- 基於 Hyperf 實現 RabbitMQ + WebSocket 訊息推送MQWeb
- WebSocket實現前後端通訊Web後端
- Spring Boot+Socket實現與html頁面的長連線,客戶端給伺服器端發訊息,伺服器給客戶端輪詢傳送訊息,附案例原始碼Spring BootHTML客戶端伺服器原始碼
- WebWorker與WebSocket實現前端訊息匯流排Web前端
- 訊息的即時推送——net實現、websocket實現以及socket.io實現Web
- 使用 WebSocket 客戶端連線 MQTT 伺服器Web客戶端MQQT伺服器
- java netty 實現 websocket 服務端和客戶端雙向通訊 實現心跳和斷線重連 完整示例JavaNettyWeb服務端客戶端
- Spring Boot中使用WebSocket總結(三):使用訊息佇列實現分散式WebSocketSpring BootWeb佇列分散式
- 使用swoole作為MQTT客戶端並接收實現即時訊息推送MQQT客戶端
- java websocket 客戶端JavaWeb客戶端
- Golang 實現客戶端與伺服器端UDP協議連線通訊Golang客戶端伺服器UDP協議
- 微信雲託管 WebSocket 實戰:基於模版實現訊息推送Web
- 訊息中介軟體客戶端消費控制實踐客戶端
- 基於WebSocket的modbus通訊(二)- 客戶端Web客戶端
- 關於 WebSocket 和 HTTP 區別的思考以及一個最簡單的 WebSocket 的客戶端和伺服器實現WebHTTP客戶端伺服器
- websocket(多個客戶端)Web客戶端
- SpringBoot 實戰 (十六) | 整合 WebSocket 基於 STOMP 協議實現廣播訊息Spring BootWeb協議
- Redis 6.0 客戶端快取的伺服器端實現Redis客戶端快取伺服器
- 實現客戶端與服務端的HTTP通訊客戶端服務端HTTP
- angular + express 實現websocket通訊AngularExpressWeb
- 使用Java實現WebSocket通訊JavaWeb
- C/S(socket、執行緒 實現多個客戶端、伺服器端簡易通訊)執行緒客戶端伺服器
- 為什麼從伺服器與客戶端不能接收訊息NetMQ框架?伺服器客戶端MQ框架
- 基於WebSocket的實時訊息傳遞設計Web
- 實現服務端和客戶端的實時雙向資料傳輸-WebSocket簡單瞭解服務端客戶端Web
- WebRTC + WebSocket 實現視訊通話Web
- Spring Boot系列20 Spring Websocket實現向指定的使用者傳送訊息Spring BootWeb
- workerman 實現訊息推送
- Java網路程式設計:QQ郵件傳送客戶端程式設計Java程式設計客戶端
- im客戶端生成訊息序列號演算法客戶端演算法