Java socket有以下兩種timeout:html
當不設置該參數時,指客戶端請求和服務端創建tcp鏈接時,會一直阻塞直到鏈接創建成功,或拋異常。當設置了connectTimeout, 客戶端請求和服務端創建鏈接時,阻塞時間超過connectTimeout時,就會拋出異常java.net.ConnectException: Connection timed out: connect。java
咱們看以下精簡後的代碼,首先是服務端:mysql
serverSocket = new ServerSocket(8080); Socket socket = serverSocket.accept();
服務端開啓ServerSocket監聽8080端口,再看客戶端:redis
socket = new Socket(); socket.connect(new InetSocketAddress("localhost", 8080)); System.out.println("Connected.");
打印「Connected.」,修改客戶端代碼中的主機名爲一個不存在的主機:sql
socket = new Socket(); long t1 = 0; try { t1 = System.currentTimeMillis(); socket.connect(new InetSocketAddress("www.ss.ssss", 8080)); } catch (IOException e) { long t2 = System.currentTimeMillis(); e.printStackTrace(); System.out.println("Connect failed, take time -> " + (t2 - t1) + "ms."); }
拋出異常:java.net.ConnectException: Connection timed out: connect,並打印:Connect failed, take time -> 18532ms. 也就是當未設置connect timeout時,connect方法會阻塞直到底層異常拋出。通過測試socket有個默認的超時時間,大概在20秒左右(測試的值,不必定準確,待研究JVM源碼)。下面咱們來設置connect timeout,再看看效果:網絡
socket = new Socket(); long t1 = 0; try { t1 = System.currentTimeMillis(); // 設置connect timeout 爲2000毫秒 socket.connect(new InetSocketAddress("www.ss.ssss", 8080), 2000); } catch (IOException e) { long t2 = System.currentTimeMillis(); e.printStackTrace(); System.out.println("Connect failed, take time -> " + (t2 - t1) + "ms."); }
拋出異常:java.net.SocketTimeoutException: connect timed out,並打印:Connect failed, take time -> 2014ms. 這裏就是connect timeout發揮做用了。socket
先看下jdk源碼註釋:tcp
Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a non-zero timeout, a read() call on the InputStream associated with this Socket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the Socket is still valid. The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout.
這個參數經過socket.setSoTimeout(int timeout)方法設置,能夠看出它的意思是,socket關聯的InputStream的read()方法會阻塞,直到超過設置的so timeout,就會拋出SocketTimeoutException。當不設置這個參數時,默認值爲無窮大,即InputStream的read方法會一直阻塞下去,除非鏈接斷開。ide
下面經過代碼來看下效果:工具
服務端代碼:
serverSocket = new ServerSocket(8080); Socket socket = serverSocket.accept();
服務端只接受socket但不發送任何數據給客戶端。客戶端代碼:
socket = new Socket(); socket.connect(new InetSocketAddress("localhost", 8080)); System.out.println("Connected."); in = socket.getInputStream(); System.out.println("reading..."); in.read(); System.out.println("read end");
客戶端創建鏈接就開始讀取InputStream。打印:
Connected.
reading...
而且一直阻塞在in.read(); 上。接下來我設置so timeout,代碼以下:
long t1 = 0; try { socket = new Socket(); socket.connect(new InetSocketAddress("localhost", 8080)); // 設置so timeout 爲2000毫秒 socket.setSoTimeout(2000); System.out.println("Connected."); in = socket.getInputStream(); System.out.println("reading..."); t1 = System.currentTimeMillis(); in.read(); } catch (IOException e) { long t2 = System.currentTimeMillis(); System.out.println("read end, take -> " + (t2 - t1) + "ms"); e.printStackTrace(); } finally { if (this.reader != null) { try { this.reader.close(); } catch (IOException e) { } } }
拋出異常:java.net.SocketTimeoutException: Read timed out, 打印:read end, take -> 2000ms , 說明so timeout起做用了。
咱們能夠經過設置connect timeout來控制鏈接創建的超時時間(不是絕對的,當設置的主機名不合法,好比我設置主機名爲abc,會拋異常java.net.UnknownHostException: abc,可是此時connect timeout設置是不起做用的,測試得出的結論,僅供參考)。
經過設置so timeout能夠控制流讀取數據的超時時間。
查閱MySQL Connector/J 5.1 Developer Guide 中的jdbc配置參數,有
connectTimeout Timeout for socket connect (in milliseconds), with 0 being no timeout. Only works on JDK-1.4 or newer. Defaults to '0'. Default: 0 Since version: 3.0.1 |
socketTimeout Timeout on network socket operations (0, the default means no timeout). Default: 0 Since version: 3.0.1 |
這兩個參數分別就是對應上面咱們分析的connect timeout和so timeout。
參數的設置方法有兩種,一種是經過url設置,
jdbc:mysql://[host1][:port1][,[host2][:port2]]...[/[database]] [?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]
即在url後面經過?加參數,好比jdbc:mysql://192.168.1.1:3306/test?connectTimeout=2000&socketTime=2000
還有一種方式是:
Properties info = new Properties(); info.put("user", this.username); info.put("password", this.password); info.put("connectTimeout", "2000"); info.put("socketTime", "2000"); return DriverManager.getConnection(this.url, info);
Jedis是最流行的redis java客戶端工具,redis.clients.jedis.Jedis對象的構造器中就有參數設置,
public Jedis(final String host, final int port, final int connectionTimeout, final int soTimeout) { super(host, port, connectionTimeout, soTimeout); }
// 用一個參數timeout同時設置connect timeout 和 so timeout public Jedis(final String host, final int port, final int timeout) { super(host, port, timeout); }
Jedis中so timeout我的以爲是有比較重要意義的,首先jedis so timeout默認值爲2000毫秒,jedis的操做流程是客戶端發送命令給客戶端執行,而後客戶端就開始執行InputStream.read()讀取響應,當某個命令比較耗時(好比數據很是多的狀況下執行「keys *」),而致使客戶端遲遲沒有收到響應,就可能致使java.net.SocketTimeoutException: Read timed out異常拋出。通常是不建議客戶端執行很是耗時的命令,可是也不排除有這種特殊邏輯,那這時候就有可能須要修改Jeids中這個so timeout的值。
瞭解了這兩個timeout以後,能夠更好的處理一些網絡服務的客戶端和服務端,同時對排查一些問題也頗有幫助。通常的成熟的網絡服務和客戶端都應該有這兩個參數的配置方法,當使用遇到相似問題能夠從這個方向去考慮下。