Java Socket設(shè)置timeout的幾種常用方式說(shuō)明
Java的網(wǎng)絡(luò)編程Socket常常用于各種網(wǎng)絡(luò)工具,比如數(shù)據(jù)庫(kù)的jdbc客戶端,redis客戶端jedis,各種RPC工具java客戶端,這其中存在一些參數(shù)來(lái)配置timeout,但是之前一直對(duì)timeout的理解還不清晰,所以會(huì)導(dǎo)致使用這些網(wǎng)絡(luò)工具的時(shí)候有點(diǎn)迷茫。在此做個(gè)總結(jié)。
1. Socket timeout
Java socket有如下兩種timeout:
- 建立連接timeout,暫時(shí)就叫 connect timeout
- 讀取數(shù)據(jù)timeout,暫時(shí)就叫so timeout
1.1 建立連接connect timeout
當(dāng)不設(shè)置該參數(shù)時(shí),指客戶端請(qǐng)求和服務(wù)端建立tcp連接時(shí),會(huì)一直阻塞直到連接建立成功,或拋異常。當(dāng)設(shè)置了connectTimeout, 客戶端請(qǐng)求和服務(wù)端建立連接時(shí),阻塞時(shí)間超過(guò)connectTimeout時(shí),就會(huì)拋出異常java.net.ConnectException: Connection timed out: connect。
我們看如下精簡(jiǎn)后的代碼,首先是服務(wù)端:
serverSocket = new ServerSocket(8080);? Socket socket = serverSocket.accept();?
服務(wù)端開(kāi)啟ServerSocket監(jiān)聽(tīng)8080端口,再看客戶端:
socket = new Socket();?
socket.connect(new InetSocketAddress("localhost", 8080));?
System.out.println("Connected.");打印“Connected.”,修改客戶端代碼中的主機(jī)名為一個(gè)不存在的主機(jī):
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,并打?。篊onnect failed, take time -> 18532ms. 也就是當(dāng)未設(shè)置connect timeout時(shí),connect方法會(huì)阻塞直到底層異常拋出。經(jīng)過(guò)測(cè)試socket有個(gè)默認(rèn)的超時(shí)時(shí)間,大概在20秒左右(測(cè)試的值,不一定準(zhǔn)確,待研究JVM源碼)。
下面我們來(lái)設(shè)置connect timeout,再看看效果:
socket = new Socket();?
long t1 = 0;?
try {?
t1 = System.currentTimeMillis();?
// 設(shè)置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發(fā)揮作用了。
1.2 讀取數(shù)據(jù)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.
這個(gè)參數(shù)通過(guò)socket.setSoTimeout(int timeout)方法設(shè)置,可以看出它的意思是,socket關(guān)聯(lián)的InputStream的read()方法會(huì)阻塞,直到超過(guò)設(shè)置的so timeout,就會(huì)拋出SocketTimeoutException。
當(dāng)不設(shè)置這個(gè)參數(shù)時(shí),默認(rèn)值為無(wú)窮大,即InputStream的read方法會(huì)一直阻塞下去,除非連接斷開(kāi)。
下面通過(guò)代碼來(lái)看下效果:
服務(wù)端代碼:
serverSocket = new ServerSocket(8080);? Socket socket = serverSocket.accept();?
服務(wù)端只接受socket但不發(fā)送任何數(shù)據(jù)給客戶端。客戶端代碼:
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");客戶端建立連接就開(kāi)始讀取InputStream。打?。?/p>
Connected.
reading...
并且一直阻塞在in.read(); 上。接下來(lái)我設(shè)置so timeout,代碼如下:
long t1 = 0;?
try {?
socket = new Socket();?
socket.connect(new InetSocketAddress("localhost", 8080));?
// 設(shè)置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, 打?。簉ead end, take -> 2000ms , 說(shuō)明so timeout起作用了。
1.3 小結(jié)
我們可以通過(guò)設(shè)置connect timeout來(lái)控制連接建立的超時(shí)時(shí)間(不是絕對(duì)的,當(dāng)設(shè)置的主機(jī)名不合法,比如我設(shè)置主機(jī)名為abc,會(huì)拋異常java.net.UnknownHostException: abc,但是此時(shí)connect timeout設(shè)置是不起作用的,測(cè)試得出的結(jié)論,僅供參考)。
通過(guò)設(shè)置so timeout可以控制流讀取數(shù)據(jù)的超時(shí)時(shí)間。
2. 使用案例
2.1 MySQL jdbc timeout
查閱MySQL Connector/J 5.1 Developer Guide 中的jdbc配置參數(shù),有
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 |
這兩個(gè)參數(shù)分別就是對(duì)應(yīng)上面我們分析的connect timeout和so timeout。
參數(shù)的設(shè)置方法有兩種,一種是通過(guò)url設(shè)置,
jdbc:mysql://[host1][:port1][,[host2][:port2]]...[/[database]] [?propertyName1=propertyValue1[&propertyName2=propertyValue2]...]
即在url后面通過(guò)?加參數(shù),比如
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對(duì)象的構(gòu)造器中就有參數(shù)設(shè)置,
public Jedis(final String host, final int port, final int connectionTimeout, final int soTimeout) {?
super(host, port, connectionTimeout, soTimeout);?
}?
// 用一個(gè)參數(shù)timeout同時(shí)設(shè)置connect timeout 和 so timeout?
public Jedis(final String host, final int port, final int timeout) {?
super(host, port, timeout);?
}Jedis中so timeout個(gè)人覺(jué)得是有比較重要意義的,首先jedis so timeout默認(rèn)值為2000毫秒,jedis的操作流程是客戶端發(fā)送命令給服務(wù)器端執(zhí)行,然后客戶端就開(kāi)始執(zhí)行InputStream.read()讀取響應(yīng),當(dāng)某個(gè)命令比較耗時(shí)(比如數(shù)據(jù)非常多的情況下執(zhí)行“keys *”),而導(dǎo)致客戶端遲遲沒(méi)有收到響應(yīng),就可能導(dǎo)致java.net.SocketTimeoutException: Read timed out異常拋出。
一般是不建議客戶端執(zhí)行非常耗時(shí)的命令,但是也不排除有這種特殊邏輯,那這時(shí)候就有可能需要修改Jeids中這個(gè)so timeout的值。
3. 總結(jié)
了解了這兩個(gè)timeout之后,可以更好的處理一些網(wǎng)絡(luò)服務(wù)的客戶端和服務(wù)端,同時(shí)對(duì)排查一些問(wèn)題也很有幫助。
一般的成熟的網(wǎng)絡(luò)服務(wù)和客戶端都應(yīng)該有這兩個(gè)參數(shù)的配置方法,當(dāng)使用遇到類似問(wèn)題可以從這個(gè)方向去考慮下。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java如何避免死鎖和競(jìng)態(tài)條件的實(shí)現(xiàn)
本文主要介紹了Java如何避免死鎖和競(jìng)態(tài)條件的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05
解決Tomcat修改get提交請(qǐng)求亂碼問(wèn)題
這篇文章主要介紹了Tomcat修改get提交請(qǐng)求亂碼問(wèn)題的解決方案,需要的朋友參考下2017-04-04
IDEA中程序包Org.Springframework.Boot不存在問(wèn)題及解決
這篇文章主要介紹了IDEA中程序包Org.Springframework.Boot不存在問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07
Java8加java10等于Java18的版本查看及特性詳解
這篇文章主要為大家介紹了Java?8加java10等于Java18的各個(gè)版本要點(diǎn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
用java實(shí)現(xiàn)的獲取優(yōu)酷等視頻縮略圖的實(shí)現(xiàn)代碼
想獲取優(yōu)酷等視頻縮略圖,在網(wǎng)上沒(méi)有找到滿意的資料,參考了huangdijia的PHP版工具一些思路,寫了下面的JAVA版代碼。。其實(shí)也可以做成JS版的2013-05-05
RabbitMQ 的七種隊(duì)列模式和應(yīng)用場(chǎng)景
最近學(xué)習(xí)RabbitMQ,本文就記錄一下RabbitMQ 的七種隊(duì)列模式和應(yīng)用場(chǎng)景,方便以后使用,也方便和大家共享,相互交流2021-05-05

