WebSocket 服务实现:Spring Boot 示例
在现代应用程序中,WebSocket 是实现双向实时通信的重要技术。本文将介绍如何使用 Spring Boot 创建一个简单的 WebSocket 服务,并提供相关的代码示例。
1. WebSocket 简介
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 请求-响应模式相比,WebSocket 允许服务器主动向客户端推送消息,适用于实时应用,如在线聊天、实时通知和游戏等。
2. 项目结构
在开始之前,确保你的项目中包含必要的依赖。在 pom.xml
中添加以下依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId>
</dependency>
3. WebSocket 配置类
为了启用 WebSocket 支持,我们需要创建一个配置类 WebSocketConfig
,如下所示:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;/*** 开启WebSocket支持* * @author terrfly* @site https://www.jeequan.com* @date 2021/6/22 12:57*/
@Configuration
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
3.1 配置类解析
- @Configuration: 表示该类是一个配置类,Spring 会在启动时加载它。
- ServerEndpointExporter: 这个 Bean 会自动注册所有带有
@ServerEndpoint
注解的 WebSocket 端点。
4. WebSocket 服务类
以下是一个简单的 WebSocket 服务类 WsChannelUserIdServer
的实现。该类负责处理客户端的连接、消息接收和发送。
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;@ServerEndpoint("/api/anon/ws/channelUserId/{appId}/{cid}")
@Component
public class WsChannelUserIdServer {private final static Logger logger = LoggerFactory.getLogger(WsChannelUserIdServer.class);private static int onlineClientSize = 0;private static Map<String, Set<WsChannelUserIdServer>> wsAppIdMap = new ConcurrentHashMap<>();private Session session;private String cid = "";private String appId = "";@OnOpenpublic void onOpen(Session session, @PathParam("appId") String appId, @PathParam("cid") String cid) {try {this.cid = cid;this.appId = appId;this.session = session;Set<WsChannelUserIdServer> wsServerSet = wsAppIdMap.get(appId);if (wsServerSet == null) {wsServerSet = new CopyOnWriteArraySet<>();}wsServerSet.add(this);wsAppIdMap.put(appId, wsServerSet);addOnlineCount();logger.info("cid[{}], appId[{}] 连接开启监听!当前在线人数为 {}", cid, appId, onlineClientSize);} catch (Exception e) {logger.error("ws监听异常 cid[{}], appId[{}]", cid, appId, e);}}@OnClosepublic void onClose() {Set<WsChannelUserIdServer> wsSet = wsAppIdMap.get(this.appId);wsSet.remove(this);if (wsSet.isEmpty()) {wsAppIdMap.remove(this.appId);}subOnlineCount();logger.info("cid[{}], appId[{}] 连接关闭!当前在线人数为 {}", cid, appId, onlineClientSize);}@OnErrorpublic void onError(Session session, Throwable error) {logger.error("ws发生错误", error);}public void sendMessage(String message) throws IOException {this.session.getBasicRemote().sendText(message);}public static void sendMsgByAppAndCid(String appId, String cid, String msg) {try {logger.info("推送ws消息到浏览器, appId={}, cid={}, msg={}", appId, cid, msg);Set<WsChannelUserIdServer> wsSet = wsAppIdMap.get(appId);if (wsSet == null || wsSet.isEmpty()) {logger.info("appId[{}] 无ws监听客户端", appId);return;}for (WsChannelUserIdServer item : wsSet) {if (!cid.equals(item.cid)) {continue;}try {item.sendMessage(msg);} catch (Exception e) {logger.info("推送设备消息时异常,appId={}, cid={}", appId, item.cid, e);}}} catch (Exception e) {logger.info("推送消息时异常,appId={}", appId, e);}}public static synchronized int getOnlineClientSize() {return onlineClientSize;}public static synchronized void addOnlineCount() {onlineClientSize++;}public static synchronized void subOnlineCount() {onlineClientSize--;}
}
5. 代码解析
5.1 连接管理
- @OnOpen: 当客户端连接成功时调用此方法。可以在此方法中获取客户端的
appId
和cid
,并将当前连接的会话存储到wsAppIdMap
中。 - @OnClose: 当客户端连接关闭时调用此方法,从
wsAppIdMap
中移除该连接。 - @OnError: 处理连接错误。
5.2 消息发送
sendMessage(String message)
: 通过当前会话向客户端发送消息。sendMsgByAppAndCid(String appId, String cid, String msg)
: 根据appId
和cid
向特定客户端推送消息。
5.3 在线人数管理
使用 onlineClientSize
变量记录当前在线的客户端数量,并提供相应的增减方法。
好的!下面我将为你提供一个简单的前端实现示例,使用 Vue.js 和 React 来连接我们之前创建的 WebSocket 服务。这样,你可以看到如何在前端与后端进行实时通信。
6. Vue.js 前端实现
6.1 安装 Vue.js
如果你还没有创建 Vue 项目,可以使用 Vue CLI 创建一个新的项目:
npm install -g @vue/cli
vue create websocket-demo
cd websocket-demo
6.2 创建 WebSocket 组件
在 src/components
目录下创建一个名为 WebSocketComponent.vue
的文件,并添加以下代码:
<template><div><h1>WebSocket Demo</h1><input v-model="message" placeholder="Type a message" /><button @click="sendMessage">Send</button><div><h2>Messages:</h2><ul><li v-for="(msg, index) in messages" :key="index">{{ msg }}</li></ul></div></div>
</template><script>
export default {data() {return {socket: null,message: '',messages: [],appId: 'yourAppId', // 替换为你的 appIdcid: 'yourClientId' // 替换为你的客户端自定义ID};},created() {this.connect();},methods: {connect() {this.socket = new WebSocket(`ws://localhost:8080/api/anon/ws/channelUserId/${this.appId}/${this.cid}`);this.socket.onopen = () => {console.log('WebSocket connection established.');};this.socket.onmessage = (event) => {const data = JSON.parse(event.data);this.messages.push(data.message);};this.socket.onclose = () => {console.log('WebSocket connection closed.');};this.socket.onerror = (error) => {console.error('WebSocket error:', error);};},sendMessage() {if (this.socket && this.socket.readyState === WebSocket.OPEN) {this.socket.send(JSON.stringify({ message: this.message }));this.message = ''; // 清空输入框} else {console.error('WebSocket is not open.');}}}
};
</script><style scoped>
/* 添加样式 */
</style>
6.3 使用组件
在 src/App.vue
中使用这个组件:
<template><div id="app"><WebSocketComponent /></div>
</template><script>
import WebSocketComponent from './components/WebSocketComponent.vue';export default {components: {WebSocketComponent}
};
</script><style>
/* 添加样式 */
</style>
6.4 运行 Vue 应用
在项目根目录下运行以下命令启动 Vue 应用:
npm run serve
7. React 前端实现
7.1 安装 React
如果你还没有创建 React 项目,可以使用 Create React App 创建一个新的项目:
npx create-react-app websocket-demo
cd websocket-demo
7.2 创建 WebSocket 组件
在 src
目录下创建一个名为 WebSocketComponent.js
的文件,并添加以下代码:
import React, { useEffect, useState } from 'react';const WebSocketComponent = () => {const [socket, setSocket] = useState(null);const [message, setMessage] = useState('');const [messages, setMessages] = useState([]);const appId = 'yourAppId'; // 替换为你的 appIdconst cid = 'yourClientId'; // 替换为你的客户端自定义IDuseEffect(() => {const ws = new WebSocket(`ws://localhost:8080/api/anon/ws/channelUserId/${appId}/${cid}`);setSocket(ws);ws.onopen = () => {console.log('WebSocket connection established.');};ws.onmessage = (event) => {const data = JSON.parse(event.data);setMessages((prevMessages) => [...prevMessages, data.message]);};ws.onclose = () => {console.log('WebSocket connection closed.');};ws.onerror = (error) => {console.error('WebSocket error:', error);};return () => {ws.close();};}, [appId, cid]);const sendMessage = () => {if (socket && socket.readyState === WebSocket.OPEN) {socket.send(JSON.stringify({ message }));setMessage(''); // 清空输入框} else {console.error('WebSocket is not open.');}};return (<div><h1>WebSocket Demo</h1><inputvalue={message}onChange={(e) => setMessage(e.target.value)}placeholder="Type a message"/><button onClick={sendMessage}>Send</button><div><h2>Messages:</h2><ul>{messages.map((msg, index) => (<li key={index}>{msg}</li>))}</ul></div></div>);
};export default WebSocketComponent;
7.3 使用组件
在 src/App.js
中使用这个组件:
import React from 'react';
import WebSocketComponent from './WebSocketComponent';function App() {return (<div className="App"><WebSocketComponent /></div>);
}export default App;
7.4 运行 React 应用
在项目根目录下运行以下命令启动 React 应用:
npm start
8. 总结
通过以上步骤,我们实现了一个简单的 WebSocket 前端示例,分别使用了 Vue.js 和 React。用户可以通过输入框发送消息,接收来自 WebSocket 服务器的消息。
8.1 注意事项
- 确保 WebSocket 服务器正在运行,并且前端应用能够访问到它。
- 替换
yourAppId
和yourClientId
为实际的应用 ID 和客户端 ID。
希望这能帮助你更好地理解如何在前端实现 WebSocket 通信!如有任何问题,请随时询问。