news 2026/3/1 10:04:36

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/2/24 19:35:20

提示词语法详解:在SD中正确调用lora-scripts生成的LoRA模型

提示词语法详解&#xff1a;在SD中正确调用lora-scripts生成的LoRA模型 在数字内容创作日益个性化的今天&#xff0c;如何让AI真正“理解”你的风格&#xff0c;成为每一位创作者关心的问题。无论是想复现某位艺术家的笔触、还原某个虚拟角色的形象&#xff0c;还是打造专属品…

作者头像 李华
网站建设 2026/2/24 15:24:58

C++多线程编程避坑宝典(死锁预防的8个黄金法则)

第一章&#xff1a;C多线程死锁问题的根源剖析在C多线程编程中&#xff0c;死锁是导致程序停滞不前的常见问题。其根本原因在于多个线程对共享资源的竞争访问缺乏合理的同步控制&#xff0c;导致彼此相互等待对方释放锁&#xff0c;从而陷入永久阻塞状态。死锁的四大必要条件 互…

作者头像 李华
网站建设 2026/2/28 14:27:28

C++26契约编程新特性:如何利用静态/动态检查提升代码健壮性

第一章&#xff1a;C26契约编程概述C26 引入的契约编程&#xff08;Contract Programming&#xff09;机制旨在提升代码的可靠性与可维护性&#xff0c;通过在函数接口中显式声明前置条件、后置条件和断言&#xff0c;使程序逻辑更加清晰&#xff0c;并为编译器和运行时系统提供…

作者头像 李华
网站建设 2026/2/27 13:14:48

C++内核优化实战案例:一个循环优化让系统吞吐量提升7倍

第一章&#xff1a;C内核性能优化的挑战与机遇在现代高性能计算、实时系统和资源受限环境中&#xff0c;C 内核的性能优化成为决定系统成败的关键因素。尽管 C 提供了对硬件的精细控制和高效的执行能力&#xff0c;但充分发挥其潜力仍面临诸多挑战&#xff0c;同时也蕴藏着巨大…

作者头像 李华
网站建设 2026/2/26 20:10:19

【C++26任务队列深度解析】:揭秘新标准中队列大小控制的5大核心机制

第一章&#xff1a;C26任务队列大小控制的演进与意义随着并发编程在现代软件系统中的广泛应用&#xff0c;任务调度机制的可控性与稳定性成为关键设计考量。C26标准在并发设施方面引入了对任务队列大小的显式控制机制&#xff0c;标志着标准库在线程池与异步执行模型上的进一步…

作者头像 李华
网站建设 2026/2/27 8:23:46

C++26反射即将上线:5个代码示例带你提前掌握未来标准

第一章&#xff1a;C26反射特性概览C26 正在为现代 C 引入原生反射支持&#xff0c;这标志着语言在元编程能力上的重大飞跃。通过编译时反射&#xff0c;开发者能够直接查询和操作类型、变量、函数等程序结构的信息&#xff0c;而无需依赖宏或复杂的模板技巧。核心目标与设计原…

作者头像 李华