点对点聊天需服务端精准查表定向发送,concurrenthashmap保障并发安全,异步发送防阻塞,json传参防解析失败,心跳机制保连接有效,全程需日志与空值防护。

点对点聊天不是靠“广播”硬塞,而是靠服务端精准查表、按 userId 找到唯一 Session 后定向发送;用错存储结构或忽略连接状态检查,消息就必然丢。
为什么 ConcurrentHashMap 是必须的,而 HashMap 会出问题
多客户端并发登录、断开、发消息时,livingSession 需被多个线程同时读写。直接用 HashMap 会导致 ConcurrentModificationException 或数据丢失。
-
ConcurrentHashMap支持高并发读写,put/remove 操作线程安全,且不锁整个表 - 别用
static HashMap+synchronized块——锁粒度太粗,吞吐量骤降 - Spring Boot 场景下,若用了
@ServerEndpoint,每个 WebSocket 实例是无状态的,必须依赖静态容器存 Session,ConcurrentHashMap是事实标准
session.getAsyncRemote().sendText() 为何比 getBasicRemote() 更稳妥
同步发送(getBasicRemote())在高并发或网络抖动时容易阻塞线程,甚至触发超时异常;异步发送(getAsyncRemote())把 IO 交给 Netty 或容器线程池处理,主业务线程不卡死。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
-
getAsyncRemote()返回RemoteEndpoint.Async,调用sendText()立即返回,不等发送完成 - 务必检查
session != null && session.isOpen(),否则sendText()会抛IllegalStateException - Netty 场景下,
Channel.writeAndFlush()是等价替代,但需确保 Channel 绑定的是目标用户 ID
前端传参格式错一个逗号,后端解析就全崩
你看到的错误现象可能是:小明发消息,小红收不到,控制台也无日志——大概率是前端拼接字符串时没校验字段顺序或空值,导致后端 split(",") 出错。
- 前端不要用
$("#myid").val()+","+$("#friendid").val()+","+$("#chat-input").val()这种裸拼;改用 JSON:JSON.stringify({from: "u1", to: "u2", msg: "hi"}) - 后端接收后必须做字段校验:
msgEntity.getReceiveUserId()不能为空,否则livingSession.get(null)返回null - Spring Boot +
@ServerEndpoint中,路径参数如/websocket/{userId}能天然绑定登录身份,避免每次消息都传from字段
连接存活检测不做,离线用户照样“收消息”
用户关掉页面、断网、杀进程后,Session 不会自动从 ConcurrentHashMap 中清除,livingSession.get(userId) 仍能取到已失效的 Session,调用 sendText() 会静默失败或抛异常。
- 必须实现心跳机制:前端每 30s 发
ping,后端@OnMessage收到后更新 lastActive 时间戳 - 另起定时任务(如
ScheduledExecutorService),每 60s 扫描一次,剔除超时(如 90s 无心跳)的 Session - Netty 场景下,可监听
channelInactive()事件,在回调中清理userId → Channel映射
最易被忽略的一点:点对点不是“发出去就算成功”,而是“对方 onmessage 回调被触发才算送达”。中间任何一环——序列化失败、跨域拦截、WS 连接未升级、Session 状态误判——都会让消息石沉大海,必须逐层加日志和空值防护。










