Java多线程Server-Client程序异常:客户端无法接收服务端响应
Java多线程服务器-客户端项目问题排查
项目目标
服务器需将数据库存储在/server/data目录下的db.json文件中;使用线程池同时处理多个请求,写入数据库文件需通过锁保护;客户端需实现从文件读取请求的功能,若传入-in参数及文件名,则从/client/data目录的对应文件读取请求。
代码实现
Client包代码
client.Main
package client; public class Main { public static void main(String[] args) { ClientInitializer clientInitializer = new ClientInitializer(args); clientInitializer.start(); } }
client.ClientInitializer
package client; import com.google.gson.Gson; import java.io.IOException; import java.nio.file.Paths; import java.util.Arrays; import java.util.List; import java.util.NoSuchElementException; import java.util.Scanner; public class ClientInitializer { private final List<String> args; public ClientInitializer(String[] args) { this.args = Arrays.asList(args); } @SuppressWarnings("CallToPrintStackTrace") public void start() { String input = ".\\client\\data\\" + getInput(); if (!input.equals(".\\client\\data\\")) { try (Scanner fileScanner = new Scanner(Paths.get(input))) { StringBuilder jsonBuilder = new StringBuilder(); while (fileScanner.hasNextLine()) { jsonBuilder.append(fileScanner.nextLine()); } String readRequest = jsonBuilder.toString(); Request request = new Request(new Gson().fromJson(readRequest, Request.class)); Client client = new Client(request); client.run(); return; } catch (IOException e) { e.printStackTrace(); } } String type = getType(); String key = getKey(); String value = getValue(); Request request = new Request(type, key, value); Client client = new Client(request); client.run(); } private String getInput() { try { int indexOfInput = args.indexOf("-in"); return indexOfInput == -1 ? "" : args.get(indexOfInput + 1); } catch (NullPointerException | NoSuchElementException e) { return ""; } } private String getType() { int indexOfType = args.indexOf("-t") + 1; return args.get(indexOfType); } private String getKey() { try { int indexOfKey = args.indexOf("-k"); return indexOfKey == -1 ? null : args.get(indexOfKey + 1); } catch (NullPointerException | NoSuchElementException e) { return null; } } private String getValue() { try { int indexOfCellIndex = args.indexOf("-v"); return indexOfCellIndex == -1 ? null : args.get(indexOfCellIndex + 1); } catch (NullPointerException | NoSuchElementException e) { return null; } } }
client.Request
package client; public class Request { private final String type; private final String key; private final String value; public Request(String type, String key, String value) { this.type = type; this.key = key; this.value = value; } public Request(Request request) { this.type = request.getType(); this.key = request.getKey(); this.value = request.getValue(); } public String getType() { return type; } public String getKey() { return key; } public String getValue() { return value; } }
client.Client
package client; import com.google.gson.Gson; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.net.Socket; public class Client { private static final String ADDRESS = "127.0.0.1"; private static final int PORT = 23456; private final Request request; public Client(Request request) { this.request = request; } @SuppressWarnings("CallToPrintStackTrace") public void run() { System.out.println("Client started!"); try (Socket socket = new Socket(ADDRESS, PORT); DataInputStream input = new DataInputStream(socket.getInputStream()); DataOutputStream output = new DataOutputStream(socket.getOutputStream()) ) { String sentJson = new Gson().toJson(request); output.writeUTF(sentJson); System.out.println("Sent: " + sentJson); if (socket.isOutputShutdown()) { System.out.println("Server closed the connection."); } else { try { String receivedJson = input.readUTF(); System.out.println("Received: " + receivedJson); } catch (EOFException e) { System.out.println("Server closed the connection unexpectedly."); } } } catch (IOException e) { e.printStackTrace(); } } }
Server包代码
server.Main
package server; public class Main { public static void main(String[] args) { Server server = new Server(); server.run(); } }
server.Server
package server; import client.Request; import com.google.gson.Gson; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class Server { private static final int PORT = 23456; private final Gson gson; private final ReadWriteLock lock; private final ExecutorService executor = Executors.newFixedThreadPool(4); private volatile boolean isRunning = true; public Server() { writeDbToFile(); this.gson = new Gson(); this.lock = new ReentrantReadWriteLock(); } @SuppressWarnings("CallToPrintStackTrace") public void run() { System.out.println("Server started!"); while (isRunning) { try (ServerSocket serverSocket = new ServerSocket(PORT)) { try (Socket acceptSocket = serverSocket.accept(); DataInputStream input = new DataInputStream(acceptSocket.getInputStream()); DataOutputStream output = new DataOutputStream(acceptSocket.getOutputStream()) ) { String receivedJson = input.readUTF(); if (receivedJson.contains("exit")) { executor.submit(() -> { handleExitRequest(output); }); isRunning = false; } else { executor.submit(() -> { handleRequest(receivedJson, output); }); } } } catch (IOException e) { e.printStackTrace(); } } } private void handleExitRequest(DataOutputStream output) { String sentJson = gson.toJson(new Response("OK")); try { output.writeUTF(sentJson); } catch (IOException e) { throw new RuntimeException("Server side IOException"); } } private void handleRequest(String receivedJson, DataOutputStream output) { Request request = gson.fromJson(receivedJson, Request.class); if (receivedJson.contains("get")) { Database db = new Database(); Lock readLock = lock.readLock(); String key = request.getKey(); try { output.writeUTF(gson.toJson(db.getInformation(key))); } catch (IOException e) { throw new RuntimeException(e); } readLock.unlock(); } else if (receivedJson.contains("set")) { Database db = new Database(); Lock writeLock = lock.writeLock(); String key = request.getKey(); String value = request.getValue(); try { output.writeUTF(gson.toJson(db.setInformation(key, value))); } catch (IOException e) { throw new RuntimeException(e); } writeDbToFile(); writeLock.unlock(); } else if (receivedJson.contains("delete")) { Database db = new Database(); Lock writeLock = lock.writeLock(); String key = request.getKey(); try { output.writeUTF(gson.toJson(db.deleteInformation(key))); } catch (IOException e) { throw new RuntimeException(e); } writeDbToFile(); writeLock.unlock(); } } @SuppressWarnings("CallToPrintStackTrace") private void writeDbToFile() { Database db = new Database(); try (FileWriter fileWriter = new FileWriter(System.getProperty("user.dir") + "/src/server/data/db.json", true)) { for (var entry : db.getDb().entrySet()) { fileWriter.write(entry.getKey() + ":" + entry.getValue() + "\n"); } } catch (IOException e) { e.printStackTrace(); } } }
server.Database
package server; import java.util.HashMap; import java.util.Map; public class Database { private final Map<String, String> db; private static final String successMessage = "OK"; private static final String errorMessage = "ERROR"; private static final String noSuchKeyMessage = "No such key"; public Database() { this.db = new HashMap<>(1000); } public Map<String, String> getDb() { return db; } public Response getInformation(String key) { return db.containsKey(key) ? new Response(successMessage, db.get(key), false) : new Response(errorMessage, noSuchKeyMessage); } public Response setInformation(String key, String value) { db.put(key, value); return new Response(successMessage); } public Response deleteInformation(String key) { if (db.containsKey(key)) { db.remove(key); return new Response(successMessage); } else { return new Response(errorMessage, noSuchKeyMessage); } } }
server.Response
package server; public class Response { private final String response; private final String reason; private final String value; public Response(String response) { this.response = response; this.reason = null; this.value = null; } public Response(String response, String reason) { this.response = response; this.reason = reason; this.value = null; } public Response(String response, String value, boolean isReason) { this.response = response; this.value = value; this.reason = null; } }
自动化测试结果
Start test 1 Server started! Client started! Sent: {"type":"exit"} Server closed the connection unexpectedly. Start test 2 Start test 3 Server started! Client started! Sent: {"type":"get","key":"1"} Server closed the connection unexpectedly.
问题说明
客户端无法读取服务端发送的响应,怀疑是多线程结构设计或锁的使用不当导致(比如线程提前关闭服务器),但缺乏多线程经验无法确定问题根源。目前文件读写功能正常,尝试参考网上方案调整代码也未解决问题。
内容的提问来源于stack exchange,提问作者sinemeister
相关产品推荐
相关产品推荐

