TCP-socket異常狀況

在Unix下進行網絡編程時,因爲網絡並不是徹底可靠,會遇到各類協議主流程外發生的各類錯誤。

而健壯的程序必須考慮到這些錯誤並正確處理,所以這裏總結網絡編程中可能發生的常見錯誤。html

TCP異常流程

整體

應答超時

​ 在握手,揮手以及消息傳遞的狀態下,若當前發送的報文期待一個應答報文。在規定時間應答報文沒有到達,發送方會重發兩次報文(報文重發間隔可設置)。若重發三次後依舊沒有收到應答,則嚮應用返回ETIMEOUT編程

目的不可達

​ 若報文在傳送的過程當中,由於找不到路由路徑,報文沒法到達等引起了ICMP錯誤,發送方會按照上述的方式重發次報文。若重發結束後依舊沒有收到應答,則嚮應用返回EHOSTUNREACH或者ENETUNREACHapi

禁止分片

​ 在IPV4中,報文超過了MTU長度會致使分片,而其存在DF(Don't Fragment)標識,表示禁止分片。同時IPV6禁止路由器分片,所以在傳送的過程當中隱含DF位。在傳送過程當中設置了DF位而超過了MTU,則會嚮應用返回EMSGSIZE緩存

阻塞時中斷

​ 在系統執行慢系統調用(可能被永遠阻塞的系統調用)時阻塞,此時捕獲到某個信號並進行了處理(系統對某些信號有默認處理方式),在沒有設置自動重啓的狀況下,會嚮應用返回EINTR服務器

​ 通常應對EINTR的方式是簡單的從新調用,但在connect返回EINTR時不能這麼作,由於connect涉及三次握手的過程,須要使用getsockopt獲取鏈接狀態網絡

讀寫時RST

​ 在調用read等阻塞時接收到對端RST信號時,會返回ECONNREST。同時對發送方斷開的套接字寫時也會返回ECONNRESETsocket

寫RST套接字

​ 當進程向收到RST的套接字執行寫操做的時候,內核向該進程發送一個SIGPIPE信號,該信號的默認行爲是終止進程,所以進程必須捕獲它以避免不情願的被終止async

​ 不論進程是捕捉了該信號並從信號處理函數中返回,仍是簡單忽略該信號,寫操做都講返回EPIPE錯誤函數

握手部分

首次握手服務端RST

1.jpg

​ 在客戶端第一次握手時,若服務端返回RST報文,當即嚮應用返回ECONNREFUSEDspa

握手結束客戶端RST

2.jpg

​ 在較爲繁忙的服務器中,可能出現上圖客戶端剛經歷三次握手後隨機發送RST報文的狀況,posix指出這種狀況errno設置爲ECONNABORTED,只須要再次調用accept便可。

​ 而在Berkeley的實現中,返回EPROTO錯誤,表明協議錯誤,是一種致命錯誤,由內核把該鏈接從已完成鏈接套接字隊列中釋放,若再次調用accept,則不會處理到本次請求,可能致使阻塞

數據傳送部分

服務端主機崩潰

​ 因爲客戶端沒法收到服務端的任何迴應,會重發處理,最終嚮應用返回的狀況可能爲應答超時或目的不可達

​ 若想盡快的檢測出主機崩潰,不主動發送數據也可作到,即套接字選項的SO_KEEPALIVE(相似心跳機制)

揮手部分

3.jpg

服務端進程終止或關機

​ 服務端因爲進程崩潰或者手動kill後,進程終止關閉全部打開的描述符。這致使了其向客戶端發送了一個FIN,客戶端則響應了一個ack,TCP揮手的前半部分完成,服務端不在發送數據。

​ 可是此時客戶端並不知道服務器端已經終止了。當客戶端向服務器寫數據的時候,因爲服務器進程終止,因此響應了RST

​ 這種狀況下能夠由select或者poll檢測到服務端的終止。

  • 若是對端TCP發送數據,套接字可讀,而且read返回一個大於0的值(讀入字節數)
  • 若是對端TCP發送了FIN(對端進程終止),套接字可讀,而且read返回0(EOF)
  • 若是對端TCP發送RST(對端崩潰並重啓),套接字可讀,而且read返回-1,errno中含有確切錯誤碼

服務端終止後重啓

​ 因爲重啓後已經丟失了套接字鏈接信息,服務端對客戶端的報文響應RST。若此時客戶端讀,則會返回ECONNRESET

套接字api異常狀況

socket

errno 含義 可能狀況 致命 解決
EACCES 權限不足
EAFNOSUPPORT 地址族不支持 參數錯誤
EINVAL 參數錯誤 未知協議或協議族不可用
EMFILE 打開的文件過多 進程級打開的fd達上限 ulimit -n 調整或等待資源釋放
ENFILE 打開的文件過多 系統級打開的fd達上限 等待資源釋放
ENOBUFS/ENOMEM 內存不足 Buffer或文件表沒法建立 等待資源釋放

bind

errno 含義 可能狀況 致命 解決
EACCES 權限不足 申請保留端口且非root運行
EADDRINUSE 地址已被使用 綁定已使用地址或申請臨時端口時已佔滿 臨時端口已滿時重試
EBADF fd不可用 fd已關閉或參數非法
EINVAL 參數錯誤 套接字重複綁定或參數錯誤
ENOTSOCK 非套接字 fd參數錯誤

listen

errno 含義 可能狀況 致命 解決
EADDRINUSE 地址已被使用 監聽已監聽端口或未bind至肯定端口而申請臨時端口時已佔滿 臨時端口已滿時重試
EBADF fd不可用 fd已關閉或參數非法
ENOTSOCK 非套接字 fd參數錯誤
EOPNOTSUPP 操做不支持 只支持SOCK_STREAM類套接字(TCP)

accept

errno 含義 可能狀況 致命 解決
EWOULDBLOCK/EAGAIN 資源暫時不可用 (非阻塞)隊列中無完成握手的鏈接 重試或處理其餘事務
EBADF fd不可用 fd已關閉或參數非法
ECONNABORTED 鏈接已中斷 握手結束客戶端RST 重試或處理其餘事務
EFAULT 地址錯誤 addr參數沒有指向用戶可寫空間
EINTR 調用中斷 調用被信號中斷 重試或處理其餘事務
EINVAL 參數錯誤 套接字未在監聽態或addrlen/flags錯誤
EMFILE 打開的文件過多 進程級打開的fd達上限 ulimit -n 調整或等待資源釋放
ENFILE 打開的文件過多 系統級打開的fd達上限 等待資源釋放
ENOMEM/ENOBUFS 內存不足 套接字buffer內存不足而非系統內存不足 等待資源釋放

connect

errno 含義 可能狀況 致命 解決
EACCES/EPERM 權限不足/操做未容許 未設置廣播標記但鏈接廣播地址或防火牆禁止鏈接
EADDRINUSE 本地地址已被使用 重試,等待資源釋放
EADDRNOTAVAIL 地址不可用 未bind至肯定端口而申請臨時端口時已佔滿 重試,等待資源釋放
EAFNOSUPPORT 地址族錯誤
EAGAIN 資源暫時不可用 無可用本地端口或路由緩存中無記錄(?) 重試,等待資源釋放
EALREADY 鏈接正在處理 (非阻塞)上一個鏈接還未結束處理,多是對返回EINPROGRESS從新調用
EBADF fd不可用 fd已關閉或參數非法
ECONNREFUSED 鏈接拒絕 給定遠端未監聽,或見首次握手服務端RST 重試
EFAULT 地址錯誤 地址指針超出用戶空間
EINPROGRESS 操做正在進行 (非阻塞)connect沒法當即完成 使用getsockopt判斷鏈接是出錯仍是已完成
EINTR 調用中斷 調用被信號中斷 使用getsockopt判斷鏈接是出錯仍是已完成
EISCONN 套接字已鏈接 使用getsockopt判斷鏈接是出錯仍是已完成
ENETUNREACH 網絡不可達 目的不可達 重試
ENOTSOCK 非套接字 fd參數錯誤
EPROTOTYPE 套接字協議錯誤
ETIMEDOUT 鏈接超時 遠端無應答,可能由於系統繁忙 重試

另附: 對connect下幾種沒法判斷鏈接是否成功創建的處理方案的討論http://www.madore.org/~david/...

EINTR/EINPROGRESS/EALREADY表明的狀況

If connect() is interrupted by a signal that is caught while blocked waiting to establish a connection, connect() shall fail and set connect() to [ EINTR], but the connection request shall not be aborted, and the connection shall be established asynchronously.

If the connection cannot be established immediately and O_NONBLOCK is set for the file descriptor for the socket, connect() shall fail and set errno to [EINPROGRESS], but the connection request shall not be aborted, and the connection shall be established asynchronously. Subsequent calls to connect() for the same socket, before the connection is established, shall fail and set errno to [EALREADY].

When the connection has been established asynchronously, select() and poll() shall indicate that the file descriptor for the socket is ready for writing.

read(只涉及套接字)

errno 含義 可能狀況 致命 解決
EAGAIN/EWOULDBLOCK 操做將會阻塞 (非阻塞)Buffer空 重試
EBADF fd不可用 fd已關閉或參數非法
EFAULT 地址錯誤 buffer超出用戶空間
EINTR 調用中斷 調用被信號中斷 重試
EINVAL 參數錯誤 fd不可讀

write(只涉及套接字)

errno 含義 可能狀況 致命 解決
EAGAIN/EWOULDBLOCK 操做將會阻塞 (非阻塞)Buffer已滿或空閒空間不足 重試
EBADF fd不可用 fd已關閉或參數非法
EDESTADDRREQ 須要目的地址 套接字未鏈接
EFAULT 地址錯誤 buffer超出用戶空間
EINTR 調用中斷 調用被信號中斷 重試
EINVAL 參數錯誤
EPIPE 管道錯誤 向讀關閉的一端寫,見寫RST套接字
相關文章
相關標籤/搜索