news 2026/4/23 15:18:50

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/4/23 11:30:45

揭秘C++26线程调度优化:如何通过CPU亲和性提升程序性能300%?

第一章&#xff1a;C26线程调度与CPU亲和性概述现代高性能计算和实时系统对线程执行效率提出了更高要求。C26标准在并发支持库中引入了对线程调度策略和CPU亲和性的标准化支持&#xff0c;使开发者能够更精细地控制线程在多核处理器上的执行位置与优先级。线程调度策略的增强 C…

作者头像 李华
网站建设 2026/4/23 13:12:32

lora-scripts支持多种基础模型:v1.5、v2.1等兼容性说明

lora-scripts 支持多种基础模型&#xff1a;v1.5、v2.1 等兼容性深度解析 在生成式 AI 快速普及的今天&#xff0c;个性化模型微调已成为创作者和开发者的核心需求。然而&#xff0c;全参数微调动辄需要数十 GB 显存与专业算力支持&#xff0c;对大多数个人用户而言门槛过高。L…

作者头像 李华
网站建设 2026/4/23 13:11:42

CUDA版本匹配问题排查:PyTorch与NVIDIA驱动对应关系

CUDA版本匹配问题排查&#xff1a;PyTorch与NVIDIA驱动对应关系 在深度学习项目中&#xff0c;当你信心满满地启动训练脚本&#xff0c;却看到 torch.cuda.is_available() 返回 False&#xff0c;或者训练刚跑几步就爆出 CUDA error: invalid device function——这种挫败感几…

作者头像 李华
网站建设 2026/4/23 13:12:42

赛博朋克风格一键生成:基于lora-scripts的艺术创作实践

赛博朋克风格一键生成&#xff1a;基于lora-scripts的艺术创作实践 在AI生成内容爆炸式增长的今天&#xff0c;我们早已不再满足于“画得像”或“说得通”。真正打动人的&#xff0c;是那些具有鲜明风格、能唤起情绪共鸣的作品——比如霓虹灯下雨雾弥漫的赛博朋克街景&#xff…

作者头像 李华
网站建设 2026/4/23 13:12:32

如何将C++程序性能压榨到极致?,内核开发者不会告诉你的8个秘密

第一章&#xff1a;C性能优化的底层认知在C开发中&#xff0c;性能优化不仅仅是算法层面的改进&#xff0c;更依赖于对计算机底层机制的深入理解。现代CPU架构、内存层级结构以及编译器优化策略共同决定了程序的实际运行效率。理解缓存与内存访问模式 CPU缓存是影响性能的关键因…

作者头像 李华
网站建设 2026/4/23 13:11:57

【C++多线程死锁避免终极指南】:掌握5大核心策略,彻底杜绝死锁风险

第一章&#xff1a;C多线程死锁的本质与成因在C多线程编程中&#xff0c;死锁是一种严重的并发问题&#xff0c;它导致两个或多个线程无限期地相互等待&#xff0c;从而程序无法继续执行。死锁的发生通常源于资源竞争和同步机制的不当使用。死锁的四个必要条件 当以下四个条件同…

作者头像 李华