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

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);
});
问题根源与修复方案

问题根源

  1. 客户端发送格式错误:Java的BufferedReader.readLine()依赖换行符(\n或\r\n)识别完整命令,但JavaScript客户端的client.write(input)仅发送输入内容,未添加换行符,导致服务器readLine()一直阻塞,无法处理命令,自然不会返回响应。只有客户端断开连接时,readLine()才返回null,触发后续响应逻辑。
  2. 服务器响应缺失:PUT、GET、DELETE命令的服务器逻辑仅在控制台打印结果,未通过out.println()向客户端返回响应,导致客户端无反馈。
  3. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 20:15:18