news 2026/4/18 4:27:33

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/3/29 19:13:58

lora-scripts安全性考量:输入数据隐私保护措施

LoRA训练中的隐私防线&#xff1a;如何安全使用自动化脚本处理敏感数据 在生成式AI快速普及的今天&#xff0c;个性化模型定制已不再是大厂专属。LoRA&#xff08;Low-Rank Adaptation&#xff09;技术让普通开发者也能用几十张图片或几百条语料&#xff0c;就完成对Stable Dif…

作者头像 李华
网站建设 2026/3/31 6:20:52

C++开发者必看,C++26反射系统详解与实战应用

第一章&#xff1a;C26反射系统概述C26标准正在积极开发中&#xff0c;其中最受期待的特性之一是原生反射系统的引入。该系统旨在通过编译时获取类型信息的能力&#xff0c;极大提升元编程的表达力与可维护性&#xff0c;减少对模板技巧和宏的依赖。核心设计目标 支持在编译期查…

作者头像 李华
网站建设 2026/4/11 20:14:07

lora-scripts迁移学习能力验证:跨领域微调表现测试

LoRA微调实战&#xff1a;lora-scripts 跨领域迁移能力深度验证 在生成式AI快速普及的今天&#xff0c;一个现实问题日益凸显&#xff1a;通用大模型虽然强大&#xff0c;但面对特定风格、专业术语或品牌语义时&#xff0c;往往“懂个大概却不够精准”。比如你让Stable Diffusi…

作者头像 李华
网站建设 2026/3/26 19:45:02

多阶段训练方案:先预训练再精调的lora-scripts实现

多阶段训练方案&#xff1a;先预训练再精调的 LoRA 落地实践 在生成式 AI 爆发式发展的今天&#xff0c;我们早已不再满足于“通用模型随便画画、随便写写”的初级体验。无论是艺术创作者想复刻自己的画风&#xff0c;还是企业希望打造专属 IP 形象或行业知识问答系统&#xff…

作者头像 李华
网站建设 2026/4/12 18:36:28

打造企业专属营销文案机器人:lora-scripts微调LLM实战

打造企业专属营销文案机器人&#xff1a;lora-scripts微调LLM实战 在内容为王的时代&#xff0c;品牌每天都在与时间赛跑——新品发布要快、节日促销要准、社交媒体互动要“有梗”。可现实是&#xff0c;市场团队常常卡在文案创作上&#xff1a;资深运营离职后风格断层&#xf…

作者头像 李华
网站建设 2026/4/16 10:52:21

企业私有化部署lora-scripts训练系统的安全策略建议

企业私有化部署 lora-scripts 训练系统的安全策略建议 在医疗、金融和法律等高敏感行业&#xff0c;AI 模型的定制化需求日益增长——从构建专属客服话术到生成符合品牌调性的视觉内容。LoRA&#xff08;Low-Rank Adaptation&#xff09;因其参数高效、资源消耗低的特点&#x…

作者头像 李华