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