摘要: Java的網絡編程Socket常常用於各種網絡工具,比如數據庫的jdbc客戶端,redis客戶端jedis,各種RPC工具java客戶端,這其中存在一些參數來配置timeout,但是之前一直對timeout的理解還不清晰,所以會導致使用這些網絡工具的時候有點迷茫。在此做個總結。
1. Socket timeout
Java socket有如下兩種timeout:
建立連接timeout,暫時就叫 connect timeout;
讀取數據timeout,暫時就叫so timeout。
1.1 建立連接connect timeout
當不設置該參數時,指客戶端請求和服務端建立tcp連接時,會一直阻塞直到連接建立成功,或拋異常。當設置了connectTimeout, 客戶端請求和服務端建立連接時,阻塞時間超過connectTimeout時,就會拋出異常java.net.ConnectException: Connection timed out: connect。
我們看如下精簡後的代碼,首先是服務端:
serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
服務端開啟ServerSocket監聽8080端口,再看客戶端:
socket = new Socket();
socket.connect(new InetSocketAddress("localhost", 8080));
System.out.println("Connected.");
打印“Connected.”,修改客戶端代碼中的主機名為一個不存在的主機:
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發揮作用了。
1.2 讀取數據so timeout
先看下jdk源碼註釋:
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方法會一直阻塞下去,除非連接斷開。
下面通過代碼來看下效果:
服務端代碼:
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起作用了。
1.3 小結
我們可以通過設置connect timeout來控制連接建立的超時時間(不是絕對的,當設置的主機名不合法,比如我設置主機名為abc,會拋異常java.net.UnknownHostException: abc,但是此時connect timeout設置是不起作用的,測試得出的結論,僅供參考)。
通過設置so timeout可以控制流讀取數據的超時時間。
2. 使用案例
2.1 MySQL jdbc 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);
2.2 Jedis timeout
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的值。
3. 總結
瞭解了這兩個timeout之後,可以更好的處理一些網絡服務的客戶端和服務端,同時對排查一些問題也很有幫助。一般的成熟的網絡服務和客戶端都應該有這兩個參數的配置方法,當使用遇到類似問題可以從這個方向去考慮下。
Java工程化、高性能及分佈式、高性能、深入淺出。高架構。性能調優、Spring,MyBatis,Netty源碼分析和大數據等多個知識點。如果你想拿高薪的,想學習的,想就業前景好的,想跟別人競爭能取得優勢的,想進阿里面試但擔心面試不過的,你都可以來,群號為:647631030
注:加群要求
1、具有1-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。
2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。
3、如果沒有工作經驗,但基礎非常紮實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。
4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。
5.阿里Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!
6.小號或者小白之類加群一律不給過,謝謝。