關於Java中Socket通訊時使用ObjectInputStream與ObjectOutputStream的順序問題
阿新 • • 發佈:2018-12-23
在Java中使用Socket與ServerSocket建立客戶機和伺服器時,若採用ObjectInputStream與ObjectOutputStream建立通訊,則需要注意兩個流的順序。否則會發生兩方互相等待導致死鎖。
下面通過一個例子來證明:
伺服器:
package com.gary; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.ServerSocket; import java.net.Socket; public class Server implements Runnable { private ServerSocket serverSocket; private int port; private boolean goon; private ObjectInputStream ois; private ObjectOutputStream oos; public Server() { goon = false; } public Server(int port) { this(); this.port = port; } public void setPort(int port) { this.port = port; } public void stopServer() { this.goon = false; try { this.serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } public void startServer() throws Exception { if (this.port == 0) { // TODO 拋異常 return; } this.serverSocket = new ServerSocket(port); this.goon = true; new Thread(this, "SERVER").start(); } @Override public void run() { while(goon) { try { Socket client = serverSocket.accept(); ois = new ObjectInputStream(client.getInputStream()); oos = new ObjectOutputStream(client.getOutputStream()); oos.writeObject(new String("去死")); String str = (String) ois.readObject(); System.out.println("伺服器收到客戶端[" + client.getInetAddress().getHostAddress() + "]訊息:" + str); } catch (IOException e) { goon = false; e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } stopServer(); } }
客戶機:
package com.gary; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; public class Client { private String serverIp; private int serverPort; public Client(String serverIp, int serverPort) { this.serverIp = serverIp; this.serverPort = serverPort; } private void closeSocket(ObjectInputStream ois, ObjectOutputStream oos, Socket socket) { try { if (ois != null) { ois.close(); } } catch (IOException e) { e.printStackTrace(); } finally { ois = null; } try { if (oos != null) { oos.close(); } } catch (IOException e) { e.printStackTrace(); } finally { oos = null; } try { if (socket != null && !socket.isClosed()) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } finally { socket = null; } } public void start() throws Exception { Socket socket = new Socket(serverIp, serverPort); ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); oos.writeObject(new String("好的")); Object result = ois.readObject(); System.out.println("client收到:" + result); closeSocket(ois, oos, socket); } }
測試:
package com.gary; public class TestServer { public static void main(String[] args) { Server rpcServer = new Server(); rpcServer.setPort(54189); try { rpcServer.startServer(); } catch (Exception e) { e.printStackTrace(); } } } package com.gary; public class TestClient { public static void main(String[] args) { Client client = new Client("172.21.126.74", 54189); try { client.start(); } catch (Exception e) { e.printStackTrace(); } } }
結果
分析原因:
仔細看ObjectInputStream的API
第一段的意思是,建立一個從指定的InputStream讀取的ObjectInputStream,序列化的流的頭是從這個Strem中讀取並驗證的。此構造方法會一直阻塞直到相應的ObjectOutputStream已經寫入並重新整理頭。
所以上述程式碼執行後會都阻塞,如果將建立ObjectInputStream的順序修改成其他的順序,便可正常通訊。