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秒后会模拟大厅变更,服务端打印的列表正常,但客户端接收的列表异常。
问题分析
- 类型不匹配错误:Server类中
setUserCurrentLobbyId方法传入UUID类型参数,但User类的setCurrentLobbyId方法仅接受int类型,这会导致编译错误,根本无法正确设置大厅ID。 - 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
相关产品推荐
相关产品推荐

