news 2026/9/16 21:56:20

WebSocket 消息推送中心:Spring Boot + Netty-SocketIO 打造“仿微信”网页版即时通讯系统 (IM)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
WebSocket 消息推送中心:Spring Boot + Netty-SocketIO 打造“仿微信”网页版即时通讯系统 (IM)

📡 前言:为什么选 Netty-SocketIO?

Spring 官方提供了spring-boot-starter-websocket,为什么不用?
虽然官方的支持 STOMP 协议,上手简单,但在面对高并发、长连接维持、心跳检测、断线自动重连等复杂场景时,基于Netty封装的netty-socketio表现得更加稳健和高性能。

它完美适配了前端的socket.io-client库,让前后端联调变得异常简单。


🏗️ 一、 架构设计:用户如何找到彼此?

IM 系统的核心在于**“路由”**:当 UserA 发消息给 UserB 时,服务器怎么知道 UserB 的长连接是哪一个?

我们需要维护一张用户 ID <–> Socket Session的映射表。

IM 消息流转图 (Mermaid):

IM 核心服务

1. 发送消息 {to: UserB}
2. 查找 SessionMap
3. 获取 UserB 的连接
4. 异步持久化
5. 推送事件 push_event
6. 收到消息

用户 A (Vue 前端)

Netty Server

Map: UserId -> UUID

SocketIOClient (UserB)

MySQL / MongoDB

用户 B (Vue 前端)

渲染聊天气泡


🛠️ 二、 后端实战:搭建 Netty-SocketIO 服务

1. 引入依赖
<dependency><groupId>com.corundumstudio.socketio</groupId><artifactId>netty-socketio</artifactId><version>1.7.22</version></dependency>
2. 配置启动类 (SocketIOConfig.java)

我们不使用 Tomcat 的端口,而是另起一个 Netty 端口(如 9092)。

@ConfigurationpublicclassSocketIOConfig{@BeanpublicSocketIOServersocketIOServer(){com.corundumstudio.socketio.Configurationconfig=newcom.corundumstudio.socketio.Configuration();config.setHostname("localhost");config.setPort(9092);// 关键:设置最大帧长度,防止发大图报错config.setMaxFramePayloadLength(1024*1024);config.setMaxHttpContentLength(1024*1024);// 握手协议参数SocketConfigsocketConfig=newSocketConfig();socketConfig.setReuseAddress(true);config.setSocketConfig(socketConfig);returnnewSocketIOServer(config);}// Spring Boot 启动时同时启动 Netty 服务@BeanpublicSpringAnnotationScannerspringAnnotationScanner(SocketIOServersocketServer){returnnewSpringAnnotationScanner(socketServer);}}
3. 核心业务逻辑 (MessageEventHandler.java)

这里实现了上线注册单聊群聊逻辑。

@ComponentpublicclassMessageEventHandler{// 线程安全的 Map,存储 UserId -> SocketClient 的映射publicstaticfinalConcurrentHashMap<String,UUID>USER_CLIENT_MAP=newConcurrentHashMap<>();@AutowiredprivateSocketIOServerserver;// --- 1. 客户端连接 (握手) ---@OnConnectpublicvoidonConnect(SocketIOClientclient){// 前端连接时带上参数:http://localhost:9092?userId=1001StringuserId=client.getHandshakeData().getSingleUrlParam("userId");if(userId!=null){USER_CLIENT_MAP.put(userId,client.getSessionId());System.out.println("用户上线: "+userId);}}// --- 2. 客户端断开 ---@OnDisconnectpublicvoidonDisconnect(SocketIOClientclient){StringuserId=client.getHandshakeData().getSingleUrlParam("userId");if(userId!=null){USER_CLIENT_MAP.remove(userId);System.out.println("用户下线: "+userId);}}// --- 3. 处理单聊消息 ---@OnEvent("send_msg")publicvoidonEvent(SocketIOClientclient,ChatMessageRequestdata){StringtoUserId=data.getToUserId();UUIDtargetSessionId=USER_CLIENT_MAP.get(toUserId);// 如果用户在线,直接推送if(targetSessionId!=null&&server.getClient(targetSessionId)!=null){server.getClient(targetSessionId).sendEvent("receive_msg",data);}else{// 用户不在线,存入数据库标记为“未读消息”saveOfflineMessage(data);}}// --- 4. 处理群聊 (加入房间) ---@OnEvent("join_group")publicvoidonJoinGroup(SocketIOClientclient,StringgroupId){client.joinRoom(groupId);// SocketIO 自带房间管理}@OnEvent("send_group_msg")publicvoidonGroupMsg(SocketIOClientclient,ChatMessageRequestdata){// 直接向房间内广播server.getRoomOperations(data.getGroupId()).sendEvent("receive_group_msg",data);}}

🎨 三、 前端 Vue3 实战:Socket.io-client

前端使用socket.io-client库,代码极其简洁。

安装:

npminstallsocket.io-client

连接与收发:

import{io}from"socket.io-client";// 1. 建立连接 (带上自己的 ID)constsocket=io("http://localhost:9092",{query:{userId:"1001"},transports:["websocket"]// 强制使用 WebSocket,不用轮询});// 2. 监听连接成功socket.on("connect",()=>{console.log("连接成功,SessionID:",socket.id);});// 3. 接收消息 (监听 receive_msg 事件)socket.on("receive_msg",(data)=>{console.log("收到新消息:",data);// 这里将 data push 到聊天记录数组中,页面会自动渲染messages.value.push(data);});// 4. 发送消息constsendMessage=()=>{socket.emit("send_msg",{fromUserId:"1001",toUserId:"1002",content:"你好,今晚吃什么?",type:"text"});};

🚀 四、 进阶挑战:分布式集群下的 Session 共享

如果你的用户量达到 10 万,一台服务器扛不住,你需要部署两台 Netty 服务。
问题来了:
UserA 连上了 Server1,UserB 连上了 Server2。
UserA 发消息给 UserB,Server1 的内存 Map 里找不到 UserB 的 Session,怎么办?

解决方案:Redis Pub/Sub (发布订阅)

  1. Server1 发现 UserB 不在本地。
  2. Server1 将消息 Publish 到 Redis 的频道IM_CHANNEL
  3. Server2 订阅了该频道,收到消息后,发现 UserB 在自己这儿。
  4. Server2 将消息推送给 UserB。

Redisson 提供了很好的支持,或者直接使用 Socket.IO 官方的Redis Adapter


🎯 总结

通过 Spring Boot + Netty-SocketIO,我们只用了几百行代码就实现了一个高实时性的 IM 系统核心。
这不仅是一个聊天工具,它还是即时通知、在线客服、游戏对战等场景的基石。

Next Step:
现在的消息只存在内存里,重启就丢了。
试着引入MongoDB来存储聊天记录(写入速度快,结构灵活),并实现“历史消息回溯”功能,你的 IM 系统就具备商业价值了!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 13:27:56

C++编译期调试革命:如何利用现代工具链将元编程错误减少80%

第一章&#xff1a;C编译期调试革命的背景与意义在现代软件工程中&#xff0c;C 以其高性能和底层控制能力广泛应用于系统编程、游戏开发和嵌入式领域。然而&#xff0c;传统运行时调试方式往往滞后于错误发生时刻&#xff0c;导致问题定位困难、修复成本高昂。编译期调试技术的…

作者头像 李华
网站建设 2026/8/28 21:53:21

learning_rate学习率调整经验总结:不同任务下的最优区间

learning_rate学习率调整经验总结&#xff1a;不同任务下的最优区间 在使用 LoRA 对 Stable Diffusion 或 LLaMA 这类大模型进行微调时&#xff0c;你有没有遇到过这样的情况&#xff1a;训练刚开始 loss 就剧烈震荡&#xff0c;甚至直接“炸掉”&#xff1f;或者相反&#xff…

作者头像 李华
网站建设 2026/9/15 17:42:22

C++26任务队列大小如何影响性能?3个关键指标你必须掌握

第一章&#xff1a;C26任务队列大小对性能影响的背景与意义在现代高性能计算和并发编程中&#xff0c;任务调度机制是决定系统吞吐量与响应延迟的关键因素。C26标准正在积极引入更完善的并发与异步支持&#xff0c;其中任务队列作为线程池和执行器的核心组件&#xff0c;其容量…

作者头像 李华
网站建设 2026/9/11 1:53:12

游戏/仿真中的物理穿透问题终极解决:C++多层碰撞检测架构设计揭秘

第一章&#xff1a;游戏/仿真中的物理穿透问题终极解决&#xff1a;C多层碰撞检测架构设计揭秘在高动态频率的游戏或物理仿真系统中&#xff0c;物体高速运动常导致“穿透”现象——即刚体穿越障碍物&#xff0c;破坏逻辑完整性。传统单一阶段的碰撞检测难以应对此类问题&#…

作者头像 李华
网站建设 2026/9/14 13:00:13

lora-scripts + Stable Diffusion:构建个性化IP形象生成系统

lora-scripts Stable Diffusion&#xff1a;构建个性化IP形象生成系统 在虚拟偶像频繁出圈、品牌吉祥物争相“出道”的今天&#xff0c;一个鲜明的视觉IP已成为产品传播的核心资产。但传统美术设计周期长、成本高&#xff0c;难以快速响应市场变化。有没有可能用AI&#xff0…

作者头像 李华
网站建设 2026/9/3 18:11:41

高效低成本模型微调方案:lora-scripts在小数据场景下的应用实践

高效低成本模型微调方案&#xff1a;lora-scripts在小数据场景下的应用实践 在消费级显卡上训练AI模型&#xff0c;曾经是天方夜谭。但如今&#xff0c;一个RTX 3090、几百张图片、不到一小时的训练时间&#xff0c;就能让你拥有一个完全个性化的图像生成模型——这不再是实验…

作者头像 李华