關於java socket中的read方法阻塞問題
前幾天一個有個同學諮詢我關於java socket程式設計的一些問題,因為我這個同學今年剛從.NET轉到java 對於java的IO體系不是很清楚,在給他解答一些問題時我自己也總結了比較容易出錯的問題。
我們直接貼一段socket程式碼看一下
客戶端: public class SocketClient { public static void main(String[] args) throws UnknownHostException, IOException, InterruptedException { Socket client = new Socket("localhost",8888); OutputStream out = client.getOutputStream(); InputStream input = client.getInputStream(); out.write("sender say hello socket".getBytes()); out.flush(); read(input); out.close(); } public static void read(InputStream input) throws IOException { byte[] buf = new byte[128]; int size = 0; while ((size = input.read(buf,0,buf.length)) != -1) { System.out.print(new String(buf)); } } } 服務端: public class SocketServer { public static void main(String[] args) { SocketServer ss = new SocketServer(); int port = 8888; try { ss.startServer(port); } catch (Exception e) { e.printStackTrace(); } } public void startServer(int port) throws Exception { ServerSocket serverSocket = new ServerSocket(port); Socket server = null; try { while (true) { server = serverSocket.accept(); System.out.println("server socket is start……"); try { BufferedReader input = new BufferedReader(new InputStreamReader(new ByteArrayInputStream("服務端發給客戶端的資訊".getBytes()))); BufferedInputStream in = new BufferedInputStream(server.getInputStream()); PrintWriter out = newPrintWriter(newOutputStreamWriter(server.getOutputStream())); String clientstring = null; out.println("歡迎客戶端連入"); out.flush(); byte[] buf = new byte[128]; int size = 0; while (( size = in.read(buf,0,buf.length)) != -1) { System.out.println(new String(buf)); } out.print(" client is over"); out.flush(); out.close(); System.out.println(" client is over"); } catch (Exception e) { e.printStackTrace(); } finally { server.close(); } } } catch (Exception e) { e.printStackTrace(); } finally { serverSocket.close(); } } }
這段程式碼執行以後會發現server類 read()方法發生了阻塞,經過查詢資料發現 read() 是一個阻塞函式,如果客戶端沒有宣告斷開outputStream那麼它就會認為客戶端仍舊可能傳送資料,像read()這種阻塞讀取函式還有 BufferedReader 類種的 readLine()、DataInputStream種的readUTF()等。
下面我們來看下一些解決方案
1)Socket任意一端在呼叫完write()方法時呼叫shutdownOutput()方法關閉輸出流,這樣對端的inputStream上的read操作就會返回-1, 這裡我們要注意下 不能呼叫socket.getInputStream().close()。因為它會導致socket直接被關閉。 當然如果不需要繼續在socket上進行讀操作,也可以直接關閉socket。但是這個方法不能用於通訊雙方需要多次互動的情況。
out.write("sender say hello socket".getBytes()); out.flush(); client.shutdownOutput();//呼叫shutdown 通知對端請求完畢
這個解決方案缺點非常明顯,socket任意一端都依賴於對方呼叫 shutdownOutput()來完成read返回 -1,如果任意一方沒有執行shutdown函式那麼就會出現問題。所以一般我們都會在socket請求時設定連線的超時時間 socket.setSoTimeout(5000);以防止長時間沒有響應造成系統癱瘓。
while (true) { server = serverSocket.accept(); System.out.println("server socket is start……"); server.setSoTimeout(5000); ..... }
2)傳送資料時,約定資料的首部固定位元組數為資料長度。這樣讀取到這個長度的資料後,就不繼續呼叫read方法
這種方式優點是不依賴對方呼叫shutdown函式,響應較快,缺點是資料傳輸是最大位元組數固定,需雙方事先約定好長度,伸縮性差。
3)傳送資料時,約定前幾位返回資料byte[]長度大小或最後輸出 \n 或 \r 作為資料傳輸終止符。
客戶端 out.write("sender say hello socket \n".getBytes()); out.flush(); 伺服器端 byte[] buf = new byte[1]; int size = 0; StringBuffer sb = new StringBuffer(); while (( size = in.read(buf,0,buf.length)) != -1) { String str = new String(buf); if(str.equals("\n")) { break; } sb.append(str); System.out.print(str); }
這種方式是對第二種方案的改良版,但不得不說 這是目前socket資料傳輸的最常用處理read()阻塞的解決方案。