Spring Boot 集成 WebSocket
本章節(jié)將介紹 Spring Boot 集成 WebSocket 的兩種主要方式:原生注解與Spring封裝。
在線WebSocket測(cè)試工具
?? Spring Boot 2.x 實(shí)踐案例(代碼倉(cāng)庫(kù))
原生注解
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
配置文件
@Configuration
public class WebSocketConfiguration {
/**
* 注入ServerEndpointExporter,
* 這個(gè)bean會(huì)自動(dòng)注冊(cè)使用了@ServerEndpoint注解聲明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
處理消息
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")
public class WebSocket {
/**
* 線程安全的無(wú)序的集合
*/
private static final CopyOnWriteArraySet<Session> SESSIONS = new CopyOnWriteArraySet<>();
/**
* 存儲(chǔ)在線連接數(shù)
*/
private static final Map<String, Session> SESSION_POOL = new HashMap<>();
@OnOpen
public void onOpen(Session session, @PathParam(value = "userId") String userId) {
try {
SESSIONS.add(session);
SESSION_POOL.put(userId, session);
log.info("【W(wǎng)ebSocket消息】有新的連接,總數(shù)為:" + SESSIONS.size());
} catch (Exception e) {
e.printStackTrace();
}
}
@OnClose
public void onClose(Session session) {
try {
SESSIONS.remove(session);
log.info("【W(wǎng)ebSocket消息】連接斷開(kāi),總數(shù)為:" + SESSIONS.size());
} catch (Exception e) {
e.printStackTrace();
}
}
@OnMessage
public void onMessage(String message) {
log.info("【W(wǎng)ebSocket消息】收到客戶端消息:" + message);
}
/**
* 此為廣播消息
*
* @param message 消息
*/
public void sendAllMessage(String message) {
log.info("【W(wǎng)ebSocket消息】廣播消息:" + message);
for (Session session : SESSIONS) {
try {
if (session.isOpen()) {
session.getAsyncRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 此為單點(diǎn)消息
*
* @param userId 用戶編號(hào)
* @param message 消息
*/
public void sendOneMessage(String userId, String message) {
Session session = SESSION_POOL.get(userId);
if (session != null && session.isOpen()) {
try {
synchronized (session) {
log.info("【W(wǎng)ebSocket消息】單點(diǎn)消息:" + message);
session.getAsyncRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 此為單點(diǎn)消息(多人)
*
* @param userIds 用戶編號(hào)列表
* @param message 消息
*/
public void sendMoreMessage(String[] userIds, String message) {
for (String userId : userIds) {
Session session = SESSION_POOL.get(userId);
if (session != null && session.isOpen()) {
try {
log.info("【W(wǎng)ebSocket消息】單點(diǎn)消息:" + message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
- @ServerEndpoint:將目前的類定義成一個(gè)websocket服務(wù)器端,注解的值將被用于監(jiān)聽(tīng)用戶連接的終端訪問(wèn)URL地址,客戶端可以通過(guò)這個(gè)URL來(lái)連接到WebSocket服務(wù)器端
- @OnOpen:當(dāng)WebSocket建立連接成功后會(huì)觸發(fā)這個(gè)注解修飾的方法。
- @OnClose:當(dāng)WebSocket建立的連接斷開(kāi)后會(huì)觸發(fā)這個(gè)注解修飾的方法。
- @OnMessage:當(dāng)客戶端發(fā)送消息到服務(wù)端時(shí),會(huì)觸發(fā)這個(gè)注解修改的方法。
- @OnError:當(dāng)WebSocket建立連接時(shí)出現(xiàn)異常會(huì)觸發(fā)這個(gè)注解修飾的方法。
Spring封裝
引入依賴
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
自定義處理器
處理器作用類似于
@RequestMapping
注解,用于處理某一個(gè)路徑的WebSocket
連接,自定義處理器需要實(shí)現(xiàn)WebSocketHandler
接口。
WebSocket操作類
public interface WebSocket {
/**
* 會(huì)話開(kāi)始回調(diào)
*
* @param session 會(huì)話
*/
void handleOpen(WebSocketSession session);
/**
* 會(huì)話結(jié)束回調(diào)
*
* @param session 會(huì)話
*/
void handleClose(WebSocketSession session);
/**
* 處理消息
*
* @param session 會(huì)話
* @param message 接收的消息
*/
void handleMessage(WebSocketSession session, String message);
/**
* 發(fā)送消息
*
* @param session 當(dāng)前會(huì)話
* @param message 要發(fā)送的消息
* @throws IOException 發(fā)送io異常
*/
void sendMessage(WebSocketSession session, String message) throws IOException;
/**
* 發(fā)送消息
*
* @param userId 用戶id
* @param message 要發(fā)送的消息
* @throws IOException 發(fā)送io異常
*/
void sendMessage(String userId, TextMessage message) throws IOException;
/**
* 發(fā)送消息
*
* @param userId 用戶id
* @param message 要發(fā)送的消息
* @throws IOException 發(fā)送io異常
*/
void sendMessage(String userId, String message) throws IOException;
/**
* 發(fā)送消息
*
* @param session 當(dāng)前會(huì)話
* @param message 要發(fā)送的消息
* @throws IOException 發(fā)送io異常
*/
void sendMessage(WebSocketSession session, TextMessage message) throws IOException;
/**
* 廣播
*
* @param message 字符串消息
* @throws IOException 異常
*/
void broadCast(String message) throws IOException;
/**
* 廣播
*
* @param message 文本消息
* @throws IOException 異常
*/
void broadCast(TextMessage message) throws IOException;
/**
* 處理會(huì)話異常
*
* @param session 會(huì)話
* @param error 異常
*/
void handleError(WebSocketSession session, Throwable error);
/**
* 獲得所有的 websocket 會(huì)話
*
* @return 所有 websocket 會(huì)話
*/
Set<WebSocketSession> getSessions();
/**
* 得到當(dāng)前連接數(shù)
*
* @return 連接數(shù)
*/
int getConnectionCount();
}
WebSocket操作實(shí)現(xiàn)類
@Slf4j
public class WebSocketImpl implements WebSocket {
/**
* 在線連接數(shù)(線程安全)
*/
private final AtomicInteger connectionCount = new AtomicInteger(0);
/**
* 線程安全的無(wú)序集合(存儲(chǔ)會(huì)話)
*/
private final CopyOnWriteArraySet<WebSocketSession> sessions = new CopyOnWriteArraySet<>();
@Override
public void handleOpen(WebSocketSession session) {
sessions.add(session);
int count = connectionCount.incrementAndGet();
log.info("a new connection opened,current online count:{}", count);
}
@Override
public void handleClose(WebSocketSession session) {
sessions.remove(session);
int count = connectionCount.decrementAndGet();
log.info("a new connection closed,current online count:{}", count);
}
@Override
public void handleMessage(WebSocketSession session, String message) {
// 只處理前端傳來(lái)的文本消息,并且直接丟棄了客戶端傳來(lái)的消息
log.info("received a message:{}", message);
}
@Override
public void sendMessage(WebSocketSession session, String message) throws IOException {
this.sendMessage(session, new TextMessage(message));
}
@Override
public void sendMessage(String userId, TextMessage message) throws IOException {
Optional<WebSocketSession> userSession = sessions.stream().filter(session -> {
if (!session.isOpen()) {
return false;
}
Map<String, Object> attributes = session.getAttributes();
if (!attributes.containsKey("uid") {
return false;
}
String uid = (String) attributes.get("uid");
return uid.equals(userId);
}).findFirst();
if (userSession.isPresent()) {
userSession.get().sendMessage(message);
}
}
@Override
public void sendMessage(String userId, String message) throws IOException {
this.sendMessage(userId, new TextMessage(message));
}
@Override
public void sendMessage(WebSocketSession session, TextMessage message) throws IOException {
session.sendMessage(message);
}
@Override
public void broadCast(String message) throws IOException {
for (WebSocketSession session : sessions) {
if (!session.isOpen()) {
continue;
}
this.sendMessage(session, message);
}
}
@Override
public void broadCast(TextMessage message) throws IOException {
for (WebSocketSession session : sessions) {
if (!session.isOpen()) {
continue;
}
session.sendMessage(message);
}
}
@Override
public void handleError(WebSocketSession session, Throwable error) {
log.error("websocket error:{},session id:{}", error.getMessage(), session.getId());
log.error("", error);
}
@Override
public Set<WebSocketSession> getSessions() {
return sessions;
}
@Override
public int getConnectionCount() {
return connectionCount.get();
}
}
自定義WebSocket處理器
public class DefaultWebSocketHandler implements WebSocketHandler {
@Autowired
private WebSocket webSocket;
/**
* 建立連接
*
* @param session Session
*/
@Override
public void afterConnectionEstablished(@NonNull WebSocketSession session) {
webSocket.handleOpen(session);
}
/**
* 接收消息
*
* @param session Session
* @param message 消息
*/
@Override
public void handleMessage(@NonNull WebSocketSession session, @NonNull WebSocketMessage<?> message) {
if (message instanceof TextMessage) {
TextMessage textMessage = (TextMessage) message;
webSocket.handleMessage(session, textMessage.getPayload());
}
}
/**
* 發(fā)生錯(cuò)誤
*
* @param session Session
* @param exception 異常
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) {
webSocket.handleError(session, exception);
}
/**
* 關(guān)閉連接
*
* @param session Session
* @param closeStatus 關(guān)閉狀態(tài)
*/
@Override
public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus closeStatus) {
webSocket.handleClose(session);
}
/**
* 是否支持發(fā)送部分消息
*
* @return false
*/
@Override
public boolean supportsPartialMessages() {
return false;
}
}
自定義攔截器
自定義處理器需要實(shí)現(xiàn)
HandshakeInterceptor
接口
public class WebSocketInterceptor implements HandshakeInterceptor {
@Override
public boolean beforeHandshake(@NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response, @NonNull WebSocketHandler wsHandler, @NonNull Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
// 模擬用戶(通常利用JWT令牌解析用戶信息)
String userId = servletServerHttpRequest.getServletRequest().getParameter("uid");
// TODO 判斷用戶是否存在
attributes.put("uid", userId);
return true;
}
return false;
}
@Override
public void afterHandshake(@NonNull ServerHttpRequest request, @NonNull ServerHttpResponse response, @NonNull WebSocketHandler wsHandler, Exception exception) {
}
}
WebSocket 無(wú)法使用 header 傳遞參數(shù),因此這里使用 url params 攜帶參數(shù)。
WebSocket配置項(xiàng)
將自定義處理器、攔截器以及WebSocket操作類依次注入到IOC容器中。
@Configuration
@EnableWebSocket
public class WebSocketConfiguration implements WebSocketConfigurer {
@Bean
public DefaultWebSocketHandler defaultWebSocketHandler() {
return new DefaultWebSocketHandler();
}
@Bean
public WebSocket webSocket() {
return new WebSocketImpl();
}
@Bean
public WebSocketInterceptor webSocketInterceptor() {
return new WebSocketInterceptor();
}
@Override
public void registerWebSocketHandlers(@NonNull WebSocketHandlerRegistry registry) {
registry.addHandler(defaultWebSocketHandler(), "ws/message")
.addInterceptors(webSocketInterceptor())
.setAllowedOrigins("*");
}
}
- @EnableWebSocket:開(kāi)啟WebSocket功能
- addHandler:添加處理器
- addInterceptors:添加攔截器
- setAllowedOrigins:設(shè)置允許跨域(允許所有請(qǐng)求來(lái)源)
WebSocket測(cè)試
原生注解
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-781073.html
Spring封裝
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-781073.html
到了這里,關(guān)于Spring Boot 集成 WebSocket(原生注解與Spring封裝)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!