上篇實現了伺服器向所有客戶端廣播推送訊息,本文進階改造為一對一私聊模式:伺服器維護用戶 ID 與 Socket 映射,根據訊息中的 friendId 精準路由轉發,同時保留廣播功能。完整展示實體類設計、伺服器路由邏輯、客戶端登入狀態區分與雙向通訊流程。
// 用戶資訊
public class UserInfoBean implements Serializable {
private long userId;
private String userName, likeName, userPwd, userIcon;
// getter/setter
}
// 聊天訊息
public class MessageBean implements Serializable {
private long messageId, groupId;
private boolean isGroup;
private int chatType; // 1文字 2圖片 3視頻 4文件 5位置 6語音 7視頻通話
private String content, errorMsg;
private int errorCode, userId, friendId;
private MessageFileBean chatFile;
// getter/setter
}
// 訊息附件
public class MessageFileBean implements Serializable {
private int fileId;
private String fileName, fileType, fileTitle;
private long fileLength;
private Byte[] fileByte;
// getter/setter
}
public class ChatServer {
private static ServerSocket server;
private List<Socket> socketList = new ArrayList<>();
private Map<Integer, Socket> socketMap = new HashMap<>(); // userId -> Socket
private Map<Integer, UserInfoBean> userMap = new HashMap<>(); // userId -> UserInfo
private Gson gson = new Gson();
public void initServer() {
server = new ServerSocket(8080);
createMessage(); // 遞迴 accept + 開啟新線程持續監聽
}
private void createMessage() {
Socket socket = server.accept();
socketList.add(socket);
new Thread(this::createMessage).start(); // 持續監聽新連線
// 處理當前連線訊息
new Thread(() -> {
try (BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()))) {
String line;
while ((line = in.readLine()) != null) {
MessageBean msg = gson.fromJson(line, MessageBean.class);
setChatMap(msg, socket); // 註冊 userId -> socket 映射
getFriend(msg); // 路由轉發給好友
}
} catch (Exception e) { e.printStackTrace(); }
}).start();
}
// 註冊映射關係
private void setChatMap(MessageBean msg, Socket socket) {
if (!userMap.containsKey(msg.getUserId()))
userMap.put(msg.getUserId(), mockUser(msg.getUserId()));
if (!socketMap.containsKey(msg.getUserId()))
socketMap.put(msg.getUserId(), socket);
}
// 精準路由轉發給好友
private void getFriend(MessageBean msg) {
Socket target = socketMap.get(msg.getFriendId());
if (target != null) {
try {
String json = gson.toJson(msg) + "\n"; // 換行符作為訊息邊界
target.getOutputStream().write(json.getBytes("utf-8"));
} catch (IOException e) { e.printStackTrace(); }
}
}
// 保留廣播功能(管理員推送)
private void getMessage() { /* 從控制台讀取並廣播給所有 socketMap.values() */ }
}
admin/1 為用戶 1,admin/2 為用戶 2,啟動時設定 userId 與 friendId 互指OutputStreamBufferedReader.readLine() 阻塞讀取,反序列化後透過 Handler 更新 UIuserId,伺服器建立 userId ↔ Socket 映射friendId 決定目標接收者,實現單聊\n 作為幀分隔,配合 readLine()accept() 監聽,每連線一執行緒處理 I/O,廣播另闢執行緒groupId、isGroup、chatType,支援群聊、多媒體擴展此架構為即時通訊系統奠定基礎,後續可引入 Netty 重構提升併發效能,或整合 Redis 實現多節點訊息路由。