天天看點

Linux 基礎知識

端口

TCP協定

TCP協定規定,對于已經建立的連接配接,網絡雙方要進行四次握手才能成功斷開連接配接,如果缺少了其中某個步驟,将會使連接配接處于假死狀态,連接配接本身占用的資源不會被釋放。網絡伺服器程式要同時管理大量連接配接,是以很有必要保證無用連接配接完全斷開,否則大量僵死的連接配接會浪費許多伺服器資源。在衆多TCP狀态中,最值得注意的狀态有兩個:CLOSE_WAIT和TIME_WAIT。

TIMEWAIT

TIMEWAIT是友好的

    TCP要保證在所有可能的情況下使得所有的資料都能夠被正确送達。當你關閉一個socket時,主動關閉一端的socket将進入TIME_WAIT狀态,而被動關閉一方則轉入CLOSED狀态,這的确能夠保證所有的資料都被傳輸。當一個socket關閉的時候,是通過兩端四次握手完成的,當一端調用close()時,就說明本端沒有資料要發送了。這好似看來在握手完成以後,socket就都可以處于初始的CLOSED狀态了,其實不然。原因是這樣安排狀态有兩個問題, 首先,我們沒有任何機制保證最後的一個ACK能夠正常傳輸,第二,網絡上仍然有可能有殘餘的資料包(wandering duplicates),我們也必須能夠正常處理。

存在即合理

TIMEWAIT就是為了解決這兩個問題而生的。

  1. 假設最後一個ACK丢失了,被動關閉一方會重發它的FIN。主動關閉一方必須維持一個有效狀态資訊(TIMEWAIT狀态下維持),以便能夠重發ACK。如果主動關閉的socket不維持這種狀态而進入CLOSED狀态,那麼主動關閉的socket在處于CLOSED狀态時,接收到FIN後将會響應一個RST。被動關閉一方接收到RST後會認為出錯了。如果TCP協定想要正常完成必要的操作而終止雙方的資料流傳輸,就必須完全正确的傳輸四次握手的四個節,不能有任何的丢失。這就是為什麼socket在關閉後,仍然處于TIME_WAIT狀态的第一個原因,因為他要等待以便重發ACK。
  2. 假設目前連接配接的通信雙方都已經調用了close(),雙方同時進入CLOSED的終結狀态,而沒有走TIME_WAIT狀态。會出現如下問題,現在有一個新的連接配接被建立起來,使用的IP位址與端口與先前的完全相同,後建立的連接配接是原先連接配接的一個完全複用。還假定原先的連接配接中有資料報殘存于網絡之中,這樣新的連接配接收到的資料報中有可能是先前連接配接的資料報。為了防止這一點,TCP不允許新連接配接複用TIME_WAIT狀态下的socket。處于TIME_WAIT狀态的socket在等待兩倍的MSL時間以後(之是以是兩倍的MSL,是由于MSL是一個資料報在網絡中單向發出到認定丢失的時間,一個資料報有可能在發送途中或是其響應過程中成為殘餘資料報,确認一個資料報及其響應的丢棄的需要兩倍的MSL),将會轉變為CLOSED狀态。這就意味着,一個成功建立的連接配接,必然使得先前網絡中殘餘的資料報都丢失了。

相關問題

time_wait連接配接數過多

調整核心參數:/etc/sysctl.conf

vim /etc/sysctl.conf

添加以下配置檔案:

net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 300
           

/sbin/sysctl -p 讓參數生效,調優完成.

參數詳解:

  1. net.ipv4.tcp_syncookies = 1 表示開啟 syn cookies 。當出現 syn 等待隊列溢出時,啟用 cookies 來處理,可防範少量 syn 攻擊,預設為 0 ,表示關閉;
  2. net.ipv4.tcp_tw_reuse = 1 表示開啟重用。允許将 time-wait sockets 重新用于新的 tcp 連接配接,預設為 0 ,表示關閉;
  3. net.ipv4.tcp_tw_recycle = 1 表示開啟 tcp 連接配接中 time-wait sockets 的快速回收,預設為 0 ,表示關閉。
  4. net.ipv4.tcp_fin_timeout 修改系靳預設的 timeout 時間

如果以上配置調優後性能還不理想,可繼續修改一下配置:

vi /etc/sysctl.conf
net.ipv4.tcp_keepalive_time = 1200 #表示當keepalive起用的時候,TCP發送keepalive消息的頻度。預設是2小時,改為20分鐘。
net.ipv4.ip_local_port_range = 1024 65000 #表示用于向外連接配接的端口範圍。預設情況下很小:32768到61000,改為1024到65000。
net.ipv4.tcp_max_syn_backlog = 8192 #表示SYN隊列的長度,預設為1024,加大隊列長度為8192,可以容納更多等待連接配接的網絡連接配接數。
net.ipv4.tcp_max_tw_buckets = 5000 #表示系統同時保持TIME_WAIT套接字的最大數量,如果超過這個數字,TIME_WAIT套接字将立刻被清除并列印警告資訊。預設為180000,改為5000。
#對于Apache、Nginx等伺服器,上幾行的參數可以很好地減少TIME_WAIT套接字數量,但是對于 Squid,效果卻不大。此項參數可以控制TIME_WAIT套接字的最大數量,避免Squid伺服器被大量的TIME_WAIT套接字拖死。
           

簡單來說,就是打開系統的TIMEWAIT重用和快速回收,至于怎麼重用和快速回收,這個問題我沒有深究,實際場景中這麼做确實有效果。用netstat或者ss觀察就能得出結論。

還有些朋友同時也會打開syncookies這個功能,如下:

net.ipv4.tcp_syncookies = 1

打開這個syncookies的目的實際上是:“在伺服器資源(并非單指端口資源,拒絕服務有很多種資源不足的情況)不足的情況下,盡量不要拒絕TCP的syn(連接配接)請求,盡量把syn請求緩存起來,留着過會兒有能力的時候處理這些TCP的連接配接請求”。

如果并發量真的非常非常高,打開這個其實用處不大。

ESTABLISHED

建立連接配接。表示兩台機器正在通信。

LISTENING

FTP服務啟動後首先處于偵聽(LISTENING)狀态。

CLOSE_WAIT

對方主動關閉連接配接或者網絡異常導緻連接配接中斷,這時我方的狀态會變成CLOSE_WAIT 此時我方要調用close()來使得連接配接正确關閉

SYN_SENT

SYN_SENT狀态表示請求連接配接,當你要通路其它的計算機的服務時首先要發個同步信号給該端口,此時狀态為SYN_SENT,如果連接配接成功了就變為 ESTABLISHED,此時SYN_SENT狀态非常短暫。但如果發現SYN_SENT非常多且在向不同的機器發出,那你的機器可能中了沖擊波或震蕩波 之類的病毒了。這類病毒為了感染别的計算機,它就要掃描别的計算機,在掃描的過程中對每個要掃描的計算機都要發出了同步請求,這也是出現許多 SYN_SENT的原因.

端口狀态說明圖

握手圖

通常情況下,一個正常的TCP連接配接,都會有三個階段:1、TCP三向交握; 2、資料傳送; 3、TCP四次揮手

注:以下說明最好能結合”圖:TCP的狀态機”來了解。

名詞解釋

SYN: (同步序列編号,Synchronize Sequence Numbers)該标志僅在三次握手建立TCP連接配接時有效。表示一個新的TCP連接配接請求。

ACK: (确認編号,Acknowledgement Number)是對TCP請求的确認标志,同時提示對端系統已經成功接收所有資料。

FIN: (結束标志,FINish)用來結束一個TCP回話.但對應端口仍處于開放狀态,準備接收後續資料。

狀态說明

1)、LISTEN:首先服務端需要打開一個socket進行監聽,狀态為LISTEN. /* The socket is listening for incoming connections. 偵聽來自遠方TCP端口的連接配接請求 /

2)、SYN_SENT:用戶端通過應用程式調用connect進行active open.于是用戶端tcp發送一個SYN以請求建立一個連接配接.之後狀态置為SYN_SENT. /The socket is actively attempting to establish a connection. 在發送連接配接請求後等待比對的連接配接請求 */

3)、SYN_RECV:服務端應發出ACK确認用戶端的SYN,同時自己向用戶端發送一個SYN. 之後狀态置為SYN_RECV /* A connection request has been received from the network. 在收到和發送一個連接配接請求後等待對連接配接請求的确認 */

4)、ESTABLISHED: 代表一個打開的連接配接,雙方可以進行或已經在資料互動了。/* The socket has an established connection. 代表一個打開的連接配接,資料可以傳送給使用者 */

5)、FIN_WAIT1:主動關閉(active close)端應用程式調用close,于是其TCP發出FIN請求主動關閉連接配接,之後進入FIN_WAIT1狀态./* The socket is closed, and the connection is shutting down. 等待遠端TCP的連接配接中斷請求,或先前的連接配接中斷請求的确認 */

6)、CLOSE_WAIT:被動關閉(passive close)端TCP接到FIN後,就發出ACK以回應FIN請求(它的接收也作為檔案結束符傳遞給上層應用程式),并進入CLOSE_WAIT. /* The remote end has shut down, waiting for the socket to close. 等待從本地使用者發來的連接配接中斷請求 */

7)、FIN_WAIT2:主動關閉端接到ACK後,就進入了FIN-WAIT-2 ./* Connection is closed, and the socket is waiting for a shutdown from the remote end. 從遠端TCP等待連接配接中斷請求 */

8)、LAST_ACK:被動關閉端一段時間後,接收到檔案結束符的應用程式将調用CLOSE關閉連接配接。這導緻它的TCP也發送一個 FIN,等待對方的ACK.就進入了LAST-ACK . /* The remote end has shut down, and the socket is closed. Waiting for acknowledgement. 等待原來發向遠端TCP的連接配接中斷請求的确認 */

9)、TIME_WAIT:在主動關閉端接收到FIN後,TCP就發送ACK包,并進入TIME-WAIT狀态。/* The socket is waiting after close to handle packets still in the network.等待足夠的時間以確定遠端TCP接收到連接配接中斷請求的确認 */

10)、CLOSING: 比較少見./* Both sockets are shut down but we still don’t have all our data sent. 等待遠端TCP對連接配接中斷的确認 */

11)、CLOSED: 被動關閉端在接受到ACK包後,就進入了closed的狀态。連接配接結束./* The socket is not being used. 沒有任何連接配接狀态 */

TIME_WAIT狀态的形成隻發生在主動關閉連接配接的一方。

主動關閉方在接收到被動關閉方的FIN請求後,發送成功給對方一個ACK後,将自己的狀态由FIN_WAIT2修改為TIME_WAIT,而必須再等2倍 的MSL(Maximum Segment Lifetime,MSL是一個資料報在internetwork中能存在的時間)時間之後雙方才能把狀态 都改為CLOSED以關閉連接配接。目前RHEL裡保持TIME_WAIT狀态的時間為60秒。當然上述很多TCP狀态在系統裡都有對應的解釋或設定,可見man tcp

二、關于長連接配接和短連接配接:

通俗點講,短連接配接就是一次TCP請求得到結果後,連接配接馬上結束.而長連接配接并不馬上斷開,而一直保持着,直到長連接配接TIMEOUT(具體程式都有相關參數說明).長連接配接可以避免不斷的進行TCP三向交握和四次揮手.

長連接配接(keepalive)是需要靠雙方不斷的發送探測包來維持的,keepalive期間服務端和用戶端的TCP連接配接狀态是ESTABLISHED.目前http 1.1版本裡預設都是keepalive(1.0版本預設是不keepalive的),ie6/7/8和firefox都預設用的是http 1.1版本了(如何檢視目前浏覽器用的是哪個版本,這裡不再贅述)。Apache,java

一個應用至于到底是該使用短連接配接還是長連接配接,應該視具體情況而定。一般的應用應該使用長連接配接。

TCP 四次握手

TCP協定有一個優雅的關閉(graceful close)機制,以保證應用程式在關閉連接配接時不必擔心正在傳輸的資料會丢失。如第4.5節的壓縮示例程式所示,這個機制還設計為允許兩個方向的資料傳輸互相獨立地終止。關閉機制的工作流程是:應用程式通過調用連接配接套接字的close()方法或shutdownOutput()方法表明資料已經發送完畢。此刻,底層的TCP實作首先将留存在SendQ隊列中的資料傳輸出去(還要依賴于另一端RecvQ隊列的剩餘空間),然後向另一端發送一個關閉TCP連接配接的握手消息。該關閉握手消息可以看作是流終止标志:它告訴接收端TCP不會再有新的資料傳入RecvQ隊列了。(注意,關閉握手消息本身并沒有傳遞給接收端應用程式,而是通過read()方法傳回-1來訓示其在位元組流中的位置。)正在關閉的TCP将等待其關閉握手消息的确認資訊,該确認資訊表明在連接配接上傳輸的所有資料已經安全地傳輸到了RecvQ中。隻要收到了确認消息,該連接配接就變成”半關閉(Half closed)”狀态。直到連接配接的另一個方向上收到了對稱的握手消息後,連接配接才完全關閉–也就是說,連接配接的兩端都表明它們再沒有資料要發送了。

TCP連接配接的關閉事件序列可能以兩種方式發生:一種方式是先由一個應用程式調用close()方法(或shutdownOutput()方法),并在另一端調用close()方法之前完成其關閉握手消息;另一種方式是兩端同時調用close()方法,它們的關閉握手消息在網絡上交叉傳輸。圖6.10展示了以第一種方式關閉連接配接時,底層實作中的事件序列。關閉握手消息已經發送,套接字資料結構的狀态也已經設定為”Closing”(專業術語稱為”FIN_WAIT_1″),然後close()調用傳回。完成這些工作後,将禁止在該Socket上的任何讀寫操作(會抛出異常)。當收到關閉握手确認消息後,套接字資料結構的狀态則改變為”半關閉”(專業術語稱為”FIN_WAIT_2″),這種狀态将一直持續,直到接收到另一端的關閉握手消息

關閉TCP連接配接的最後微妙之處在于對Time-Wait狀态的需要。TCP規範要求在終止連接配接時,兩端的關閉握手都完成後,至少要有一個套接字在Time-Wait狀态保持一段時間。這個要求的提出是由于消息在網絡中傳輸時可能延遲。如果在連接配接兩端都完成了關閉握手後,它們都移除了其底層資料結構,而此時在同樣一對套接字位址之間又立即建立了新的連接配接,那麼前一個連接配接在網絡上傳輸時延遲的消息就可能在新連接配接建立後到達。由于其包含了相同的源位址和目的位址,舊消息就會被錯誤地認為是屬于新連接配接的,其包含的資料就可能被錯誤地配置設定到應用程式中。

雖然這種情形可能很少發生,TCP還是使用了包括Time-Wait狀态在内的多種機制對其進行防範。Time-Wait狀态用于保證每個TCP連接配接都在一段平靜時間内結束,這期間不會有資料發送。平靜時間的長度應該等于分組封包在網絡上存留的最長時間的兩倍。是以,當一個連接配接完全結束(即套接字資料結構離開Time-Wait狀态并被删除),并為同樣一對位址上的新連接配接清理道路後,就不會再有舊執行個體發送的消息還存留在網絡中。實際上,平靜時間的長度要依賴于具體實作,因為沒有機制能真正限制分組封包在網絡上能夠延遲的時間。通常使用的時間範圍是4分鐘減到30秒,或更短。

Time-Wait狀态最重要的作用是,隻要底層套接字資料結構還存在,就不允許在相同的本地端口上關聯其他套接字。尤其是試圖使用該端口建立新的Socket執行個體時,将抛出IOException異常。

TCP三向交握/四次揮手詳解

1、建立連接配接協定(三次握手)

(1)用戶端發送一個帶SYN标志的TCP封包到伺服器。這是三次握手過程中的封包1。

(2) 伺服器端回應用戶端的,這是三次握手中的第2個封包,這個封包同時帶ACK标志和SYN标志。是以它表示對剛才用戶端SYN封包的回應;同時又标志SYN給用戶端,詢問用戶端是否準備好進行資料通訊。

(3) 客戶必須再次回應服務段一個ACK封包,這是封包段3。

2、連接配接終止協定(四次揮手)

由于TCP連接配接是全雙工的,是以每個方向都必須單獨進行關閉。這原則是當一方完成它的資料發送任務後就能發送一個FIN來終止這個方向的連接配接。收到一個 FIN隻意味着這一方向上沒有資料流動,一個TCP連接配接在收到一個FIN後仍能發送資料。首先進行關閉的一方将執行主動關閉,而另一方執行被動關閉。

(1) TCP用戶端發送一個FIN,用來關閉客戶到伺服器的資料傳送(封包段4)。

(2) 伺服器收到這個FIN,它發回一個ACK,确認序号為收到的序号加1(封包段5)。和SYN一樣,一個FIN将占用一個序号。

(3) 伺服器關閉用戶端的連接配接,發送一個FIN給用戶端(封包段6)。

(4) 客戶段發回ACK封包确認,并将确認序号設定為收到序号加1(封包段7)。

CLOSED: 這個沒什麼好說的了,表示初始狀态。

LISTEN: 這個也是非常容易了解的一個狀态,表示伺服器端的某個SOCKET處于監聽狀态,可以接受連接配接了。

SYN_RCVD: 這個狀态表示接受到了SYN封包,在正常情況下,這個狀态是伺服器端的SOCKET在建立TCP連接配接時的三次握手會話過程中的一個中間狀态,很短暫,基本上用netstat你是很難看到這種狀态的,除非你特意寫了一個用戶端測試程式,故意将三次TCP握手過程中最後一個ACK封包不予發送。是以這種狀态時,當收到用戶端的ACK封包後,它會進入到ESTABLISHED狀态。

SYN_SENT: 這個狀态與SYN_RCVD遙想呼應,當用戶端SOCKET執行CONNECT連接配接時,它首先發送SYN封包,是以也随即它會進入到了SYN_SENT狀态,并等待服務端的發送三次握手中的第2個封包。SYN_SENT狀态表示用戶端已發送SYN封包。

ESTABLISHED:這個容易了解了,表示連接配接已經建立了。

FIN_WAIT_1: 這個狀态要好好解釋一下,其實FIN_WAIT_1和FIN_WAIT_2狀态的真正含義都是表示等待對方的FIN封包。而這兩種狀态的差別是:FIN_WAIT_1狀态實際上是當SOCKET在ESTABLISHED狀态時,它想主動關閉連接配接,向對方發送了FIN封包,此時該SOCKET即進入到FIN_WAIT_1狀态。而當對方回應ACK封包後,則進入到FIN_WAIT_2狀态,當然在實際的正常情況下,無論對方何種情況下,都應該馬上回應ACK封包,是以FIN_WAIT_1狀态一般是比較難見到的,而FIN_WAIT_2狀态還有時常常可以用netstat看到。

FIN_WAIT_2:上面已經詳細解釋了這種狀态,實際上FIN_WAIT_2狀态下的SOCKET,表示半連接配接,也即有一方要求close連接配接,但另外還告訴對方,我暫時還有點資料需要傳送給你,稍後再關閉連接配接。

TIME_WAIT: 表示收到了對方的FIN封包,并發送出了ACK封包,就等2MSL後即可回到CLOSED可用狀态了。如果FIN_WAIT_1狀态下,收到了對方同時帶FIN标志和ACK标志的封包時,可以直接進入到TIME_WAIT狀态,而無須經過FIN_WAIT_2狀态。

CLOSING: 這種狀态比較特殊,實際情況中應該是很少見,屬于一種比較罕見的例外狀态。正常情況下,當你發送FIN封包後,按理來說是應該先收到(或同時收到)對方的ACK封包,再收到對方的FIN封包。但是CLOSING狀态表示你發送FIN封包後,并沒有收到對方的ACK封包,反而卻也收到了對方的FIN封包。什麼情況下會出現此種情況呢?其實細想一下,也不難得出結論:那就是如果雙方幾乎在同時close一個SOCKET的話,那麼就出現了雙方同時發送FIN封包的情況,也即會出現CLOSING狀态,表示雙方都正在關閉SOCKET連接配接。

CLOSE_WAIT: 這種狀态的含義其實是表示在等待關閉。怎麼了解呢?當對方close一個SOCKET後發送FIN封包給自己,你系統毫無疑問地會回應一個ACK封包給對方,此時則進入到CLOSE_WAIT狀态。接下來呢,實際上你真正需要考慮的事情是察看你是否還有資料發送給對方,如果沒有的話,那麼你也就可以close這個SOCKET,發送FIN封包給對方,也即關閉連接配接。是以你在CLOSE_WAIT狀态下,需要完成的事情是等待你去關閉連接配接。

LAST_ACK: 這個狀态還是比較容易好了解的,它是被動關閉一方在發送FIN封包後,最後等待對方的ACK封包。當收到ACK封包後,也即可以進入到CLOSED可用狀态了。

最後有2個問題的回答,我自己分析後的結論(不一定保證100%正确):

1、 為什麼建立連接配接協定是三次握手,而關閉連接配接卻是四次握手呢?

這是因為服務端的LISTEN狀态下的SOCKET當收到SYN封包的建連請求後,它可以把ACK和SYN(ACK起應答作用,而SYN起同步作用)放在一個封包裡來發送。但關閉連接配接時,當收到對方的FIN封包通知時,它僅僅表示對方沒有資料發送給你了;但未必你所有的資料都全部發送給對方了,是以你可以未必會馬上會關閉SOCKET,也即你可能還需要發送一些資料給對方之後,再發送FIN封包給對方來表示你同意現在可以關閉連接配接了,是以它這裡的ACK封包和FIN封包多數情況下都是分開發送的。

2、 為什麼TIME_WAIT狀态還需要等2MSL後才能傳回到CLOSED狀态?

這是因為:雖然雙方都同意關閉連接配接了,而且握手的4個封包也都協調和發送完畢,按理可以直接回到CLOSED狀态(就好比從SYN_SEND狀态到ESTABLISH狀态那樣);但是因為我們必須要假想網絡是不可靠的,你無法保證你最後發送的ACK封包會一定被對方收到,是以對方處于LAST_ACK狀态下的SOCKET可能會因為逾時未收到ACK封包,而重發FIN封包,是以這個TIME_WAIT狀态的作用就是用來重發可能丢失的ACK封包。

ฅ平平庸庸的普通人ฅ