(一) accept句柄耗盡:
服務器每次accept一個鏈接之后,都會分配新的socket資源。Linux下對每個進程所能使用的文件句柄數是有限制的,默認是1024。扣除stdin,stdout,stderr,只有1021個句柄可用。一旦出現了accept的連接數超過了這個限制后就會很尷尬:accept返回-1,errno = 24,即EMFILE。如果采用Select、poll 或者epoll LT方式,系統會不停通知你,但是你沒法拿到那個fd,也就不能處理,甚至連close都不能。只能尷尬地讓cpu跑在100%,期待着某個客戶端斷線,某個句柄恰好被close,然后才能處理之。
解決辦法有兩種,
1, 准備一個空閑的nullfd = open(“/dev/null”), close(nullFd),然后accept,close(socketFd),最后在nullfd = open(“/dev/null”),缺點,並不是線程安全的。
2, 直接在代碼中限制最大允許連接數,一旦超過則直接踢出。缺點,需要額外的關注。
(二) Socket資源不釋放
服務器未能有效地檢測和處理socket關閉事件,導致最后socket資源被耗盡,再也不能連入新的連接。
解決辦法:keepalive、網絡連接心跳檢測,定時關閉不活動的連接。
(三) 發送緩沖使用不當,數據包亂序。
在上篇文章中的例子程序就存在這樣的問題:當有數據要發送時,無視發送緩沖直接調用send函數。在同一連接中會發生數據A發送了一部分,又發送數據B的一部分,在發送數據A的一部分。令接收者無所適從。
解決辦法:從不直接調用send發送數據,應將發送數據放入發送緩沖中處理。
(四) 網絡串包:
將A的信息發給了B。通常發生在服務器用socketFd1接受A的請求,服務器處理中。A掉線,服務器close(socketFd1)。這時B再上線,POSIX標准要求每次打開文件的時候必須使用當前最小可用的文件描述符號,於是B使用了socketFd1句柄。服務器就將A的應答發送給了B.
解決辦法:1,socket錯誤處理時不能直接close了事。2,處理應答時不能直接將數據包丟給socket句柄,要建立某種機制確認socket句柄是你想發送的那個。
(五)服務器端太多的TIME_WAIT
主動關閉的Socket端會進入TIME_WAIT狀態,並且持續2MSL時間長度,MSL就是maximum segment lifetime(最大分節生命期),這是一個IP數據包能在互聯網上生存的最長時間,超過這個時間將在網絡中消失。MSL在RFC 1122上建議是2分鍾,而源自berkeley的TCP實現傳統上使用30秒,因而,TIME_WAIT狀態一般維持在1-4分鍾。如果每次關閉socket的動作都由服務器發起,那么TIME_WAIT狀態就會留在服務端,服務端的內核就需要維護更多的狀態。收到ip包,做hash運算,hlist沖突的概率更大。
解決辦法:使用shutdown(sockfd, SHUT_WR)通知客戶端,由客戶端執行close,將TIME_WAIT留在客戶端。
(六)沒有處理SIGPIPE引起服務器崩潰。
當一個進程向某個已收到RST的Socket執行寫操作時,內核將向進程發送一個SIGPIPE信號。該信號的默認行為是終止進程。
解決辦法:忽略該信號。
- struct sigaction act;
- act.sa_handler = SIG_IGN;
- if (sigaction(SIGPIPE, &act, NULL) == 0) {
- LOG("SIGPIPE ignore");
- }
(七)阻塞I/O沒有處理EINTR、非阻塞I/O沒有處理EAGAIN
對於慢系統調用,像accept, receive這樣有可能無法返回的函數的進程一旦捕獲了某個信號,並且相應的信號處理函數返回時,該系統調用有可能返回一個EINTR錯誤。
非阻塞模式下調用了阻塞操作,在該操作沒有完成就返回EAGAIN。EAGAIN不會破壞socket的同步,不用管它。