WebSocket 服务实现:Spring Boot 示例
在现代应用程序中,WebSocket 是实现双向实时通信的重要技术。本文将介绍如何使用 Spring Boot 创建一个简单的 WebSocket 服务,并提供相关的代码示例。
1. WebSocket 简介
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。与传统的 HTTP 请求-响应模式相比,WebSocket 允许服务器主动向客户端推送消息,适用于实时应用,如在线聊天、实时通知和游戏等。
2. 项目结构
在开始之前,确保你的项目中包含必要的依赖。在 pom.xml
中添加以下依赖:
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
,如下所示:
java
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 {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3.1 配置类解析
- @Configuration: 表示该类是一个配置类,Spring 会在启动时加载它。
- ServerEndpointExporter : 这个 Bean 会自动注册所有带有
@ServerEndpoint
注解的 WebSocket 端点。
4. WebSocket 服务类
以下是一个简单的 WebSocket 服务类 WsChannelUserIdServer
的实现。该类负责处理客户端的连接、消息接收和发送。
java
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 = "";
@OnOpen
public 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);
}
}
@OnClose
public 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);
}
@OnError
public 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 创建一个新的项目:
bash
npm install -g @vue/cli
vue create websocket-demo
cd websocket-demo
6.2 创建 WebSocket 组件
在 src/components
目录下创建一个名为 WebSocketComponent.vue
的文件,并添加以下代码:
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', // 替换为你的 appId
cid: '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
中使用这个组件:
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 应用:
bash
npm run serve
7. React 前端实现
7.1 安装 React
如果你还没有创建 React 项目,可以使用 Create React App 创建一个新的项目:
bash
npx create-react-app websocket-demo
cd websocket-demo
7.2 创建 WebSocket 组件
在 src
目录下创建一个名为 WebSocketComponent.js
的文件,并添加以下代码:
javascript
import React, { useEffect, useState } from 'react';
const WebSocketComponent = () => {
const [socket, setSocket] = useState(null);
const [message, setMessage] = useState('');
const [messages, setMessages] = useState([]);
const appId = 'yourAppId'; // 替换为你的 appId
const cid = 'yourClientId'; // 替换为你的客户端自定义ID
useEffect(() => {
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>
<input
value={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
中使用这个组件:
javascript
import React from 'react';
import WebSocketComponent from './WebSocketComponent';
function App() {
return (
<div className="App">
<WebSocketComponent />
</div>
);
}
export default App;
7.4 运行 React 应用
在项目根目录下运行以下命令启动 React 应用:
bash
npm start
8. 总结
通过以上步骤,我们实现了一个简单的 WebSocket 前端示例,分别使用了 Vue.js 和 React。用户可以通过输入框发送消息,接收来自 WebSocket 服务器的消息。
8.1 注意事项
- 确保 WebSocket 服务器正在运行,并且前端应用能够访问到它。
- 替换
yourAppId
和yourClientId
为实际的应用 ID 和客户端 ID。
希望这能帮助你更好地理解如何在前端实现 WebSocket 通信!如有任何问题,请随时询问。