You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java实现P2P聊天系统TCP协议偶发消息丢失、无法全量展示问题求助

偶发消息展示不全根因

  • numberOfConnections变量存在可见性问题:waitUntilEnds方法直接读取numberOfConnections时未加同步锁,虽然addNumberOfConnections是同步方法,能保证修改后刷新到主内存,但无同步的读操作可能读取到工作线程缓存的旧值,导致numberOfConnections实际未归零就提前退出等待,部分消息还未写入集合就被读取,出现展示不全。
  • 集合读写未保证一致性:仅对addMessage写操作加了同步锁,执行show命令读取messages、senders集合时没有加锁,并发场景下可能读取到修改过程中的中间状态,出现计数不准、漏展示消息的问题。
  • 连接处理逻辑阻塞accept线程:当前在ServerSocket的accept主线程中直接执行startNewConnection处理消息,没有为每个连接启动独立线程,同一时间只能处理一个连接,多个连接同时到达时后续连接会排队等待,导致numberOfConnections计数滞后,提前触发等待结束。

端口重绑定后完全收不到消息根因

  • 未开启端口复用选项:TCP连接断开后会存在2MSL的TIME_WAIT阶段,端口默认会被占用1~2分钟,创建ServerSocket时未设置SO_REUSEADDR参数,频繁重绑端口时会绑定失败。代码中仅捕获绑定异常打印堆栈,未做失败提示,容易误以为端口监听已启动,实际服务未生效,自然收不到该端口的消息。
  • 旧服务资源未清理干净:重绑定端口时,未正确关闭旧的ServerSocket、中断旧的accept线程,可能出现旧线程仍占用端口,导致新服务绑定失败。

修复建议

  1. 调整setServer中连接处理逻辑,为每个连接启动独立线程,避免阻塞accept流程:
private void setServer(int port) {
        try {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.setReuseAddress(true); // 开启端口复用
            serverSocket.bind(new InetSocketAddress(port));
            new Thread(() -> {
                while (!serverSocket.isClosed()) {
                    try {
                        Socket socket = serverSocket.accept();
                        addNumberOfConnections(1);
                        // 独立线程处理单连接逻辑
                        new Thread(() -> {
                            try {
                                startNewConnection(socket);
                            } finally {
                                addNumberOfConnections(-1);
                            }
                        }).start();
                    } catch (IOException ignored) {}
                }
            }).start();
        } catch (IOException e) {
            e.printStackTrace();
            // 补充绑定失败的错误提示,避免静默失败
            System.err.println("端口" + port + "监听失败");
        }
    }
  1. 修正waitUntilEnds的变量读取逻辑,保证可见性:
private void waitUntilEnds() {
        while (true) {
            // 读操作也加同步锁,保证读取的是主内存最新值
            synchronized (this) {
                if (numberOfConnections == 0) break;
            }
            try {
                Thread.sleep(10);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
  1. 所有读取messages、senders集合的逻辑(比如show命令的实现),都要加和addMessage同一对象的同步锁,保证读写一致性。

内容的提问来源于stack exchange,提问作者Taha Akbari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.11 16:48:03