伺服端程式碼中:
System.out.println(is.readLine()); // 第一次讀取
Runtime.getRuntime().exec(is.readLine()); // 第二次讀取 → 永久阻塞
執行到 exec() 時程式停住,無錯誤、無後續輸出。
BufferedReader.readLine() 會消耗輸入流資料。第一次呼叫已將指令字串(如 "cmd /k start E://test.txt")讀走並列印,第二次呼叫時流中已無資料,導致 readLine() 陷入「等待新資料」的阻塞狀態,exec() 根本未被呼叫。
String command = is.readLine(); // 唯一讀取一次
System.out.println("Received: " + command);
Runtime.getRuntime().exec(command); // 使用已讀取的變數
| 問題 | 修正 |
|---|---|
String line = null; 直接進入 while(!line.equals("bye")) 拋 NPE | 初始化 String line = ""; 或改用 while(true) 內部 break |
使用 BufferedReader 需客戶端送 \r\n 才能讀取完整行 | 改用 InputStream.read() 讀取原始位元組,或協定約定換行符 |
未處理 exec() 回傳的 Process 物件 | 取得 Process p = Runtime.getRuntime().exec(...),讀取 p.getInputStream()/getErrorStream() 避免緩衝區滿死鎖 |
public class Server {
public static void main(String[] args) {
try (ServerSocket sersoc = new ServerSocket(8399);
Socket socket = sersoc.accept();
BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter os = new PrintWriter(socket.getOutputStream(), true);
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in))) {
String command = is.readLine(); // 讀取指令
System.out.println("Command: " + command);
if (command != null) {
Process p = Runtime.getRuntime().exec(command);
// 非同步消耗輸出/錯誤流,防止死鎖
new Thread(() -> consume(p.getInputStream())).start();
new Thread(() -> consume(p.getErrorStream())).start();
p.waitFor();
}
String line;
while ((line = sin.readLine()) != null) {
os.println(line);
System.out.println("Server: " + line);
String clientMsg = is.readLine();
if (clientMsg == null || "bye".equalsIgnoreCase(clientMsg)) break;
System.out.println("Client: " + clientMsg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
static void consume(InputStream is) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
String l; while ((l = br.readLine()) != null) System.out.println(l);
} catch (IOException ignored) {}
}
}
掌握「一次讀取、存變數、再使用」原則,並妥善處理 Process 流與阻塞邊界,即可避免 Runtime.exec() 卡死陷阱。