最近在做一個給公司大屏上飄彈幕的功能梅屉,彈幕里是人的照片 + 一句話值纱,比如:【照片】歡迎某總前來畫餅!熱烈歡迎E魈馈E斑搿!
因為要實時更新惰聂,就想到用WebSocket了凿滤,簡單來個demo復(fù)習(xí)一下。
1.什么是WebSocket庶近?
WebSocket是一種在單個TCP連接上進(jìn)行全雙工通信的協(xié)議翁脆,瀏覽器和服務(wù)器只需要完成一次握手,就直接可以創(chuàng)建持久性的連接鼻种,進(jìn)行雙向數(shù)據(jù)傳輸反番。
2.它可以做什么?
場景很多叉钥,比如:發(fā)通告罢缸、聊天、待辦待閱投队,還有我們要做的彈幕等等枫疆。
4.demo
- maven依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 配置類
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* ServerEndpointExporter會自動注冊使用了@ServerEndpoint聲明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
- 操作類
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")
public class WebSocket {
//客戶端會話
private Session session;
private String userId;
private static CopyOnWriteArraySet<WebSocket> webSockets =new CopyOnWriteArraySet<>();
private static ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<String,Session>();
/**
* 連接成功
*/
@OnOpen
public void onOpen(Session session, @PathParam(value="userId")String userId) {
try {
this.session = session;
this.userId = userId;
webSockets.add(this);
sessionPool.put(userId, session);
log.info("【websocket消息】有新的連接,總數(shù)為:"+webSockets.size());
} catch (Exception e) {
}
}
/**
* 連接關(guān)閉
*/
@OnClose
public void onClose() {
try {
webSockets.remove(this);
sessionPool.remove(this.userId);
log.info("【websocket消息】連接斷開敷鸦,總數(shù)為:"+webSockets.size());
} catch (Exception e) {
}
}
/**
* 收到客戶端消息
*/
@OnMessage
public void onMessage(String message) {
log.info("【websocket消息】收到客戶端消息:"+message);
}
/*
* 發(fā)送錯誤
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用戶錯誤,原因:"+error.getMessage());
error.printStackTrace();
}
/*
* 廣播
*/
public void sendAllMessage(String message) {
log.info("【websocket消息】廣播消息:"+message);
for(WebSocket webSocket : webSockets) {
try {
if(webSocket.session.isOpen()) {
webSocket.session.getAsyncRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
* 單獨發(fā)送
*/
public void sendOneMessage(String userId, String message) {
Session session = sessionPool.get(userId);
if (session != null&&session.isOpen()) {
try {
log.info("【websocket消息】 單點消息:"+message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
/*
* 發(fā)送多人
*/
public void sendMoreMessage(String[] userIds, String message) {
for(String userId:userIds) {
Session session = sessionPool.get(userId);
if (session != null&&session.isOpen()) {
try {
log.info("【websocket消息】 單點消息:"+message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
5.測試
可以用ApiPost這個工具來測試息楔,給服務(wù)端發(fā)送消息試試寝贡。要測試服務(wù)端往客戶端推送消息的話可以用接口或者定時任務(wù)。
image-20230608184544711.png
image-20230608184333814.png
image-20230608184620018.png
6.問題
IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
原因同一個session發(fā)送消息產(chǎn)生沖突(就是說值依,同一時刻圃泡,多個線程向一個socket寫數(shù)據(jù)發(fā)生沖突了),就會出現(xiàn) TEXT_FULL_WRITING
異常愿险,可以使用 getBasicRemote() 取代 getAsyncRemote() 颇蜡,并對業(yè)務(wù)報錯處加鎖,確保數(shù)據(jù)傳輸?shù)耐健?/p>
synchronized(this){
session.getBasicRemote().sendText(message);
}