原文: 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的主要部分,略過了一些章節,因此可能有一些遺漏。服務器
UDP的工做過程是簡單的,僅僅將用戶數據封裝到一個IP數據報中發送到目的地而已,而不關注其餘方面。網絡
TCP倒是一個極其複雜的協議,如下只是冰山一角併發
(SYN J)
,進入SYN_SENT
狀態(SYN J)
,並往回發送(SYN K, ACK J+1)
,進入SYN_RCVD
狀態(SYN K, ACK J+1)
,並往回發送(ACK K+1)
,進入ESTABLISHED
狀態(ACK K+1)
,也進入ESTABLISHED
狀態以上過程以下圖所示:異步
注意到在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的攻擊原理,它的防範主要有如下措施:
tcp_synack_retries
設爲0,表示迴應第二個握手包(SYN K, ACK J+1)
給客戶端後,若是收不到ACK,不進行重試,加快回收「半開鏈接」。tcp_max_syn_backlog
參數根據內存狀況適當調大,該參數通常指的是維護的半開鏈接的隊列的長度(不一樣OS不同)。tcp_abort_on_overflow
選項,處理不過來就直接拒絕掉。(FIN M)
,進入FIN_WAIT_1
狀態(FIN M)
,並往回發送(ACK M+1)
,進入CLOSE_WAIT
狀態(ACK M+1)
,進入FIN_WAIT_2
狀態(FIN N)
,進入LAST_ACK
狀態(FIN N)
,並往回發送(ACK N+1)
,進入TIME_WAIT
狀態(ACK N+1)
,進入CLOSED
狀態TIME_WAIT
狀態中超時後,進入CLOSED
狀態以上過程以下圖所示:
其實就是2次,只不過TCP是全雙工的,因此,發送方和接收方都須要FIN和ACK。 只不過,有一方是被動的,因此看上去就成了所謂的4次揮握手。
注意到最後有這麼一條涉及到TIME_WAIT
的狀態
7. 主動方在
TIME_WAIT
狀態中超時後,進入CLOSED
狀態
須要通過一個TIME_WAIT
超時的狀態而不是直接進入CLOSED
的緣由有兩個,一是確保有足夠的時間讓對端收到ACK,二是容許老的分節在網絡中慢慢的消逝。
然而,若是系統中存在着大量的短連接,那麼大量的TIME_WAIT
狀態就會成爲系統的累贅。網上一些資料提到的tcp_tw_reuse
和tcp_tw_recycle
選項來解決這個問題,可是最好仍是別亂用,好像coolshell中有提到過,可能會出不少詭異的問題。還能夠調整tcp_max_tw_buckets
,當併發的TIME_WAIT
過多時,會直接把多的給destory掉,而後在日誌裏打一個警告。引用一句「其實,TIME_WAIT表示的是你主動斷鏈接,因此,這就是所謂的no zuo, no die」。
首先,服務器進程終止(收到SIGKILL
信號)。做爲進程停止處理的工做之一,該進程全部打開着的描述符將被關閉,這會致使向對端(客戶端)發送(FIN N)
,而客戶端則回覆(ACK N+1)
,這就是TCP斷開鏈接的前半部分。
而後,此時客戶端收到(FIN N)
並不意味着鏈接斷開(雖然在這個例子中,確實斷開了),只是意味着服務器再也不向客戶端發送數據了,客戶端還能夠繼續向服務器發送數據。若是此時客戶端還繼續向服務器發送數據,服務器TCP將發現以前的打開該套接字的進程已終止,因而回應一個RST
。客戶端在收到這個RST
以前的read操做將會返回EOF,在收到這個RST
後的read操做會返回ECONNRESET
錯誤,在收到這個RST
後的write操做會使當前進程收到SIGPIPE
信號。
以上過程以下圖所示:
服務器主機崩潰的意思是,沒有任何預兆,來不及在網絡上發送任何消息,主機就沒法工做了。這種狀況等價於直接切斷網絡,或者通俗的說,能夠直接拔掉網線來模擬這一狀況。
這時,若是客戶端向服務器發送數據,後調用read操做,TCP會一直等待服務器的ACK確認消息,而且不斷的超時重傳(按照Berkeley的實現,重傳12次,共需9分鐘),直到到達重傳次數,返回ETIMEOUT
錯誤。若是是由中間的路由器斷定服務器主機不可達,響應「destination unreasonable」的ICMP消息,將返回EHOSTUNREACH
和ENETUNREACH
錯誤。
重啓以後的服務器已經丟失了以前的TCP信息,因此即便收到了客戶端發來的TCP數據,也會回覆RST
,日後的狀況和「服務器主機崩潰」中提到的相似。
Unix系統關機時,init進程一般會給其餘進程發送SIGTERM
信號,而後等待10s左右給仍在運行的進程發送SIGKILL
信號。因此若是進程不捕獲SIGTERM
信號,則將由SIGKILL
信號終止,和「服務器進程終止」中提到的相似。
再日後的I/O模型、守護進程和inetd的工做原理、服務器程序設計範式、客戶端程序設計範式過幾天有時間再寫吧。