幾個(gè)重要的TCP/IP選項(xiàng)解析(Java Socket)
1. SO_LINGER / SO_REUSEADDR
TCP正常的關(guān)閉過(guò)程如下(四次握手過(guò)程):
(FIN_WAIT_1) A ---FIN---> B(CLOSE_WAIT)
(FIN_WAIT_2) A <--ACK-- B(CLOSE_WAIT)
(TIME_WAIT)A <--FIN---- B(LAST_ACK)
(TIME_WAIT)A ---ACK-> B(CLOSED)
Ø A端首先發(fā)送一個(gè)FIN請(qǐng)求給B端,要求關(guān)閉,發(fā)送后A段的TCP狀態(tài)變更為FIN_WAIT_1,接收到FIN請(qǐng)求后B端的TCP狀態(tài)變更為CLOSE_WAIT
Ø B接收到ACK請(qǐng)求后,B回一個(gè)ACK給A端,確認(rèn)接收到的FIN請(qǐng)求,接收到ACK請(qǐng)求后,A端的TCP狀態(tài)變更為為FIN_WAIT_2。
Ø B端再發(fā)送一個(gè)FIN請(qǐng)求給A端,與連接過(guò)程的3次握手過(guò)程不一樣,這個(gè)FIN請(qǐng)求之所以并不是與上一個(gè)請(qǐng)求一起發(fā)送,之所以如此處理,是因?yàn)門CP是雙 通道的,允許在發(fā)送ACK請(qǐng)求后,并不馬上發(fā)FIN請(qǐng)求,即只關(guān)閉A到B端的數(shù)據(jù)流,仍然允許B端到A端的數(shù)據(jù)流。這個(gè)ACK請(qǐng)求發(fā)送之后,B端的TCP 狀態(tài)變更為L(zhǎng)AST_ACK,A端的狀態(tài)變更為TIME_WAIT。
Ø A端接收到B端的FIN請(qǐng)求后,再回B端一個(gè)ACK信息,對(duì)上一個(gè)FIN請(qǐng)求進(jìn)行確認(rèn),到此時(shí)B端狀態(tài)變更為CLOSED,Socket可以關(guān)閉。
除了如上正常的關(guān)閉(優(yōu)雅關(guān)閉)之外,TCP還提供了另外一種非優(yōu)雅的關(guān)閉方式RST(Reset)
(CLOSED) A ---RST--> B (CLOSED)
Ø A端發(fā)送RST狀態(tài)之后,TCP進(jìn)入CLOSED狀態(tài),B端接收到RST后,也即可進(jìn)入CLOSED狀態(tài)。
在第一種關(guān)閉方式上(優(yōu)雅關(guān)閉),非常遺憾,A端在最后發(fā)送一個(gè)ACK請(qǐng)求后,并不能馬上將該Socket回收,因?yàn)锳并不能確定B一定能夠接收到這個(gè) ACK請(qǐng)求,因此A端必須對(duì)這個(gè)Socket維持TIME_WAIT狀態(tài)2MSL(MSL=Max Segment Lifetime,取決于操作系統(tǒng)和TCP實(shí)現(xiàn),該值為30秒、60秒或2分鐘)。如果A端是客戶端,這并不會(huì)成為問(wèn)題,但如果A端是服務(wù)端,那就很危險(xiǎn) 了,如果連接的Socket非常多,而又維持如此多的TIME_WAIT狀態(tài)的話,那么有可能會(huì)將Socket耗盡(報(bào)Too Many Open File)。
服務(wù)端為了解決這個(gè)問(wèn)題,可選擇的方式有三種:
Ø 保證由客戶端主動(dòng)發(fā)起關(guān)閉(即做為B端)
Ø 關(guān)閉的時(shí)候使用RST的方式
Ø 對(duì)處于TIME_WAIT狀態(tài)的TCP允許重用
一般我們當(dāng)然最好是選擇第一種方式,實(shí)在沒(méi)有辦法的時(shí)候,我們可以使用SO_LINGER選擇第二種方式,使用SO_REUSEADDR選擇第三種方式
- public void setSoLinger( boolean on, int linger) throws SocketException
- public void setReuseAddress( boolean on) throws SocketException
第一個(gè)on表示是否使用SO_LINGER選項(xiàng),linger(以秒為單位)表示在發(fā)RST之前會(huì)等待多久,因?yàn)橐坏┌l(fā)送RST,還在緩沖區(qū)中還沒(méi)有發(fā)送出去的數(shù)據(jù)就會(huì)直接丟棄
2.TCP_NODELAY
對(duì)于交互型的應(yīng)用(譬如telnet),經(jīng)常存在的情況是客戶端和服務(wù)端之間需要頻繁地進(jìn)行一些小數(shù)據(jù)交換,譬如telnet可能每敲一個(gè)鍵盤都需要將數(shù) 據(jù)發(fā)送到服務(wù)端。為了避免這種情況會(huì)產(chǎn)生大量小數(shù)據(jù)包,提出了Nagle算法。Nagle算法要求每次在發(fā)送端最后只有一個(gè)未被確認(rèn)的包,因此上一個(gè)包發(fā) 送出去還沒(méi)有接收到響應(yīng)之前,要求發(fā)送的包回先放在緩沖區(qū),接收到響應(yīng)之后,會(huì)將緩沖區(qū)中的包合并成一個(gè)包發(fā)送出去(可以看到,響應(yīng)回地越快,發(fā)送出去的 數(shù)據(jù)也會(huì)越快)。
需要注意的是,由Nagle算法要求只能有一個(gè)未被確認(rèn)的包,因此窗口參數(shù)會(huì)失效,在大數(shù)據(jù)量傳送的情況下會(huì)使網(wǎng)絡(luò)吞吐量下降,因此對(duì)于大數(shù)據(jù)量的交互, 應(yīng)該關(guān)閉Nagle算法,Nagle算法比較適合小數(shù)據(jù)量頻繁交換的情景。我們可以使用TCP_NODELAY關(guān)閉Nagle算法。
- public void setTcpNoDelay( boolean on) throws SocketException
3.SO_KEEPALIVE
在一個(gè)TCP連接建立之后,我們會(huì)很奇怪地發(fā)現(xiàn),默認(rèn)情況下,如果一端異常退出(譬如網(wǎng)絡(luò)中斷后一端退出,使地關(guān)閉請(qǐng)求另一端無(wú)法接收到),TCP的另一 端并不能獲得這種情況,仍然會(huì)保持一個(gè)半關(guān)閉的連接,對(duì)于服務(wù)端,大量半關(guān)閉的連接將會(huì)是非常致命的。SO_KEEPALIVE提供了一種手段讓TCP的 一端(通常服務(wù)提供者端)可以檢測(cè)到這種情況。如果我們?cè)O(shè)置了SO_KEEPALIVE,TCP在距離上一次TCP包交互2個(gè)小時(shí)(取決于操作系統(tǒng)和 TCP實(shí)現(xiàn),規(guī)范建議不低于2小時(shí))后,會(huì)發(fā)送一個(gè)探測(cè)包給另一端,如果接收不到響應(yīng),則在75秒后重新發(fā)送,連續(xù)10次仍然沒(méi)有響應(yīng),則認(rèn)為對(duì)方已經(jīng)關(guān) 閉,系統(tǒng)會(huì)將該連接關(guān)閉。一般情況下,如果對(duì)方已經(jīng)關(guān)閉,則對(duì)方的TCP層會(huì)回RST響應(yīng)回來(lái),這種情況下,同樣會(huì)將連接關(guān)閉。
posted on 2012-02-09 11:53 大龍 閱讀(664) 評(píng)論(0) 編輯 收藏 引用