適用于單客戶端,一個賬號登陸一個客戶端,登陸多個客戶端會報錯
The remote endpoint was in state [TEXT_FULL_WRITING]?
這是因為此時的session是不同的,只能鎖住一個session,解決此問題的方法把全局靜態(tài)對象鎖住,因為賬號是唯一的
http://t.csdn.cn/e6LjH
<!-- websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
新建配置類
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @Description 開啟springboot對websocket的支持
* @Author WangKun
* @Date 2023/8/14 17:21
* @Version
*/
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
@Configuration
public class WebSocketConfig{
/**
* @Description 注入一個ServerEndpointExporter, 會自動注冊使用@ServerEndpoint注解
* @param
* @Throws
* @Return org.springframework.web.socket.server.standard.ServerEndpointExporter
* @Date 2023-08-14 17:26:31
* @Author WangKun
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
這個注解需要打上聲明是開發(fā)環(huán)境,否則在tomcat部署中會報錯
新建服務(wù)類
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Description websocket服務(wù),不考慮分組
* @Author WangKun
* @Date 2023/8/14 17:29
* @Version
*/
@ConditionalOnClass(value = WebSocketConfig.class)
@ServerEndpoint("/websocket/{userId}")
@Component
@Slf4j
public class WebSocket {
//在線計數(shù)器
private static int onlineCount = 0;
//存放每個客戶端對應(yīng)的WebSocket對象。
private static final ConcurrentHashMap<String, WebSocket> WEB_SOCKET_MAP = new ConcurrentHashMap<>();
//與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)
private Session session;
private String userId;
/**
* @param
* @Description 在線數(shù)
* @Throws
* @Return int
* @Date 2023-08-14 17:47:19
* @Author WangKun
*/
public static synchronized int getOnlineCount() {
return onlineCount;
}
/**
* @param
* @Description 在線數(shù)加1
* @Throws
* @Return void
* @Date 2023-08-14 17:47:32
* @Author WangKun
*/
public static synchronized void addOnlineCount() {
WebSocket.onlineCount++;
}
/**
* @param
* @Description 在線數(shù)減1
* @Throws
* @Return void
* @Date 2023-08-14 17:47:47
* @Author WangKun
*/
public static synchronized void subOnlineCount() {
WebSocket.onlineCount--;
}
/**
* @param session
* @param userId
* @Description 建立連接
* @Throws
* @Return void
* @Date 2023-08-14 17:52:08
* @Author WangKun
*/
@OnOpen
public void onOpen(final Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
// 防止前端刷新重連用戶重復(fù),存在計數(shù)器不累加
if (WEB_SOCKET_MAP.containsKey(userId)) {
WEB_SOCKET_MAP.remove(userId);
WEB_SOCKET_MAP.put(userId, this);
} else {
WEB_SOCKET_MAP.put(userId, this);
addOnlineCount();
}
sendMessage(String.valueOf(ResponseCode.CONNECT_SUCCESS.getCode())); //自定義成功返回碼
log.info("用戶--->{} 連接成功,當(dāng)前在線人數(shù)為--->{}", userId, getOnlineCount());
}
/**
* @param message
* @Description 向客戶端發(fā)送消息 session.getBasicRemote()與session.getAsyncRemote()的區(qū)別
* @Throws
* @Return void
* @Date 2023-08-14 17:51:07
* @Author WangKun
*/
public void sendMessage(String message) {
try {
// 加鎖避免阻塞
synchronized (session){
this.session.getBasicRemote().sendText(message);
}
} catch (IOException e) {
log.error("向客戶端發(fā)送消息--->{}", e.getMessage(), e);
throw new RuntimeException(e);
}
}
/**
* @param
* @Description 關(guān)閉連接
* @Throws
* @Return void
* @Date 2023-08-14 17:52:30
* @Author WangKun
*/
@OnClose
public void onClose(Session session) {
if (!WEB_SOCKET_MAP.isEmpty() && WEB_SOCKET_MAP.containsKey(userId)) {
this.session = session;
WEB_SOCKET_MAP.remove(userId);
subOnlineCount();
log.info("用戶--->{} 關(guān)閉連接!當(dāng)前在線人數(shù)為--->{}", userId, getOnlineCount());
}
}
/**
* @param message
* @param session
* @Description 收到客戶端消息
* @Throws
* @Return void
* @Date 2023-08-15 10:54:55
* @Author WangKun
*/
@OnMessage
public void onMessage(String message, Session session) {
//這一塊可以操作數(shù)據(jù),比如存到數(shù)據(jù)
//判斷心跳是否存活, 防止心跳自動斷開,再重連
synchronized (session) {
if ("ping".equalsIgnoreCase(message) && !WEB_SOCKET_MAP.isEmpty() && WEB_SOCKET_MAP.containsKey(userId)) {
WEB_SOCKET_MAP.get(userId).sendMessage("pong");
}
log.info("收到來自客戶端用戶:{} 消息:--->{}", userId, message);
}
}
/**
* @param session
* @param error
* @Description 發(fā)生錯誤時
* @Throws
* @Return void
* @Date 2023-08-15 10:55:27
* @Author WangKun
*/
@OnError
public void onError(Session session, Throwable error) {
if (!WEB_SOCKET_MAP.isEmpty() && WEB_SOCKET_MAP.containsKey(userId)) {
WEB_SOCKET_MAP.remove(userId);
subOnlineCount();
log.error("用戶--->{} 錯誤!" + userId, "原因--->{}" + error.getMessage());
error.printStackTrace();
}
}
/**
* @param userId
* @param message
* @Description 通過userId向客戶端發(fā)送消息(指定用戶發(fā)送)
* @Throws
* @Return void
* @Date 2023-08-14 18:01:35
* @Author WangKun
*/
public static void sendTextMessageByUserId(String userId, String message) {
log.info("服務(wù)端發(fā)送消息到用戶{},消息:{}", userId, message);
if (!WEB_SOCKET_MAP.isEmpty() && StringUtils.isNotBlank(userId) && WEB_SOCKET_MAP.containsKey(userId)) {
WEB_SOCKET_MAP.get(userId).sendMessage(message);
} else {
log.error("用戶{}不在線", userId);
}
}
/**
* @param message
* @Description 群發(fā)自定義消息
* @Throws
* @Return void
* @Date 2023-08-14 18:03:38
* @Author WangKun
*/
public static void sendTextMessage(String message) {
// 如果在線一個就廣播
log.info("廣播數(shù)據(jù)到當(dāng)前在線人,人數(shù):{}", getOnlineCount());
if (getOnlineCount() > 0 && !WEB_SOCKET_MAP.isEmpty()) {
for (String item : WEB_SOCKET_MAP.keySet()) {
WEB_SOCKET_MAP.get(item).sendMessage(message);
log.info("服務(wù)端發(fā)送消息到用戶{},消息:{}", item, message);
}
}
}
}
@ConditionalOnClass(value = WebSocketConfig.class)文章來源:http://www.zghlxwxcb.cn/news/detail-674943.html
指定使用自定義配置文件文章來源地址http://www.zghlxwxcb.cn/news/detail-674943.html
到了這里,關(guān)于SpringBoot2.0集成WebSocket的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!