[轉載] 讀《UNIX網絡編程 卷1:套接字聯網API》

原文: http://cstdlib.com/tech/2014/10/09/read-unix-network-programming-1/html

文章寫的很清楚, 適合初學者shell

 

最近看了《UNIX網絡編程 卷1:套接字聯網API》, 英文名叫Unix Network Programming啦,後來上網查了查, 通常都叫UNP逼格會高一點, 就像APUE同樣。 他們的做者都是W. Richard Stevens。 另外,他也是TCP/IP Illustrated的做者。 靠,看完做者簡介,簡直崇拜得五體投地了。編程

說說這本書中比較讓我印象深入的內容吧,我只看了書中關於關於TCP和UDP的主要部分,略過了一些章節,因此可能有一些遺漏。服務器

  • TCP和UDP的工做過程
  • TCP鏈接在「非正常」狀況下的工做情況
  • 各類I/O模型(阻塞/非阻塞/IO複用/信號驅動/異步)
  • 守護進程和inetd的工做原理
  • 服務器程序設計範式
  • 客戶端程序設計範式

TCP和UDP的工做過程

UDP的工做過程是簡單的,僅僅將用戶數據封裝到一個IP數據報中發送到目的地而已,而不關注其餘方面。網絡

TCP倒是一個極其複雜的協議,如下只是冰山一角併發

  • 創建鏈接的三次握手
    1. 主動方發送(SYN J),進入SYN_SENT狀態
    2. 被動方收到(SYN J),並往回發送(SYN K, ACK J+1),進入SYN_RCVD狀態
    3. 主動方收到(SYN K, ACK J+1),並往回發送(ACK K+1),進入ESTABLISHED狀態
    4. 被動方收到(ACK K+1),也進入ESTABLISHED狀態

    以上過程以下圖所示:異步

    establish

    注意到在TCP三次握手的過程當中,服務器有這麼一條:tcp

    2. 被動方收到(SYN J),並往回發送(SYN K, ACK J+1),進入SYN_RCVD狀態設計

    服務器進入SYN_RCVD狀態(此時鏈接稱爲半開鏈接)後,應當期待再收到一個ACK。 若是超時未收到客戶端的ACK,服務器將重發(SYN K, ACK J+1)。 因而,就有一種叫作SYN Flooding的攻擊方式。 攻擊者向服務器高速發送(SYN J)(並且能夠將SYN分節中的IP地址設爲隨機數), 而且在隨後收到服務器回覆的(SYN K, ACK J+1)以後再也不繼續回覆, 這使得服務器上存在不少的半開鏈接,這些半開鏈接通常狀況下會持續63秒 (在Linux下,默認重試次數爲5次,重試的間隔時間從1s開始每次都翻倍,5次的重試時間間隔爲1s, 2s, 4s, 8s, 16s,第5次發出後還要等32s都知道第5次也超時了,因此,總共須要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s,TCP纔會把斷開這個鏈接)。 它的危害有兩方面,一方面天然是佔用了服務器的資源;另外一方面是填充了半開鏈接的隊列,使得合法的SYN分節沒法排隊。unix

    根據SYN Flooding的攻擊原理,它的防範主要有如下措施:

    1. 過濾掉最大嫌疑攻擊的IP或IP段
    2. tcp_synack_retries設爲0,表示迴應第二個握手包(SYN K, ACK J+1)給客戶端後,若是收不到ACK,不進行重試,加快回收「半開鏈接」。
    3. tcp_max_syn_backlog參數根據內存狀況適當調大,該參數通常指的是維護的半開鏈接的隊列的長度(不一樣OS不同)。
    4. 設置tcp_abort_on_overflow選項,處理不過來就直接拒絕掉。
  • 斷開鏈接的四次握手
    1. 主動方發送(FIN M),進入FIN_WAIT_1狀態
    2. 被動方收到(FIN M),並往回發送(ACK M+1),進入CLOSE_WAIT狀態
    3. 主動方收到(ACK M+1),進入FIN_WAIT_2狀態
    4. 被動方發送(FIN N),進入LAST_ACK狀態
    5. 主動方收到(FIN N),並往回發送(ACK N+1),進入TIME_WAIT狀態
    6. 被動方收到(ACK N+1),進入CLOSED狀態
    7. 主動方在TIME_WAIT狀態中超時後,進入CLOSED狀態

    以上過程以下圖所示:

    close

    其實就是2次,只不過TCP是全雙工的,因此,發送方和接收方都須要FIN和ACK。 只不過,有一方是被動的,因此看上去就成了所謂的4次揮握手。

    注意到最後有這麼一條涉及到TIME_WAIT的狀態

    7. 主動方在TIME_WAIT狀態中超時後,進入CLOSED狀態

    須要通過一個TIME_WAIT超時的狀態而不是直接進入CLOSED的緣由有兩個,一是確保有足夠的時間讓對端收到ACK,二是容許老的分節在網絡中慢慢的消逝。

    然而,若是系統中存在着大量的短連接,那麼大量的TIME_WAIT狀態就會成爲系統的累贅。網上一些資料提到的tcp_tw_reusetcp_tw_recycle選項來解決這個問題,可是最好仍是別亂用,好像coolshell中有提到過,可能會出不少詭異的問題。還能夠調整tcp_max_tw_buckets,當併發的TIME_WAIT過多時,會直接把多的給destory掉,而後在日誌裏打一個警告。引用一句「其實,TIME_WAIT表示的是你主動斷鏈接,因此,這就是所謂的no zuo, no die」。

TCP鏈接在「非正常」狀況下的工做情況

  • 服務器進程終止

    首先,服務器進程終止(收到SIGKILL信號)。做爲進程停止處理的工做之一,該進程全部打開着的描述符將被關閉,這會致使向對端(客戶端)發送(FIN N),而客戶端則回覆(ACK N+1),這就是TCP斷開鏈接的前半部分。

    而後,此時客戶端收到(FIN N)並不意味着鏈接斷開(雖然在這個例子中,確實斷開了),只是意味着服務器再也不向客戶端發送數據了,客戶端還能夠繼續向服務器發送數據。若是此時客戶端還繼續向服務器發送數據,服務器TCP將發現以前的打開該套接字的進程已終止,因而回應一個RST。客戶端在收到這個RST以前的read操做將會返回EOF,在收到這個RST後的read操做會返回ECONNRESET錯誤,在收到這個RST後的write操做會使當前進程收到SIGPIPE信號。

    以上過程以下圖所示:

    server_kill

  • 服務器主機崩潰

    服務器主機崩潰的意思是,沒有任何預兆,來不及在網絡上發送任何消息,主機就沒法工做了。這種狀況等價於直接切斷網絡,或者通俗的說,能夠直接拔掉網線來模擬這一狀況。

    這時,若是客戶端向服務器發送數據,後調用read操做,TCP會一直等待服務器的ACK確認消息,而且不斷的超時重傳(按照Berkeley的實現,重傳12次,共需9分鐘),直到到達重傳次數,返回ETIMEOUT錯誤。若是是由中間的路由器斷定服務器主機不可達,響應「destination unreasonable」的ICMP消息,將返回EHOSTUNREACHENETUNREACH錯誤。

  • 服務器主機崩潰後重啓

    重啓以後的服務器已經丟失了以前的TCP信息,因此即便收到了客戶端發來的TCP數據,也會回覆RST,日後的狀況和「服務器主機崩潰」中提到的相似。

  • 服務器主機關機

    Unix系統關機時,init進程一般會給其餘進程發送SIGTERM信號,而後等待10s左右給仍在運行的進程發送SIGKILL信號。因此若是進程不捕獲SIGTERM信號,則將由SIGKILL信號終止,和「服務器進程終止」中提到的相似。

再日後的I/O模型、守護進程和inetd的工做原理、服務器程序設計範式、客戶端程序設計範式過幾天有時間再寫吧。

相關文章
相關標籤/搜索