您好,登錄后才能下訂單哦!
這篇文章將為大家詳細(xì)講解有關(guān)Java Socket的超時(shí)淺析是什么,文章內(nèi)容質(zhì)量較高,因此小編分享給大家做個(gè)參考,希望大家閱讀完這篇文章后對(duì)相關(guān)知識(shí)有一定的了解。
套接字或插座(socket)是一種軟件形式的抽象,用于表達(dá)兩臺(tái)機(jī)器間一個(gè)連接的“終端”。針對(duì)一個(gè)特定的連接,每臺(tái)機(jī)器上都有一個(gè)“套接字”,可以想象它們之間有一條虛擬的“線纜”。JAVA 有兩個(gè)基于數(shù)據(jù)流的套接字類:ServerSocket,服務(wù)器用它“偵聽”進(jìn)入的連接;Socket,客戶端用它初始一次連接。偵聽套接字只能接收新的連接請(qǐng)求,不能接收實(shí)際的數(shù)據(jù)包。
套接字是基于TCP/IP實(shí)現(xiàn)的,它是用來(lái)提供一個(gè)訪問(wèn)TCP的服務(wù)接口,或者說(shuō)套接字socket是TCP的應(yīng)用編程接口API,通過(guò)它應(yīng)用層就可以訪問(wèn)TCP提供的服務(wù)。
在JAVA中,我們用 ServerSocket、Socket類創(chuàng)建一個(gè)套接字連接,從套接字得到的結(jié)果是一個(gè)InputStream以及OutputStream對(duì)象,以便將連接作為一個(gè)IO流對(duì)象對(duì)待。通過(guò)IO流可以從流中讀取數(shù)據(jù)或者寫數(shù)據(jù)到流中,讀寫IO流會(huì)有異常IOException產(chǎn)生。
套接字底層是基于TCP的,所以socket的超時(shí)和TCP超時(shí)是相同的。下面先討論套接字讀寫緩沖區(qū),接著討論連接建立超時(shí)、讀寫超時(shí)以及JAVA套接字編程的嵌套異常捕獲和一個(gè)超時(shí)例子程序的抓包示例。
1、socket讀寫緩沖區(qū)
一旦創(chuàng)建了一個(gè)套接字實(shí)例,操作系統(tǒng)就會(huì)為其分配緩沖區(qū)以存放接收和要發(fā)送的數(shù)據(jù)。
JAVA可以設(shè)置讀寫緩沖區(qū)的大小-setReceiveBufferSize(int size), setSendBufferSize(int size)。
向輸出流寫數(shù)據(jù)并不意味著數(shù)據(jù)實(shí)際上已經(jīng)被發(fā)送,它們只是被復(fù)制到了發(fā)送緩沖區(qū)隊(duì)列SendQ,就是在Socket的OutputStream上調(diào)用 flush()方法,也不能保證數(shù)據(jù)能夠立即發(fā)送到網(wǎng)絡(luò)。真正的數(shù)據(jù)發(fā)送是由操作系統(tǒng)的TCP協(xié)議棧模塊從緩沖區(qū)中取數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)來(lái)完成的。
當(dāng)有數(shù)據(jù)從網(wǎng)絡(luò)來(lái)到時(shí),TCP協(xié)議棧模塊接收數(shù)據(jù)并放入接收緩沖區(qū)隊(duì)列RecvQ,輸入流InputStream通過(guò)read方法從RecvQ中取出數(shù)據(jù)。
2、socket連接建立超時(shí)
socket連接建立是基于TCP的連接建立過(guò)程。TCP的連接需要通過(guò)3次握手報(bào)文來(lái)完成,開始建立TCP連接時(shí)需要發(fā)送同步SYN報(bào)文,然后等待確認(rèn)報(bào)文SYN+ACK,***再發(fā)送確認(rèn)報(bào)文ACK。TCP連接的關(guān)閉通過(guò)4次揮手來(lái)完成,主動(dòng)關(guān)閉TCP連接的一方發(fā)送FIN報(bào)文,等待對(duì)方的確認(rèn)報(bào)文;被動(dòng)關(guān)閉的一方也發(fā)送FIN報(bào)文,然等待確認(rèn)報(bào)文。
正在等待TCP連接請(qǐng)求的一端有一個(gè)固定長(zhǎng)度的連接隊(duì)列,該隊(duì)列中的連接已經(jīng)被TCP接受(即三次握手已經(jīng)完成),但還沒有被應(yīng)用層所接受。TCP接受一個(gè)連接是將其放入這個(gè)連接隊(duì)列,而應(yīng)用層接受連接是將其從該隊(duì)列中移出。應(yīng)用層可以通過(guò)設(shè)置backlog變量來(lái)指明該連接隊(duì)列的***長(zhǎng)度,即已被TCP接受而等待應(yīng)用層接受的***連接數(shù)。
當(dāng)一個(gè)連接請(qǐng)求SYN到達(dá)時(shí),TCP確定是否接受這個(gè)連接。如果隊(duì)列中還有空間,TCP模塊將對(duì)SYN進(jìn)行確認(rèn)并完成連接的建立。但應(yīng)用層只有在三次握手中的第三個(gè)報(bào)文收到后才會(huì)知道這個(gè)新連接。如果隊(duì)列沒有空間,TCP將不理會(huì)收到的SYN。
如果應(yīng)用層不能及時(shí)接受已被TCP接受的連接,這些連接可能占滿整個(gè)連接隊(duì)列,新的連接請(qǐng)求可能不被響應(yīng)而會(huì)超時(shí)。如果一個(gè)連接請(qǐng)求SYN發(fā)送后,一段時(shí)間后沒有收到確認(rèn)SYN+ACK,TCP會(huì)重傳這個(gè)連接請(qǐng)求SYN兩次,每次重傳的時(shí)間間隔加倍,在規(guī)定的時(shí)間內(nèi)仍沒有收到SYN+ACK,TCP將放棄這個(gè)連接請(qǐng)求,連接建立就超時(shí)了。
JAVA Socket連接建立超時(shí)和TCP是相同的,如果TCP建立連接時(shí)三次握手超時(shí),那么導(dǎo)致Socket連接建立也就超時(shí)了??梢栽O(shè)置Socket連接建立的超時(shí)時(shí)間-
connect(SocketAddress endpoint, int timeout)
如果在timeout內(nèi),連接沒有建立成功,在TimeoutException異常被拋出。如果timeout的值小于三次握手的時(shí)間,那么Socket連接永遠(yuǎn)也不會(huì)建立。
不同的應(yīng)用層有不同的連接建立過(guò)程,Socket的連接建立和TCP一樣-僅僅需要三次握手就完成連接,但有些應(yīng)用程序需要交互很多信息后才能成功建立連接,比如Telnet協(xié)議,在TCP三次握手完成后,需要進(jìn)行選項(xiàng)協(xié)商之后,Telnet連接才建立完成。
3、socket讀超時(shí)
如果輸入緩沖隊(duì)列RecvQ中沒有數(shù)據(jù),read操作會(huì)一直阻塞而掛起線程,直到有新的數(shù)據(jù)到來(lái)或者有異常產(chǎn)生。調(diào)用setSoTimeout(int timeout)可以設(shè)置超時(shí)時(shí)間,如果到了超時(shí)時(shí)間仍沒有數(shù)據(jù),read會(huì)拋出一個(gè)SocketTimeoutException,程序需要捕獲這個(gè)異常,但是當(dāng)前的socket連接仍然是有效的。
如果對(duì)方進(jìn)程崩潰、對(duì)方機(jī)器突然重啟、網(wǎng)絡(luò)斷開,本端的read會(huì)一直阻塞下去,這時(shí)設(shè)置超時(shí)時(shí)間是非常重要的,否則調(diào)用read的線程會(huì)一直掛起。
TCP模塊把接收到的數(shù)據(jù)放入RecvQ中,直到應(yīng)用層調(diào)用輸入流的read方法來(lái)讀取。如果RecvQ隊(duì)列被填滿了,這時(shí)TCP會(huì)根據(jù)滑動(dòng)窗口機(jī)制通知對(duì)方不要繼續(xù)發(fā)送數(shù)據(jù),本端停止接收從對(duì)端發(fā)送來(lái)的數(shù)據(jù),直到接收者應(yīng)用程序調(diào)用輸入流的read方法后騰出了空間。
4、socket寫超時(shí)
socket的寫超時(shí)是基于TCP的超時(shí)重傳。超時(shí)重傳是TCP保證數(shù)據(jù)可靠性傳輸?shù)囊粋€(gè)重要機(jī)制,其原理是在發(fā)送一個(gè)數(shù)據(jù)報(bào)文后就開啟一個(gè)計(jì)時(shí)器,在一定時(shí)間內(nèi)如果沒有得到發(fā)送報(bào)文的確認(rèn)ACK,那么就重新發(fā)送報(bào)文。如果重新發(fā)送多次之后,仍沒有確認(rèn)報(bào)文,就發(fā)送一個(gè)復(fù)位報(bào)文RST,然后關(guān)閉TCP連接。***數(shù)據(jù)報(bào)文發(fā)送與復(fù)位報(bào)文傳輸之間的時(shí)間差大約為9分鐘,也就是說(shuō)如果9分鐘內(nèi)沒有得到確認(rèn)報(bào)文,就關(guān)閉連接。但是這個(gè)值是根據(jù)不同的TCP協(xié)議棧實(shí)現(xiàn)而不同。
如果發(fā)送端調(diào)用write持續(xù)地寫出數(shù)據(jù),直到SendQ隊(duì)列被填滿。如果在SendQ隊(duì)列已滿時(shí)調(diào)用write方法,則write將被阻塞,直到 SendQ有新的空閑空間為止,也就是說(shuō)直到一些字節(jié)傳輸?shù)搅私邮照咛捉幼值腞ecvQ中。如果此時(shí)RecvQ隊(duì)列也已經(jīng)被填滿,所有操作都將停止,直到接收端調(diào)用read方法將一些字節(jié)傳輸?shù)綉?yīng)用程序。
當(dāng)Socket的write發(fā)送數(shù)據(jù)時(shí),如果網(wǎng)線斷開、對(duì)端進(jìn)程崩潰或者對(duì)端機(jī)器重啟動(dòng),TCP模塊會(huì)重傳數(shù)據(jù),***超時(shí)而關(guān)閉連接。下次如再調(diào)用write會(huì)導(dǎo)致一個(gè)異常而退出。
Socket寫超時(shí)是基于TCP協(xié)議棧的超時(shí)重傳機(jī)制,一般不需要設(shè)置write的超時(shí)時(shí)間,也沒有提供這種方法。
5、雙重嵌套異常捕獲
如果ServerSocket、Socket構(gòu)造失敗,只需要僅僅捕獲這個(gè)構(gòu)造失敗異常而不需要調(diào)用套接字的close方法來(lái)釋放資源(必須保證構(gòu)造失敗后不會(huì)留下任何需要清除的資源),因?yàn)檫@時(shí)套接字內(nèi)部資源沒有被成功分配。如果構(gòu)造成功,必須進(jìn)入一個(gè)try finally語(yǔ)句塊里調(diào)用close釋放套接字。請(qǐng)參照下面例子程序。
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(); } } }
import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class SocketServerTest { public static final int PORT = 8088; public static final int BACKLOG = 2; public static void main( String[] args ) throws IOException { ServerSocket server = new ServerSocket( PORT, BACKLOG ); System.out.println("started: " + server); try { Socket socket = server.accept(); try { BufferedReader in = new BufferedReader( new InputStreamReader( socket.getInputStream() ) ); String info = null; while( ( info = in.readLine() ) != null ) { System.out.println( info ); } } finally { socket.close(); } } finally { server.close(); } } }
執(zhí)行上面的程序,在程序運(yùn)行一會(huì)兒之后,斷開client和server之間的網(wǎng)絡(luò)連接,在機(jī)器上輸出如下:
Server上的輸出:
Echoing:client sent -----hello0 Echoing:client sent -----hello1 Echoing:client sent -----hello2 Echoing:client sent -----hello3 Echoing:client sent -----hello4 Echoing:client sent -----hello5 Echoing:client sent -----hello6
---->> 斷開了網(wǎng)絡(luò)連接之后沒有數(shù)據(jù)輸出
Client上的輸出:
socket default timeout = 0 socket = Socket[addr=/10.15.9.99,port=8088,localport=4691] begin to read client sent --- hello *** 0 client sent --- hello *** 1 client sent --- hello *** 2 client sent --- hello *** 3 client sent --- hello *** 4 client sent --- hello *** 5 client sent --- hello *** 6 client sent --- hello *** 7 client sent --- hello *** 8 client sent --- hello *** 9 client sent --- hello *** 10
---->> 斷開網(wǎng)絡(luò)連接后客戶端進(jìn)程掛起
java.net.SocketException : Connection reset by peer: socket write error at java.net.SocketOutputStream.socketWrite0( Native Method ) at java.net.SocketOutputStream.socketWrite( SocketOutputStream.java:92 ) at java.net.SocketOutputStream.write( SocketOutputStream.java:136 ) at sun.nio.cs.StreamEncoder.writeBytes( StreamEncoder.java:202 ) at sun.nio.cs.StreamEncoder.implFlushBuffer( StreamEncoder.java:272 ) at sun.nio.cs.StreamEncoder.implFlush( StreamEncoder.java:276 ) at sun.nio.cs.StreamEncoder.flush( StreamEncoder.java:122 ) at java.io.OutputStreamWriter.flush( OutputStreamWriter.java:212 ) at java.io.BufferedWriter.flush( BufferedWriter.java:236 ) at com.xtera.view.SocketClientTest.main( SocketClientTest.java:99 )
當(dāng)hello6被發(fā)送到server端后,網(wǎng)絡(luò)連接被斷開,這時(shí)server端不能接收任何數(shù)據(jù)而掛起。client端仍然繼續(xù)發(fā)送數(shù)據(jù),實(shí)際上hello7、hello8、hello9、hello10都被復(fù)制到SendQ隊(duì)列中,write方法立即返回。當(dāng)client的SendQ隊(duì)列被填滿之后,write方法就被阻塞。TCP模塊在發(fā)送報(bào)文hello7之后,沒有收到確認(rèn)而超時(shí)重傳,再重傳幾次之后關(guān)閉了TCP連接,同時(shí)導(dǎo)致被阻塞的write方法異常返回。
通過(guò)抓包工具,我們可以看到超時(shí)重傳的報(bào)文。
關(guān)于Java Socket的超時(shí)淺析是什么就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。