1. 程式人生 > >Socket關於設定連線超時

Socket關於設定連線超時

做網路程式設計的人對setSoTimeout方法一定很熟悉,都知道是設定連線的超時時間!

但是我在網上找資料時發現很多人把這個超時時間理解成了鏈路的超時時間!我看了一下JDK 關於這個方法的說明,其實根本不是鏈路的超時時間!

Java程式碼  收藏程式碼
  1. setSoTimeout  
  2. publicvoid setSoTimeout(int timeout)  
  3.     throws SocketException啟用/禁用帶有指定超時值的 SO_TIMEOUT,以毫秒為單位。將此選項設為非零的超時值時,在與此 Socket 關聯的 InputStream 上呼叫 read() 將只阻塞此時間長度。  
  4.     如果超過超時值,將引發 java.net.SocketTimeoutException,雖然 Socket 仍舊有效。選項必須在進入阻塞操作前被啟用才能生效。  
  5.     超時值必須是 > 0 的數。超時值為 0 被解釋為無窮大超時值。   
  6. 引數:  
  7. timeout - 指定的以毫秒為單位的超時值。   
  8. 丟擲:   
  9. SocketException - 如果底層協議出現錯誤,例如 TCP 錯誤。  
  10. 從以下版本開始:   
  11. JDK 1.1
  12. 另請參見:  
  13. getSoTimeout()  
setSoTimeout
public void setSoTimeout(int timeout)
	throws SocketException啟用/禁用帶有指定超時值的 SO_TIMEOUT,以毫秒為單位。將此選項設為非零的超時值時,在與此 Socket 關聯的 InputStream 上呼叫 read() 將只阻塞此時間長度。
	如果超過超時值,將引發 java.net.SocketTimeoutException,雖然 Socket 仍舊有效。選項必須在進入阻塞操作前被啟用才能生效。
	超時值必須是 > 0 的數。超時值為 0 被解釋為無窮大超時值。 
引數:
timeout - 指定的以毫秒為單位的超時值。 
丟擲: 
SocketException - 如果底層協議出現錯誤,例如 TCP 錯誤。
從以下版本開始: 
JDK 1.1 
另請參見:
getSoTimeout()

其實說白了他只是read方法的超時時間,這個方法是堵塞的!

寫個小例子驗證一下:

服務端,收到一個請求後處理,但是隻處理一個請求,處理完畢後結束:

Java程式碼  收藏程式碼
  1. package socket;  
  2. import java.io.ByteArrayOutputStream;  
  3. import java.io.InputStream;  
  4. import java.net.InetSocketAddress;  
  5. import java.net.ServerSocket;  
  6. import java.net.Socket;  
  7. import java.net.SocketAddress;  
  8. import
     java.net.SocketException;  
  9. import java.net.SocketTimeoutException;  
  10. import java.text.SimpleDateFormat;  
  11. import java.util.Arrays;  
  12. import java.util.Date;  
  13. publicclass SocketService {  
  14.     publicstaticvoid main(String[] args) {  
  15.         try {  
  16.             SocketAddress address = new InetSocketAddress("192.168.9.155"30001);  
  17.             // 啟動監聽埠 8001
  18.             ServerSocket ss = new ServerSocket();  
  19.             ss.bind(address);  
  20.             // 接收請求
  21.             Socket s = ss.accept();  
  22.             new Thread(new T(s)).start();  
  23.         } catch (Exception e) {  
  24.             e.printStackTrace();  
  25.         }  
  26.     }  
  27. }  
  28. class T implements Runnable {  
  29.     publicvoid run() {  
  30.         try {  
  31.             System.out.println(socket.toString());  
  32.             socket.setKeepAlive(true);  
  33.             socket.setSoTimeout(5 * 1000);  
  34.             String _pattern = "yyyy-MM-dd HH:mm:ss";  
  35.             SimpleDateFormat format = new SimpleDateFormat(_pattern);  
  36.             while (true) {  
  37.                 System.out.println("開始:" + format.format(new Date()));  
  38.                 try {  
  39.                     InputStream ips = socket.getInputStream();  
  40.                     ByteArrayOutputStream bops = new ByteArrayOutputStream();  
  41.                     int data = -1;  
  42.                     while((data = ips.read()) != -1){  
  43.                         System.out.println(data);  
  44.                         bops.write(data);  
  45.                     }  
  46.                     System.out.println(Arrays.toString(bops.toByteArray()));  
  47.                 }catch(SocketTimeoutException e){  
  48.                     e.printStackTrace();  
  49.                 }catch(SocketException e){  
  50.                     e.printStackTrace();  
  51.                 } catch (Exception e) {  
  52.                     e.printStackTrace();  
  53.                 }  
  54.                 Thread.sleep(1000);  
  55.                 System.out.println(socket.isBound()); // 是否邦定
  56.                 System.out.println(socket.isClosed()); // 是否關閉
  57.                 System.out.println(socket.isConnected()); // 是否連線
  58.                 System.out.println(socket.isInputShutdown()); // 是否關閉輸入流
  59.                 System.out.println(socket.isOutputShutdown()); // 是否關閉輸出流
  60.                 System.out.println("結束:" + format.format(new Date()));  
  61.             }  
  62.         } catch (Exception e) {  
  63.             e.printStackTrace();  
  64.         }  
  65.     }  
  66.     private Socket socket = null;  
  67.     public T(Socket socket) {  
  68.         this.socket = socket;  
  69.     }  
  70.     public Socket getSocket() {  
  71.         return socket;  
  72.     }  
  73.     publicvoid setSocket(Socket socket) {  
  74.         this.socket = socket;  
  75.     }  
  76. }  
package socket;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class SocketService {
	public static void main(String[] args) {
		try {
			SocketAddress address = new InetSocketAddress("192.168.9.155", 30001);
			// 啟動監聽埠 8001
			ServerSocket ss = new ServerSocket();
			ss.bind(address);
			// 接收請求
			Socket s = ss.accept();
			new Thread(new T(s)).start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
class T implements Runnable {
	public void run() {
		try {
			System.out.println(socket.toString());
			socket.setKeepAlive(true);
			socket.setSoTimeout(5 * 1000);
			String _pattern = "yyyy-MM-dd HH:mm:ss";
			SimpleDateFormat format = new SimpleDateFormat(_pattern);
			while (true) {
				System.out.println("開始:" + format.format(new Date()));
				try {
					InputStream ips = socket.getInputStream();
					ByteArrayOutputStream bops = new ByteArrayOutputStream();
					int data = -1;
					while((data = ips.read()) != -1){
						System.out.println(data);
						bops.write(data);
					}
					System.out.println(Arrays.toString(bops.toByteArray()));
				}catch(SocketTimeoutException e){
					e.printStackTrace();
				}catch(SocketException e){
					e.printStackTrace();
				} catch (Exception e) {
					e.printStackTrace();
				}
				Thread.sleep(1000);
				System.out.println(socket.isBound()); // 是否邦定
				System.out.println(socket.isClosed()); // 是否關閉
				System.out.println(socket.isConnected()); // 是否連線
				System.out.println(socket.isInputShutdown()); // 是否關閉輸入流
				System.out.println(socket.isOutputShutdown()); // 是否關閉輸出流
				System.out.println("結束:" + format.format(new Date()));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	private Socket socket = null;
	public T(Socket socket) {
		this.socket = socket;
	}
	public Socket getSocket() {
		return socket;
	}
	public void setSocket(Socket socket) {
		this.socket = socket;
	}
}

 第一個客戶端,連線後一直保持連線物件的存活,但是不傳送資料,服務端列印:

Java程式碼  收藏程式碼
  1. package socket;  
  2. import java.net.Socket;  
  3. publicclass Client {  
  4.     publicstaticvoid main(String[] args) {  
  5.         try {  
  6.             Socket socket = new Socket("192.168.9.155"30001);  
  7.             socket.setKeepAlive(true);  
  8.             while(true && null != socket){  
  9.                 Thread.sleep(10 * 1000);  
  10.             }  
  11.         } catch (Exception e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.     }  
  15. }  
package socket;
import java.net.Socket;
public class Client {
	public static void main(String[] args) {
		try {
			Socket socket = new Socket("192.168.9.155", 30001);
			socket.setKeepAlive(true);
			while(true && null != socket){
				Thread.sleep(10 * 1000);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

列印如下,可以看到鏈路一直是活的,間隔超時時間的間隔就列印一組異常資訊: 

Java程式碼  收藏程式碼
  1. Socket[addr=/192.168.9.155,port=3017,localport=30001]  
  2. 開始:2012-11-1411:15:30
  3. java.net.SocketTimeoutException: Read timed out  
  4.     at java.net.SocketInputStream.socketRead0(Native Method)  
  5.     at java.net.SocketInputStream.read(Unknown Source)  
  6.     at java.net.SocketInputStream.read(Unknown Source)  
  7.     at socket.T.run(SocketService.java:42)  
  8.     at java.lang.Thread.run(Unknown Source)  
  9. true
  10. false
  11. true
  12. false
  13. false
  14. 結束:2012-11-1411:15:36
  15. 開始:2012-11-1411:15:36
  16. java.net.SocketTimeoutException: Read timed out  
  17.     at java.net.SocketInputStream.socketRead0(Native Method)  
  18.     at java.net.SocketInputStream.read(Unknown Source)  
  19.     at java.net.SocketInputStream.read(Unknown Source)  
  20.     at socket.T.run(SocketService.java:42)  
  21.     at java.lang.Thread.run(Unknown Source)  
  22. true
  23. false
  24. true
  25. false
  26. false
  27. 結束:2012-11-1411:15:42
  28. 開始:2012-11-1411:15:42
Socket[addr=/192.168.9.155,port=3017,localport=30001]
開始:2012-11-14 11:15:30
java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.read(Unknown Source)
	at java.net.SocketInputStream.read(Unknown Source)
	at socket.T.run(SocketService.java:42)
	at java.lang.Thread.run(Unknown Source)
true
false
true
false
false
結束:2012-11-14 11:15:36
開始:2012-11-14 11:15:36
java.net.SocketTimeoutException: Read timed out
	at java.net.SocketInputStream.socketRead0(Native Method)
	at java.net.SocketInputStream.read(Unknown Source)
	at java.net.SocketInputStream.read(Unknown Source)
	at socket.T.run(SocketService.java:42)
	at java.lang.Thread.run(Unknown Source)
true
false
true
false
false
結束:2012-11-14 11:15:42
開始:2012-11-14 11:15:42

 然後我們編寫一個客戶端,連線後馬上關閉連線,也不傳送任何資料:

Java程式碼  收藏程式碼
  1. package socket;  
  2. import java.net.Socket;  
  3. publicclass Client {  
  4.     publicstaticvoid main(String[] args) {  
  5.         try {  
  6.             Socket socket = new Socket("192.168.9.155"30001);  
  7.             socket.setKeepAlive(true);  
  8.         } catch (Exception e) {  
  9.             e.printStackTrace();  
  10.         }  
  11.     }  
  12. }  
package socket;
import java.net.Socket;
public class Client {
	public static void main(String[] args) {
		try {
			Socket socket = new Socket("192.168.9.155", 30001);
			socket.setKeepAlive(true);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

列印如下:

Java程式碼  收藏程式碼
  1. 開始:2012-11-1411:17:42
  2. java.net.SocketException: Connection reset  
  3.     at java.net.SocketInputStream.read(Unknown Source)  
  4.     at java.net.SocketInputStream.read(Unknown Source)  
  5.     at socket.T.run(SocketService.java:42)  
  6.     at java.lang.Thread.run(Unknown Source)  
  7. true
  8. false
  9. true
  10. false
  11. false
  12. java.net.SocketException: Connection reset  
  13.     at java.net.SocketInputStream.read(Unknown Source)  
  14.     at java.net.SocketInputStream.read(Unknown Source)  
  15.     at socket.T.run(SocketService.java:42)  
  16.     at java.lang.Thread.run(Unknown Source)  
  17. 結束:2012-11-1411:17:43
  18. 開始:2012-11-1411:17:43
  19. true
  20. false
  21. true
  22. false
  23. false
  24. 結束:2012-11-1411:17:44
開始:2012-11-14 11:17:42
java.net.SocketException: Connection reset
	at java.net.SocketInputStream.read(Unknown Source)
	at java.net.SocketInputStream.read(Unknown Source)
	at socket.T.run(SocketService.java:42)
	at java.lang.Thread.run(Unknown Source)
true
false
true
false
false
java.net.SocketException: Connection reset
	at java.net.SocketInputStream.read(Unknown Source)
	at java.net.SocketInputStream.read(Unknown Source)
	at socket.T.run(SocketService.java:42)
	at java.lang.Thread.run(Unknown Source)
結束:2012-11-14 11:17:43
開始:2012-11-14 11:17:43
true
false
true
false
false
結束:2012-11-14 11:17:44

異常是不一樣的,不一樣的還有,如果是超時,則五秒鐘迴圈一次,然後是連線中斷,則不在迴圈馬上再報錯,因為連線已經掛了!但是列印這個連線還是有效的,這個我也不知道怎麼回事!

所以,如果大家理解為超時時間內沒有資料連線就自動關閉或失效,那麼這個理解就非常有問題了!

socket的讀寫是阻塞的,soTimeout是socket讀寫超時,而不是連結超時。
connection reset是由於在一個半關閉的connection上讀取資料導致,client已經關閉但是server端沒有關閉。

http://xiaoz5919.iteye.com/blog/1685138