1 超時java
套接字底層是基於TCP的,因此socket的超時和TCP超時是相同的。下面先討論套接字讀寫緩衝區,接着討論鏈接創建超時、讀寫超時以及JAVA套接字編程的嵌套異常捕獲和一個超時例子程序的抓包示例。編程
一旦建立了一個套接字實例,操做系統就會爲其分配緩衝區以存放接收和要發送的數據。網絡
(1)socket 讀寫緩衝區app
JAVA能夠設置讀寫緩衝區的大小-setReceiveBufferSize(int size), setSendBufferSize(int size)。向輸出流寫數據並不意味着數據實際上已經被髮送,它們只是被複制到了發送緩衝區隊列SendQ,就是在Socket的OutputStream上調用flush()方法,也不能保證數據可以當即發送到網絡。真正的數據發送是由操做系統的TCP協議棧模塊從緩衝區中取數據發送到網絡來完成的。當有數據從網絡來到時,TCP協議棧模塊接收數據並放入接收緩衝區隊列RecvQ,輸入流InputStream經過read方法從RecvQ中取出數據。socket
(2)socket鏈接創建超時spa
socket鏈接創建是基於TCP的鏈接創建過程。TCP的鏈接須要經過3次握手報文來完成,開始創建TCP鏈接時須要發送同步SYN報文,而後等待確認報文SYN+ACK,最後再發送確認報文ACK。TCP鏈接的關閉經過4次揮手來完成,主動關閉TCP鏈接的一方發送FIN報文,等待對方的確認報文;被動關閉的一方也發送FIN報文,然等待確認報文。操作系統
正在等待TCP鏈接請求的一端有一個固定長度的鏈接隊列,該隊列中的鏈接已經被TCP接受(即三次握手已經完成),但尚未被應用層所接受。TCP接受一個鏈接是將其放入這個鏈接隊列,而應用層接受鏈接是將其從該隊列中移出。應用層能夠經過設置backlog變量來指明該鏈接隊列的最大長度,即已被TCP接受而等待應用層接受的最大鏈接數。當一個鏈接請求SYN到達時,TCP肯定是否接受這個鏈接。若是隊列中還有空間,TCP模塊將對SYN進行確認並完成鏈接的創建。但應用層只有在三次握手中的第三個報文收到後纔會知道這個新鏈接。若是隊列沒有空間,TCP將不理會收到的SYN。若是應用層不能及時接受已被TCP接受的鏈接,這些鏈接可能佔滿整個鏈接隊列,新的鏈接請求可能不被響應而會超時。若是一個鏈接請求SYN發送後,一段時間後沒有收到確認SYN+ACK,TCP會重傳這個鏈接請求SYN兩次,每次重傳的時間間隔加倍,在規定的時間內仍沒有收到SYN+ACK,TCP將放棄這個鏈接請求,鏈接創建就超時了。.net
JAVA Socket鏈接創建超時和TCP是相同的,若是TCP創建鏈接時三次握手超時,那麼致使Socket鏈接創建也就超時了。能夠設置Socket鏈接創建的超時時間-線程
connect(SocketAddress endpoint, int timeout)code
若是在timeout內,鏈接沒有創建成功,在TimeoutException異常被拋出。若是timeout的值小於三次握手的時間,那麼Socket鏈接永遠也不會創建。
import java.net.*; import java.io.*; public class SocketClientTest { public static final int PORT = 8088; public static void main( String[] args ) throws Exception { InetAddress addr = InetAddress.getByName( "127.0.0.1" ); Socket socket = new Socket(); try { socket.connect( new InetSocketAddress( addr, PORT ), 30000 ); socket.setSendBufferSize(100); BufferedWriter out = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) ); int i = 0; while( true ) { System.out.println( "client sent --- hello *** " + i++ ); out.write( "client sent --- hello *** " + i ); out.flush(); Thread.sleep( 1000 ); } } finally { socket.close(); } } }
(3)socket 讀超時
若是輸入緩衝隊列RecvQ中沒有數據,read操做會一直阻塞而掛起線程,直到有新的數據到來或者有異常產生。調用setSoTimeout(int timeout)能夠設置超時時間,若是到了超時時間仍沒有數據,read會拋出一個SocketTimeoutException,程序須要捕獲這個異常,可是當前的socket鏈接仍然是有效的。若是對方進程崩潰、對方機器忽然重啓、網絡斷開,本端的read會一直阻塞下去,這時設置超時時間是很是重要的,不然調用read的線程會一直掛起。TCP模塊把接收到的數據放入RecvQ中,直到應用層調用輸入流的read方法來讀取。若是RecvQ隊列被填滿了,這時TCP會根據滑動窗口機制通知對方不要繼續發送數據,本端中止接收從對端發送來的數據,直到接收者應用程序調用輸入流的read方法後騰出了空間。
假設咱們須要控制咱們的客戶端在開始讀取數據10秒後尚未讀到數據就中斷阻塞的話咱們能夠這樣作:
public class Client { public static void main(String args[]) throws Exception { //爲了簡單起見,全部的異常都直接往外拋 String host = "127.0.0.1"; //要鏈接的服務端IP地址 int port = 8899; //要鏈接的服務端對應的監聽端口 //與服務端創建鏈接 Socket client = new Socket(host, port); //創建鏈接後就能夠往服務端寫數據了 Writer writer = new OutputStreamWriter(client.getOutputStream()); writer.write("Hello Server."); writer.write("eof\n"); writer.flush(); //寫完之後進行讀操做 BufferedReader br = new BufferedReader(new InputStreamReader(client.getInputStream())); //設置超時間爲10秒 client.setSoTimeout(10*1000); StringBuffer sb = new StringBuffer(); String temp; int index; try { while ((temp=br.readLine()) != null) { if ((index = temp.indexOf("eof")) != -1) { sb.append(temp.substring(0, index)); break; } sb.append(temp); } } catch (SocketTimeoutException e) { System.out.println("數據讀取超時。"); } System.out.println("from server: " + sb); writer.close(); br.close(); client.close(); } }
2 斷開鏈接
TCP Socket鏈接是雙向的,經過四次揮手的方式斷開,雙方分別調用Socket.close()
方法斷開鏈接。鏈接斷開的過程當中,通常一方A先斷開鏈接,另外一方B發現A斷開鏈接後,也斷開鏈接。爲方便表述,將先斷開鏈接的一方A稱爲「主動斷開鏈接」;後斷開的一方B,則爲「被動斷開鏈接」。
在一方B阻塞執行in.readUTF()
方法時,若是對方A主動斷開Socket鏈接,這個方法會拋出異常。從而在B處理異常時,能夠被動的斷開這邊的鏈接。
爲保證主動斷開鏈接的一方不會阻塞在in.readUTF()
方法中,須要先執行socket.shutdownInput()
。因此主動斷開鏈接的代碼以下。
socket.shutdownInput();
in.close();
socket.close();
被動斷開鏈接的一方,在捕獲到in.readUTF()
的異常後,斷開Socket鏈接。
try { String s = in.readUTF(); } catch (IOException e) { // 鏈接被斷開(被動) try { in.close(); socket.close(); in = null; socket = null; } catch (IOException e) { e.printStackTrace(); } }