概述
本章主要實現的程序模型:
2 TCP回射服務器程序
服務器與客戶程序約定一個固定的端口,要比5000大,比49152小。
fork后子進程第一件事就是關掉listenfd,父進程的第一件事是關掉connfd。
在等待客戶的read調用返回出錯后,如果是因為被信號打斷,要重新調用read。
正常情況
正常啟動
監聽套接字處于LISTEN狀態。
客戶的connect在三路握手的第二個分節就返回了,而服務器要直到第三個分節才返回,即客戶的connect返回時服務器還沒有accept。
服務器的連接套接字處于ESTABBLISHED狀態。
正常終止
客戶端主動關閉時,客戶TCP發送一個FIN給服務器,服務器TCP響應ACK,此時服務器處于CLOSE_WAIT狀態,客戶處于FIN_WAIT_2狀態。
服務器關閉時,服務器發送FIN給客戶,客戶發送ACK給服務器,連接完全終止,客戶進入TIME_WAIT狀態。
服務器子進程終止時給父進程發送一個SIGCHLD信號。默認行為是忽略,但我們必須捕捉此信號,清理僵死進程。
POSIX信號處理
信號是某個進程發生了某個事件的通知,有時也稱為軟件中斷,通常是異步發生的,也就是說進程預先不知道信號的準確發生時刻。
信號可以:
由一個進程發給另一個進程
由內核發給某個進程
每個信號都有一個與之關聯的處置也稱行為。
處理SIGCHLD信號
多進程下父進程必須捕捉SIGCHLD信號以回收終止狀態的子進程資源,否則進程處于僵尸狀態。可以在信號處理函數中用wait或waitpid。慢速系統調用會被信號處理函數打斷,可能會返回EINTR錯誤,也可能會自動重啟。我們編寫捕獲信號的程序時,必須對此有所準備。例如,對accept的處理,connect被打斷后就不能被使用了。
wait和waitpid函數
通過wait和waitpid都可以獲得終止的子進程的pid和狀態,waitpid還能指定想等待的pid,options參數允許指定附加選項,最常用的是WNOHANG,在沒有終止子進程時不阻塞。
信號阻塞期間如果該信號產生了多次,解除阻塞后只能接收到一次,因此要用waitpid(-1,*,WNOHANG)來循環回收所有結束的子進程。
accept返回前連接中止
三路握手完成,連接建立后,客戶TCP發送了RST,服務器端在調用accept前收到了這個RST。
如何處理這種中止依賴于不同的實現。BSD的實現是在內核中處理,服務器的accept繼續阻塞,SVR4的實現是返回一個錯誤給進程。如果返回了一個錯誤,再次調用accept就行。
服務器進程終止
客戶與服務器連接成功后,服務器進程如果終止(被動),套接字被關閉,向客戶發送FIN,客戶響應ACK,此時客戶進程可能阻塞在用戶輸出上,看不到這個RST,此時如果進行write,再read,就會收到預期外的EOF。
SIGPIPE信號
寫一個已收到FIN的套接字會收到RST,寫一個已收到RST的套接字會產生SIGPIPE信號。
如果沒有特殊的事情要做,就將SIGPIPE設置為SIG_IGN,忽略它,并在后面的讀寫操作中檢查返回的錯誤。如果需要采取特殊措施(如寫入日志),就要捕捉該信號。但如果用了多個套接字,信號處理程序無法分辨是哪個套接字出的錯。如果需要知道出錯的位置,要么不理會該信號,要么從信號處理函數返回后再處理write的EPIPE。
服務器主機崩潰 服務器主機關機
服務器主機崩潰時,已有的網絡連接上不發出任何東西(如FIN)。客戶對服務器的寫操作會持續重傳數據,試圖接收一個ACK,直到超時。客戶隨后的readline調用會返回一個錯誤。
可以對readline設置一個超時。如果不主動向服務器發送數據也想檢測出服務器主機的崩潰,需要SO_KEEPALIVE套接字選項。
服務器主機崩潰后重啟
服務器主機在崩潰后客戶發數據前重啟完成,客戶不知道服務器主機的崩潰,發送數據,但服務器TCP丟失了之前的連接信息,因此響應RST,客戶TCP收到RST時,客戶進程正阻塞于readline調用,該調用返回一個錯誤。
數據格式
例子:在客戶與服務器之間傳遞文本串
用sscanf獲取文本中的指定數據,再用snprintf把結果轉換為文本串。
例子:在客戶與服務器之間傳遞二進制結構
當這樣的客戶和服務器程序運行在字節序不一樣的或某些類型長度不一致的兩個主機上時,工作將失常。
不同的實現在存儲二進制數據的格式上(大端小端)、相同類型的長度上、給結構打包的方式(對齊)上都可能不同,因此直接傳送二進制結構絕不明智。
解決方法:
所有的數值數據作為文本串來傳遞。
顯式定義所支持數據類型的進進制格式,并以這樣的格式在客戶與服務器間傳遞所有數據。