Java Socket上的Read操作阻塞問題詳解
Socket上的Read操作阻塞問題
從Socket上讀取對端發過來的數據一般有兩種方法
1)按照字節流讀取
BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); int r = -1; List<Byte> l = new LinkedList<Byte>(); while ((r = in.read()) != -1) { l.add(Byte.valueOf((byte) r)); }
2)按照字符流讀取
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); String s; while ((s = in.readLine()) != null) { System.out.println("Reveived: " + s); }
這兩個方法read()和readLine()都會讀取對端發送過來的數據,如果無數據可讀,就會阻塞直到有數據可讀。或者到達流的末尾,這個時候分別返回-1和null。
這個特性使得編程非常方便也很高效。
但是這樣也有一個問題,就是如何讓程序從這兩個方法的阻塞調用中返回。
總結一下,有這麼幾個方法
1)發送完後調用Socket的shutdownOutput()方法關閉輸出流,這樣對端的輸入流上的read操作就會返回-1。
註意不能調用socket.getInputStream().close()。這樣會導致socket被關閉。
當然如果不需要繼續在socket上進行讀操作,也可以直接關閉socket。
但是這個方法不能用於通信雙方需要多次交互的情況。
2)發送數據時,約定數據的首部固定字節數為數據長度。這樣讀取到這個長度的數據後,就不繼續調用read方法。
3)為瞭防止read操作造成程序永久掛起,還可以給socket設置超時。
如果read()方法在設置時間內沒有讀取到數據,就會拋出一個java.net.SocketTimeoutException異常。
例如下面的方法設定超時3秒。
socket.setSoTimeout(3000);
Socket編程—read方法阻塞問題
java通信項目簡單寫瞭個聊天室,實現群聊私聊瞭,就大言不慚地往簡歷上寫瞭對java網絡編程和多線程有瞭一定的瞭解。給客戶端各自開瞭線程,寫瞭句server.accept()、Socket client=new Socket(“127.0.0.1”,9999),就叫瞭解瞭? Too young too simple.
然而一問,BIO和NIO有什麼區別?— 納尼?什麼玩意兒?
- 那你說說你那個聊天室的流? — 臥槽,這有什麼好說的,get啊,輸入流用來讀的,輸出流用來寫數據給對方的
- 可能知道我沒get到點吧,那你自己說一下你的通信項目吧 — 哈哈,先創建一個ServerSocket對象,然後accept等客戶端來連,給每個客戶端都開一個線程各自處理
- 奧,你是給每個客戶端都開瞭一個線程啊============ 結束會話
聊完瞭啊,可是我並不知道發生瞭什麼,這特麼到底要問啥啊。現在一想,可能對方覺得我特麼就是傻缺吧,一個隻會碼而沒有思想的人。
那不管,先不說BIO和NIO,通過這個,我倒是好像get到他是不是在跟我說流的阻塞問題。那麼,總結一下吧。
講文件流的時候,我們會看到這兩個read方法,看一下API
如果隻是使用一次,那麼沒有數據讀的時候,會一直阻塞,然後想執行下面那是不可能的瞭,直到有數據可讀;
如果用在while裡,那麼沒讀到文件末尾,也是一直阻塞的,直到被返回-1。
文件它是自己知道讀到文件末尾瞭,那麼我們的Socket是兩端的通信,一直等待著對方傳來數據的,並不知道啥時候會完,所以就會一直不會等於-1,阻塞在while循環裡瞭,下面的代碼就不會被執行。
package SocketIO; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { String m = ""; ServerSocket ss = new ServerSocket(9999); Socket s = ss.accept(); String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+"...connected..."); m = ip+"...connected..."; InputStream in = s.getInputStream(); int len = 0; byte[] buf = new byte[1024]; while((len=in.read(buf))!=-1){ m += new String(buf); } System.out.println(m); OutputStream out = s.getOutputStream(); out.write("飯菜馬上到".getBytes()); out.flush(); in.close(); out.close(); s.close(); ss.close(); } }
package SocketIO; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws UnknownHostException, IOException { Socket s = new Socket("127.0.0.1", 9999); OutputStream out = s.getOutputStream(); out.write("我肚子餓瞭".getBytes()); out.flush(); String m = ""; InputStream in = s.getInputStream(); byte[] buf = new byte[1024]; int len = 0; while((len=in.read(buf))!=-1){ m += new String(buf); } System.out.println(m); in.close(); out.close(); s.close(); } }
當客戶端連接上服務器時,把“我肚子餓瞭”寫給服務器,服務器讀的時候不知道客戶端發沒發完,就一直阻塞在while循環裡,故服務器的console隻會輸出
,
而客戶端的console什麼也不會輸出
那麼,我們隻用加一句,
客戶端寫完數據後,就直接把輸出流shutdown,那麼服務器讀到的就會是-1,跳出循環,繼續往下執行。那為什麼服務器寫完數據後不把輸出流shutdown,你沒看見所有的流都close瞭嗎?客戶端當然會讀到-1,進而輸出我們想看到的東東瞭。如果你還不服,你把close都註釋掉,看看效果(都註釋掉瞭客戶端讀的時候又阻塞瞭),再加上s.shutdownOutput()再看看效果(這會又恢復正常瞭)。
這個例子其實隻是想說明socket編程中流的read方法是阻塞的。
那麼,回到我們的聊天室來,我一個服務器要處理這麼多個客戶端,如果一個客戶端的read方法阻塞瞭,那別的客戶端不都得等著它嗎?顯然,這是不可能的。那,一個客戶端給一個線程吧,讓它們自己阻塞自己的。
對於服務器端而言,給每個客戶端啟動一個線程,然後在每個客戶端各自的線程裡循環去讀客戶端發來的數據,沒的話阻塞等待直到有,有的話轉發,所以阻塞在循環裡也無所謂,反正while循環之外我暫時也沒想著執行。當然,還是要把它結束掉。
對於服務器端,先讀後轉發,嵌在一個循環裡,不會有什麼問題。
而對於客戶端而言,我要隨時監聽去讀,又想著隨時去寫。那寫的操作不能在讀的循環裡瞭啊,隻能寫在外面。而讀的操作又是一直阻塞著的,豈會讓出時間讓你寫?那,大招來瞭。你把讀的操作放一個線程裡不就得瞭,由它去阻塞,想怎麼讀就怎麼讀。那互不影響的話,我也想怎麼寫就怎麼寫啊。
寫的操作我就不截圖瞭,就是在主線程裡完成的。
以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。