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

Java对象经Socket传输后字段值异常问题求助

问题描述

将包含用户列表和大厅列表的对象通过Socket从服务器发送到客户端时,客户端能正常接收用户名等数据,但用户的大厅ID始终无法同步更新——服务器端打印显示大厅ID已变更,客户端接收的却还是初始值。

排查过程:

  • 最初怀疑UUID的序列化问题,将ID改为int后问题依旧
  • 尝试更换集合结构(从ConcurrentHashMap改为同步ArrayList),问题未解决
  • 新用户连接时能收到正确列表,但用户加入/离开大厅的变更无法同步,服务器端日志始终正常

以下是最小可复现代码:

Server.java

import java.io.IOException;
import java.net.ServerSocket;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class Server implements Runnable {
  private final ServerSocket serverSocket;
  private ConcurrentHashMap<UUID, User> users;

  public Server(int port) throws IOException {
    serverSocket = new ServerSocket(port);
    users = new ConcurrentHashMap<>();
  }

  public ConcurrentHashMap<UUID, User> getUsers() {
    return users;
  }

  public void addUser(User user) {
    users.put(user.getId(), user);
  }

  private void setUserCurrentLobbyId(User user, UUID lobbyId) {
    users.get(user.getId()).setCurrentLobbyId(lobbyId);
  }

  @Override
  public void run() {
    while (true) {
      try {
        (new Thread(new ClientHandler(this, serverSocket.accept()))).start();
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
    }
  }

  public static void main(String[] args) throws IOException, InterruptedException {
    Server server = new Server(5050);
    (new Thread(server)).start();
    User user1 = new User("user1");
    server.addUser(user1);
    Thread.sleep(5000);
    server.setUserCurrentLobbyId(user1, UUID.randomUUID());
  }
}

ClientHandler.java

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public class ClientHandler implements Runnable {
  private final Server server;
  private final Socket socket;
  private ConcurrentHashMap<UUID, User> users;

  public ClientHandler(Server server, Socket socket) {
    this.server = server;
    this.socket = socket;
    users = new ConcurrentHashMap<>();
  }

  @Override
  public void run() {
    ObjectOutputStream out;
    try {
      out = new ObjectOutputStream(socket.getOutputStream());
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    while (true) {
      updateMaps();
      Response response = new Response(users);

      System.out.println("Sent user list:");
      for (Map.Entry<UUID, User> entry : response.users().entrySet()) {
        System.out.println("	" + entry.getValue().getUsername()
            + ", currentLobbyId: "
            + entry.getValue().getCurrentLobbyId());
      }

      try {
        out.writeObject(response);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }

      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        throw new RuntimeException(e);
      }
    }
  }

  public void updateMaps() {
    this.users = new ConcurrentHashMap<>(server.getUsers());
  }
}

Client.java

import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.util.Map;
import java.util.UUID;

public class Client {
  public static void main(String[] args) throws IOException, ClassNotFoundException {
    Socket socket = new Socket("localhost", 5050);

    ObjectInputStream in = new ObjectInputStream(socket.getInputStream());

    while (true) {
      Response response = (Response) in.readObject();

      System.out.println("Received user list:");
      for (Map.Entry<UUID, User> entry : response.users().entrySet()) {
        System.out.println("	" + entry.getValue().getUsername()
            + ", currentLobbyId: "
            + entry.getValue().getCurrentLobbyId());
      }
    }
  }
}

User.java

import java.io.Serializable;
import java.util.UUID;

public class User implements Serializable {
  private final UUID id;
  private final String username;
  private int currentLobbyId;

  public User(String username) {
    this.id = UUID.randomUUID();
    this.username = username;
  }

  public UUID getId() {
    return id;
  }

  public String getUsername() {
    return username;
  }

  public int getCurrentLobbyId() {
    return currentLobbyId;
  }

  public void setCurrentLobbyId(int newLobbyId) {
    currentLobbyId = newLobbyId;
  }
}

Response.java

import java.io.Serializable;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

public record Response(
    ConcurrentHashMap<UUID, User> users)
    implements Serializable {
}

运行方式:在一个窗口启动Server.java,另一个窗口启动Client.java,对比输出。5秒后会模拟大厅变更,服务端打印的列表正常,但客户端接收的列表异常。


问题分析

  1. 类型不匹配错误:Server类中setUserCurrentLobbyId方法传入UUID类型参数,但User类的setCurrentLobbyId方法仅接受int类型,这会导致编译错误,根本无法正确设置大厅ID。
  2. ObjectOutputStream缓存机制:Java的ObjectOutputStream默认会缓存已序列化的对象实例。第一次发送User对象后,即使后续修改了该对象属性,再次调用writeObject时会直接复用缓存的旧数据,不会重新序列化更新后的属性——这是客户端无法收到最新大厅ID的核心原因。

解决方案

1. 修复类型不匹配问题

修正Server类中的方法参数,使其与User类的方法匹配:

// 修改Server类的setUserCurrentLobbyId方法
private void setUserCurrentLobbyId(User user, int lobbyId) {
    users.get(user.getId()).setCurrentLobbyId(lobbyId);
}

// 修改main方法中的调用逻辑
public static void main(String[] args) throws IOException, InterruptedException {
    Server server = new Server(5050);
    (new Thread(server)).start();
    User user1 = new User("user1");
    server.addUser(user1);
    Thread.sleep(5000);
    // 传入int类型的大厅ID
    server.setUserCurrentLobbyId(user1, 1001);
}

2. 解决ObjectOutputStream缓存问题

可以选择以下两种方式之一:

方式1:使用writeUnshared重置序列化缓存

修改ClientHandler中的发送逻辑,用writeUnshared替代writeObject,强制重新序列化对象:

try {
    out.writeUnshared(response);
    out.reset(); // 清空输出流的对象缓存,确保后续发送的都是最新数据
} catch (IOException e) {
    throw new RuntimeException(e);
}

方式2:发送前创建User对象的深拷贝

在User类中添加拷贝构造方法:

public User(User other) {
    this.id = other.id;
    this.username = other.username;
    this.currentLobbyId = other.currentLobbyId;
}

修改ClientHandler的updateMaps方法,复制每个User对象生成新实例:

public void updateMaps() {
    ConcurrentHashMap<UUID, User> newUsers = new ConcurrentHashMap<>();
    for (Map.Entry<UUID, User> entry : server.getUsers().entrySet()) {
        newUsers.put(entry.getKey(), new User(entry.getValue()));
    }
    this.users = newUsers;
}

内容的提问来源于stack exchange,提问作者Christo Swanepoel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 11:24:56