Java TCP Socket服务器与JavaScript客户端通信无实时响应问题排查
问题描述
我正在开发一个基于TCP Socket的简单键值存储应用:
- 服务器采用Java实现
- 客户端采用JavaScript实现
核心需求:客户端输入CONNECT、PUT、GET、DELETE或DISCONNECT命令,发送至服务器处理后返回响应。
当前异常:
- JavaScript客户端在收到服务器响应前,就被提示输入新内容
- 仅输入
DISCONNECT时,服务器才会返回此前所有操作的响应 - 切换为Java客户端时,程序可正常运行
控制台现象
客户端
Connected to server Enter message to send to server: CONNECT Juan Enter message to send to server:
(未收到预期的CONNECT: OK响应)
服务器
Server started on port 8080 New client connected CONNECT Juan
(服务器打印了收到的命令,但未返回响应)
相关代码
Java服务器代码
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.Map; public class KeyValueServer { private static Map<String, String> keyValueStore; private static Map<String, Boolean> clientConnected; public KeyValueServer() { keyValueStore = new HashMap<>(); clientConnected = new HashMap<>(); } public void start(int port) { try (ServerSocket serverSocket = new ServerSocket(port)) { System.out.println("Server started on port " + port); while (true) { Socket clientSocket = serverSocket.accept(); System.out.println("New client connected"); ClientHandler clientHandler = new ClientHandler(clientSocket); Thread clientThread = new Thread(clientHandler); clientThread.start(); } } catch (IOException e) { System.err.println("Error: Port " + port + " is already in use."); System.exit(1); } } private static class ClientHandler implements Runnable { private final Socket clientSocket; public ClientHandler(Socket clientSocket) { this.clientSocket = clientSocket; } @Override public void run() { try { BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); String inputLine; while ((inputLine = in.readLine()) != null) { System.out.println(inputLine); String[] tokens = inputLine.split(" ", 2); String command = tokens[0]; switch (command) { case "CONNECT": if (tokens.length < 2) { System.out.println("ERROR: Invalid command"); break; } if (clientConnected.containsKey(tokens[1])) { System.out.println("CONNECT: ERROR"); out.println("CONNECT: ERROR"); break; } String clientId = tokens[1]; clientConnected.put(clientId, true); out.println("CONNECT: OK"); System.out.println("CONNECT: OK"); out.flush(); break; case "PUT": if (tokens.length < 2) { System.out.println("ERROR: Invalid command"); break; } System.out.println(clientConnected.toString()); if (clientConnected.containsValue(true)) { String inputLine2; inputLine2 = in.readLine(); keyValueStore.put(tokens[1], inputLine2); System.out.println(keyValueStore.toString()); System.out.println("PUT: OK"); } else { System.out.println("PUT: ERROR"); } break; case "GET": if (tokens.length < 2) { System.out.println("ERROR: Invalid command"); break; } if (clientConnected.containsValue(true)) { String value = keyValueStore.getOrDefault(tokens[1], ""); if (value.isEmpty()) { System.out.println("GET: ERROR"); } else { System.out.println(value); } } else { System.out.println("GET: ERROR"); } break; case "DELETE": if (tokens.length < 2) { System.out.println("ERROR: Invalid command"); break; } if (clientConnected.containsValue(true)) { keyValueStore.remove(tokens[1]); System.out.println("DELETE: OK"); } else { System.out.println("DELETE: ERROR"); } break; case "DISCONNECT": if (clientConnected.containsValue(true)) { clientConnected.remove(tokens[0]); keyValueStore.remove(tokens[0]); clientConnected.clear(); System.out.println("DISCONNECT: OK"); out.println("DISCONNECT: OK"); System.exit(0); } else { System.out.println("DISCONNECT: OK"); out.println("DISCONNECT: OK"); } break; default: System.out.println("ERROR: Invalid command"); out.println("ERROR: Invalid command"); in.close(); out.close(); clientSocket.close(); System.exit(0); break; } } in.close(); out.close(); clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String[] args) { KeyValueServer server = new KeyValueServer(); server.start(8080); } }
JavaScript客户端代码
const readline = require('readline'); const net = require('net'); const serverAddress = 'localhost'; const port = 8080; const client = new net.Socket(); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); client.connect(port, serverAddress, () => { console.log('Connected to server'); rl.setPrompt('Enter message to send to server: '); rl.prompt(); }); rl.on('line', (input) => { if (input.trim() === 'DISCONNECT') { client.write(input); client.end(); rl.close(); } else { client.write(input); rl.prompt(); } }); client.on('data', (data) => { console.log(`Server message Received: ${data}`); const serverMessage = data.toString(); console.log('Server says: ' + serverMessage); rl.prompt(); }); client.on('close', () => { console.log('Connection closed'); process.exit(0); }); client.on('error', (err) => { console.log('Error connecting to server: ' + err.message); process.exit(1); });
问题根源与修复方案
问题根源
- 客户端发送格式错误:Java的
BufferedReader.readLine()依赖换行符(\n或\r\n)识别完整命令,但JavaScript客户端的client.write(input)仅发送输入内容,未添加换行符,导致服务器readLine()一直阻塞,无法处理命令,自然不会返回响应。只有客户端断开连接时,readLine()才返回null,触发后续响应逻辑。 - 服务器响应缺失:
PUT、GET、DELETE命令的服务器逻辑仅在控制台打印结果,未通过out.println()向客户端返回响应,导致客户端无反馈。 - DISCONNECT逻辑错误:服务器
DISCONNECT命令中clientConnected.remove(tokens[0])的参数为DISCONNECT字符串,而非当前客户端ID,同时用clientConnected.containsValue(true)判断连接状态的方式不准确,多客户端场景会失效。
修复方案
1. 修复JavaScript客户端:添加换行符并调整输入时机
修改客户端发送逻辑,确保每次命令都携带换行符,且仅在收到服务器响应后提示输入:
rl.on('line', (input) => { if (input.trim() === 'DISCONNECT') { client.write(input + '\n'); // 添加换行符 client.end(); rl.close(); } else { client.write(input + '\n'); // 添加换行符 // 移除提前触发的输入提示,改为收到响应后再提示 } });
2. 修复Java服务器:补全命令响应
为PUT、GET、DELETE命令添加客户端响应输出:
case "PUT": if (tokens.length < 2) { System.out.println("ERROR: Invalid command"); out.println("ERROR: Invalid command"); break; } System.out.println(clientConnected.toString()); if (clientConnected.containsValue(true)) { String inputLine2; inputLine2 = in.readLine(); keyValueStore.put(tokens[1], inputLine2); System.out.println(keyValueStore.toString()); System.out.println("PUT: OK"); out.println("PUT: OK"); out.flush(); } else { System.out.println("PUT: ERROR"); out.println("PUT: ERROR"); out.flush(); } break; case "GET": if (tokens.length < 2) { System.out.println("ERROR: Invalid command"); out.println("ERROR: Invalid command"); break; } if (clientConnected.containsValue(true)) { String value = keyValueStore.getOrDefault(tokens[1], ""); if (value.isEmpty()) { System.out.println("GET: ERROR"); out.println("GET: ERROR"); out.flush(); } else { System.out.println(value); out.println(value); out.flush(); } } else { System.out.println("GET: ERROR"); out.println("GET: ERROR"); out.flush(); } break; case "DELETE": if (tokens.length < 2) { System.out.println("ERROR: Invalid command"); out.println("ERROR: Invalid command"); break; } if (clientConnected.containsValue(true)) { keyValueStore.remove(tokens[1]); System.out.println("DELETE: OK"); out.println("DELETE: OK"); out.flush(); } else { System.out.println("DELETE: ERROR"); out.println("DELETE: ERROR"); out.flush(); } break;
3. 修复服务器DISCONNECT逻辑
在ClientHandler中添加currentClientId字段记录当前客户端ID,替换不准确的连接状态判断:
private static class ClientHandler implements Runnable { private final Socket clientSocket; private String currentClientId; // 添加客户端ID记录 public ClientHandler(Socket clientSocket) { this.clientSocket = clientSocket; } // ...其他代码 case "DISCONNECT": if (currentClientId != null && clientConnected.containsKey(currentClientId)) { clientConnected.remove(currentClientId); keyValueStore.remove(currentClientId); System.out.println("DISCONNECT: OK"); out.println("DISCONNECT: OK"); out.flush(); } else { System.out.println("DISCONNECT: OK"); out.println("DISCONNECT: OK"); out.flush(); } // 移除System.exit(0),避免关闭整个服务器 break; // ...其他代码 }
内容的提问来源于stack exchange,提问作者juanLude
相关产品推荐
相关产品推荐

