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