1. SO_LINGER / SO_REUSEADDR
TCP正常的關閉過程如下(四次握手過程):
(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端首先發送一個FIN請求給B端,要求關閉,發送后A段的TCP狀態變更為FIN_WAIT_1,接收到FIN請求后B端的TCP狀態變更為CLOSE_WAIT
Ø B接收到ACK請求后,B回一個ACK給A端,確認接收到的FIN請求,接收到ACK請求后,A端的TCP狀態變更為為FIN_WAIT_2。
Ø B端再發送一個FIN請求給A端,與連接過程的3次握手過程不一樣,這個FIN請求之所以并不是與上一個請求一起發送,之所以如此處理,是因為TCP是雙 通道的,允許在發送ACK請求后,并不馬上發FIN請求,即只關閉A到B端的數據流,仍然允許B端到A端的數據流。這個ACK請求發送之后,B端的TCP 狀態變更為LAST_ACK,A端的狀態變更為TIME_WAIT。
Ø A端接收到B端的FIN請求后,再回B端一個ACK信息,對上一個FIN請求進行確認,到此時B端狀態變更為CLOSED,Socket可以關閉。
除了如上正常的關閉(優雅關閉)之外,TCP還提供了另外一種非優雅的關閉方式RST(Reset)
(CLOSED) A ---RST--> B (CLOSED)
Ø A端發送RST狀態之后,TCP進入CLOSED狀態,B端接收到RST后,也即可進入CLOSED狀態。
在第一種關閉方式上(優雅關閉),非常遺憾,A端在最后發送一個ACK請求后,并不能馬上將該Socket回收,因為A并不能確定B一定能夠接收到這個 ACK請求,因此A端必須對這個Socket維持TIME_WAIT狀態2MSL(MSL=Max Segment Lifetime,取決于操作系統和TCP實現,該值為30秒、60秒或2分鐘)。如果A端是客戶端,這并不會成為問題,但如果A端是服務端,那就很危險 了,如果連接的Socket非常多,而又維持如此多的TIME_WAIT狀態的話,那么有可能會將Socket耗盡(報Too Many Open File)。
服務端為了解決這個問題,可選擇的方式有三種:
Ø 保證由客戶端主動發起關閉(即做為B端)
Ø 關閉的時候使用RST的方式
Ø 對處于TIME_WAIT狀態的TCP允許重用
一般我們當然最好是選擇第一種方式,實在沒有辦法的時候,我們可以使用SO_LINGER選擇第二種方式,使用SO_REUSEADDR選擇第三種方式
- public void setSoLinger( boolean on, int linger) throws SocketException
- public void setReuseAddress( boolean on) throws SocketException
第一個on表示是否使用SO_LINGER選項,linger(以秒為單位)表示在發RST之前會等待多久,因為一旦發送RST,還在緩沖區中還沒有發送出去的數據就會直接丟棄
2.TCP_NODELAY
對于交互型的應用(譬如telnet),經常存在的情況是客戶端和服務端之間需要頻繁地進行一些小數據交換,譬如telnet可能每敲一個鍵盤都需要將數 據發送到服務端。為了避免這種情況會產生大量小數據包,提出了Nagle算法。Nagle算法要求每次在發送端最后只有一個未被確認的包,因此上一個包發 送出去還沒有接收到響應之前,要求發送的包回先放在緩沖區,接收到響應之后,會將緩沖區中的包合并成一個包發送出去(可以看到,響應回地越快,發送出去的 數據也會越快)。
需要注意的是,由Nagle算法要求只能有一個未被確認的包,因此窗口參數會失效,在大數據量傳送的情況下會使網絡吞吐量下降,因此對于大數據量的交互, 應該關閉Nagle算法,Nagle算法比較適合小數據量頻繁交換的情景。我們可以使用TCP_NODELAY關閉Nagle算法。
- public void setTcpNoDelay( boolean on) throws SocketException
3.SO_KEEPALIVE
在一個TCP連接建立之后,我們會很奇怪地發現,默認情況下,如果一端異常退出(譬如網絡中斷后一端退出,使地關閉請求另一端無法接收到),TCP的另一 端并不能獲得這種情況,仍然會保持一個半關閉的連接,對于服務端,大量半關閉的連接將會是非常致命的。SO_KEEPALIVE提供了一種手段讓TCP的 一端(通常服務提供者端)可以檢測到這種情況。如果我們設置了SO_KEEPALIVE,TCP在距離上一次TCP包交互2個小時(取決于操作系統和 TCP實現,規范建議不低于2小時)后,會發送一個探測包給另一端,如果接收不到響應,則在75秒后重新發送,連續10次仍然沒有響應,則認為對方已經關 閉,系統會將該連接關閉。一般情況下,如果對方已經關閉,則對方的TCP層會回RST響應回來,這種情況下,同樣會將連接關閉。
public void setKeepAlive(
boolean on)
throws SocketException