本功能参考SpringBoot 整合 Socket 实战案例
1. 基本功能搭建
1.1 引入依赖
xml
<!-- Socket.io -->
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
1.2 添加yml配置
根据需求进行调整
yaml
socketio:
# SocketIO服务器的host
host: localhost
# SocketIO服务器的端口
port: 8503
# SocketIO服务器允许的最大帧负载长度
maxFramePayloadLength: 1048576
# SocketIO服务器的最大http内容长度
maxHttpContentLength: 1048576
# SocketIO服务器的线程数量
bossCount: 1
# SocketIO服务器的工作线程数量
workCount: 100
# SocketIO服务器的允许自定义请求
allowCustomRequests: true
# SocketIO服务器的升级超时时间
upgradeTimeout: 10000
# SocketIO服务器的ping超时时间
pingTimeout: 60000
# SocketIO服务器的ping间隔时间
pingInterval: 25000
1.3 创建配置类
java
package com.tool.tooladmin.webSocket.config;
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author yhc
* @description SocketServerConfig
*/
@Configuration
public class SocketServerConfig {
/**
* SocketIO服务器的host
*/
@Value("${socketio.host}")
private String host;
/**
* SocketIO服务器的端口
*/
@Value("${socketio.port}")
private Integer port;
/**
* SocketIO服务器的boss线程数
*/
@Value("${socketio.bossCount}")
private int bossCount;
/**
* SocketIO服务器的work线程数
*/
@Value("${socketio.workCount}")
private int workCount;
/**
* 是否允许自定义请求
*/
@Value("${socketio.allowCustomRequests}")
private boolean allowCustomRequests;
/**
* SocketIO服务器的升级超时时间
*/
@Value("${socketio.upgradeTimeout}")
private int upgradeTimeout;
/**
* SocketIO服务器的ping超时时间
*/
@Value("${socketio.pingTimeout}")
private int pingTimeout;
/**
* SocketIO服务器的ping间隔时间
*/
@Value("${socketio.pingInterval}")
private int pingInterval;
/**
* 创建一个SocketIOServer实例,并设置相关配置。
*/
@Bean
public SocketIOServer socketIOServer() {
SocketConfig socketConfig = new SocketConfig();
socketConfig.setTcpNoDelay(true);
socketConfig.setSoLinger(0);
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
buildSocketConfig(socketConfig, config);
return new SocketIOServer(config);
}
/**
* 创建一个SpringAnnotationScanner实例,用于扫描netty-socketIo的注解( @OnConnect、@OnEvent等)
*/
@Bean
public SpringAnnotationScanner springAnnotationScanner() {
return new SpringAnnotationScanner(socketIOServer());
}
/**
* 设置SocketIO服务器的配置,将SocketConfig配置项设置到com.corundumstudio.socketio.Configuration中
* @param socketConfig
* @param config
*/
private void buildSocketConfig(SocketConfig socketConfig, com.corundumstudio.socketio.Configuration config) {
config.setSocketConfig(socketConfig);
config.setHostname(host);
config.setPort(port);
config.setBossThreads(bossCount);
config.setWorkerThreads(workCount);
config.setAllowCustomRequests(allowCustomRequests);
config.setUpgradeTimeout(upgradeTimeout);
config.setPingTimeout(pingTimeout);
config.setPingInterval(pingInterval);
}
}
1.4 创建消息实体类
java
package com.tool.tooladmin.webSocket.domain;
import lombok.Data;
@Data
public class SocketMessageVo {
/** 消息类型 */
private String type;
/** 消息内容 */
private String content;
/** 消息发送者 */
private String from;
/** 消息接收者 */
private String to;
/** 消息通道 */
private String channel;
}
1.5 封装socket常用函数
java
package com.tool.tooladmin.webSocket;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.annotation.OnEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@Component
public class SocketUtil {
private final Logger log = LoggerFactory.getLogger(this.getClass());
//暂且把用户&客户端信息存在缓存
public static ConcurrentMap<String, SocketIOClient> connectMap = new ConcurrentHashMap<>();
@OnEvent(value = "CHANNEL_SYSTEM")
public void systemDataListener(String receiveMsg) {
if (!StringUtils.hasLength(receiveMsg)){
return;
}
JSONObject msgObject = (JSONObject) JSON.parse(receiveMsg);
String userFlag = String.valueOf(msgObject.get("from"));
String content = String.valueOf(msgObject.get("content"));
log.info("收到用户 : {} 推送到系统频道的一条消息 :{}",userFlag,content );
}
public void sendToAll(Map<String, Object> msg,String sendChannel) {
if (connectMap.isEmpty()){
return;
}
//给在这个频道的每个客户端发消息
for (Map.Entry<String, SocketIOClient> entry : connectMap.entrySet()) {
entry.getValue().sendEvent(sendChannel, msg);
}
}
public void sendToOne(String userFlag, Map<String, Object> msg,String sendChannel) {
//拿出某个客户端信息
SocketIOClient socketClient = getSocketClient(userFlag);
if (Objects.nonNull(socketClient) ){
//单独给他发消息
socketClient.sendEvent(sendChannel,msg);
}
}
/***
* 广播在线用户数
*/
@OnEvent(value = "onlineUsers")
public void broadcastOnlineUsers() {
int count = connectMap.size();
System.out.println("当前在线用户数:" + count);
if (!connectMap.isEmpty()){
for (Map.Entry<String, SocketIOClient> entry : connectMap.entrySet()) {
entry.getValue().sendEvent("onlineUsers", count);
System.out.println("已向用户:" + entry.getKey() + " 发送在线用户数:" + count);
}
}
}
/**
* 识别出客户端
* @param userFlag
* @return
*/
public SocketIOClient getSocketClient(String userFlag){
SocketIOClient client = null;
if (StringUtils.hasLength(userFlag) && !connectMap.isEmpty()){
for (String key : connectMap.keySet()) {
if (userFlag.equals(key)){
client = connectMap.get(key);
}
}
}
return client;
}
}
1.6 创建 socket handler
负责客户端的连接、断开等
java
package com.tool.tooladmin.webSocket;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class SocketHandler {
private final Logger log = LoggerFactory.getLogger(this.getClass());
@Autowired
private SocketIOServer socketIoServer;
@PostConstruct
private void start(){
try {
socketIoServer.start();
}catch (Exception e){
e.printStackTrace();
}
}
@PreDestroy
private void destroy(){
try {
socketIoServer.stop();
}catch (Exception e){
e.printStackTrace();
}
}
@OnConnect
public void connect(SocketIOClient client) {
String userFlag = client.getHandshakeData().getSingleUrlParam("userFlag");
SocketUtil.connectMap.put(userFlag, client);
log.info("客户端: "+ userFlag+ "已连接");
}
@OnDisconnect
public void onDisconnect(SocketIOClient client) {
String userFlag = client.getHandshakeData().getSingleUrlParam("userFlag");
log.info("客户端:" + userFlag + "断开连接");
SocketUtil.connectMap.remove(userFlag, client);
}
}
1.7 创建前端VUE页面
发现使用VUE一直掉线和存在无法使用,可能是用法不对,使用html倒是正常,未发现问题点,等发现后再调整代码
html
<template>
<h1>聊天室</h1>
<h5>当前在线用户: {{ onlineUsers }}</h5>
<div>
<ul>
<li v-for="msg in messages" :key="msg">{{ msg }}</li>
</ul>
</div>
<input v-model="newMessage" @keyup.enter="sendMessage" placeholder="输入消息...">
<button @click="sendMessage">发送</button>
</template>
<script setup>
import {ref, onMounted, onUnmounted} from 'vue';
import {io} from 'socket.io-client';
const newMessage = ref('');
const messages = ref([]);
const onlineUsers = ref(0);
let socket = null;
onMounted(() => {
// 替换为你的 Socket.IO 服务器地址
const userFlag = 'userFlag=用户' + Math.floor(Math.random() * 1000);
socket = io('http://localhost:8889', {
// transports: ['websocket'], // 指定传输方式,如WebSocket
autoConnect: true, // 是否自动连接
reconnection: true, // 是否自动重新连接
// reconnectionAttempts: 3, // 重新连接尝试次数
// reconnectionDelay: 1000, // 重新连接延迟时间(毫秒)
query: { userFlag: userFlag }, // 自定义查询参数
});
socket.on('connect', () => {
console.log('已连接到聊天室');
});
socket.on('message', (data) => {
console.log('222222');
messages.value.push(data);
});
socket.on('disconnect', () => {
console.log('已断开连接');
});
socket.on('error', (error) => {
console.error('Socket.IO 错误:', error);
});
socket.on('onlineUsers', (count) => {
console.log('当前在线用户:', count)
onlineUsers.value = count;
});
});
onUnmounted(() => {
if (socket) {
socket.disconnect();
}
});
const sendMessage = () => {
console.log('发送消息', newMessage.value)
if (newMessage.value.trim()) {
socket.emit('CHANNEL_SYSTEM', JSON.stringify({
'content': newMessage.value,
'from': 'user_JC'
}));
newMessage.value = '';
}
}
</script>
<style scoped>
/* 添加样式以美化聊天界面 */
</style>