非阻塞 connect: html
在 TCP socket 被設置爲非阻塞的狀況下調用 connect ,若沒有當即返回成功,則會返回 -1 以及 errno =
EINPROGRESS 的
錯誤,其表示鏈接操做正在進行中,可是還沒有完成,與此同時 TCP 三次握手操做會同時進行。在這以後,咱們能夠經過調用 select 來檢查這個連接是否創建成功。
非阻塞 connect 的三種用途:
- 能夠在 TCP 三次握手的同時作一些其它的處理。connect 操做須要一個往返時間才能完成,從幾個毫秒(局域網)到幾百毫秒或幾秒(廣域網)。在這段時間內咱們可能有一些其餘的處理想要同時執行;
- 能夠用這種技術同時創建多個鏈接。在 Web 瀏覽器中很廣泛;
- 因爲咱們使用 select 來等待鏈接的完成,所以咱們能夠給 select 設置一個時間限制,從而縮短 connect 的超時時間。在大多數實現中,connect 的超時時間在 75 秒到幾分鐘 之間(linux 內核中對 connect 的超時限制是 75 秒)。有時候應用程序想要一個更短的超時時間,使用非阻塞 connect 就是一種方法。
非阻塞 connect 聽起來雖然簡單,可是仍然有一些細節問題要處理:
1.即便套接字是非阻塞的,若是鏈接的服務器在同一臺主機上,那麼在調用 connect 創建鏈接時,鏈接一般會當即創建成功。咱們必須處理這種狀況;
2.源自 Berkeley 的實現有兩條與 select 和非阻塞 I/O 相關的規則:
A) 當鏈接創建成功時,套接口描述符變成
可寫
(鏈接創建時,寫緩衝區空閒,因此可寫)
;
B) 當鏈接創建出錯時,套接口描述符變成
既可讀又可寫
(因爲有未決的錯誤,從而可讀又可寫)
;
注意:當一個套接口出錯時,它會被 select 調用標記爲既可讀又可寫。
非阻塞 connect 有這麼多好處,可是處理非阻塞 connect 時會遇到不少【可移植性問題】。
處理非阻塞 connect 的步驟:
第一步,建立 socket,返回套接字描述符;
第二步,調用 fcntl 或 ioctlsocket 把套接口描述符設置成非阻塞;
第三步,調用 connect 開始創建鏈接;
第四步,判斷鏈接是否成功創建:
A) 若是 connect 返回 0 ,表示鏈接成功(服務器和客戶端在同一臺機器上時就有可能發生這種狀況);
B) 調用 select 來斷定鏈接創建的是否成功;
若是 select 返回 0 ,則表示在 select 的超時時間內未能成功創建鏈接;咱們須要返回超時錯誤給用戶,同時關閉鏈接,以防止 TCP 三次握手繼續進行下去;
若是 select 返回大於 0 的值,則說明檢測到可讀或可寫或異常的套接字描述符存在;此時咱們能夠經過調用 getsockopt 來檢測集合中的套接口上是否存在待處理的錯誤,若是鏈接創建是成功的,則經過 getsockopt(sockfd,SOL_SOCKET,SO_ERROR,(char *)&error,&len) 獲取的 error 值將是 0 ,若是創建鏈接時遇到錯誤,則 error 的值是鏈接錯誤所對應的 errno 值,好比 ECONNREFUSED,ETIMEDOUT 等。
=============
「讀取套接口上的錯誤」是遇到的【第一個可移植性問題】:若是出現問題,getsockopt 源自 Berkeley 的實現是返回 0 ,等待處理的錯誤在變量 errno 中返回;可是 Solaris 會讓 getsockopt 返回 -1 ,errno 置爲待處理的錯誤。咱們對這兩種狀況都要處理。
這樣,在處理非阻塞 connect 時,在不一樣的套接口實現的平臺中存在的移植性問題。首先,有可能在調用 select 以前,鏈接就已經創建成功,並且對方的數據已經到來。在這種狀況下,鏈接成功時套接口將既可讀又可寫,這和鏈接失敗時是同樣的。這個時候咱們還得經過 getsockopt 來讀取錯誤值。這是【第二個可移植性問題】。
=============
移植性問題總結
:
- 對於出錯的套接口描述符,getsockopt 的返回值源自 Berkeley 的實現是返回 0 ,待處理的錯誤值存儲在 errno 中;而源自 Solaris 的實現是返回 -1 ,待處理的錯誤存儲在 errno 中。(套接口描述符出錯時調用 getsockopt 的返回值不可移植)
- 有可能在調用 select 以前,鏈接就已經創建成功,並且對方的數據已經到來,在這種狀況下,套接口描述符是既可讀又可寫,這與套接口描述符出錯時是同樣的。(怎樣判斷鏈接是否創建成功的條件不可移植)
這樣的話,在咱們判斷鏈接是否創建成功的條件不惟一時,咱們能夠有如下的方法來解決這個問題:
- 調用獲取對端 socket 地址的 getpeername 代替 getsockopt 。若是調用 getpeername 失敗,getpeername 返回 ENOTCONN ,表示鏈接創建失敗,以後咱們必須再以 SO_ERROR 調用 getsockopt 獲得套接口描述符上的待處理錯誤;
- 調用 read ,讀取長度爲 0 字節的數據。若是鏈接創建失敗,則 read 會返回 -1 ,且相應的 errno 指明瞭鏈接失敗的緣由;若是鏈接創建成功,read 應該返回 0 。
- 再調用一次 connect 。它應該失敗,若是錯誤 errno 是 EISCONN ,就表示套接口已經創建,並且第一次鏈接是成功的;不然,鏈接就是失敗的。
被中斷的 connect
:
若是在一個阻塞式套接口上調用 connect ,在 TCP 的三次握手操做完成以前被中斷了,好比說被捕獲的信號中斷,將會發生什麼呢?假定 connect 不會自動重啓,它將返回 EINTR 。那麼這個時候,咱們就不能再調用 connect 等待鏈接創建完成了,若是再次調用 connect 來等待鏈接創建完成的話,connect 將會返回錯誤值 EADDRINUSE 。在這種狀況下,應該作的是調用 select ,就像在非阻塞式 connect 中所作的同樣。而後 select 在鏈接創建成功(使套接口描述符可寫)或鏈接創建失敗(使套接口描述符既可讀又可寫)時返回。
===================
非阻塞socket調用connect, epoll和select檢查鏈接狀況示例 linux