唯快不破:TCP網絡編程--非阻塞accept和非阻塞connect

非阻塞accept
當一個已完成的連接準備好被accept的時候,select會把監聽socket標記爲可讀。因此,如果用select等待外來的連接時,應該不需要把監聽socket設置爲非阻塞模式,因爲如果select告訴我們連接已經就緒,accept就不應該被阻塞。不過這樣做的時候有一個BUG:當客戶端在跟服務器建立連接之後發送了一個RST包,這個時候accept就會阻塞,直到有下一個已完成的連接準備好被accept爲止。
struct linger的l_onoff標誌設爲1,l_linger設爲0。這個時候,如果關閉TCP連接時,會先在socket上發送一個RST包。這個時候會出現下面的問題:
A:select向服務器返回監聽socket可讀,但是服務器要在一段時間之後才能調用accept;
B:在服務器從select返回和調用accept之前,收到從客戶發送過來的RST;
C:這個已經完成的連接被從隊列中刪除,我們假設沒有其它已完成的連接存在;
D:服務器調用accept,但是由於沒有其它已完成的連接存在,因而服務器被阻塞了;
注意,服務器會被一直阻塞在accept調用上,直到另外一個客戶建立一個連接爲止;但是如果一直沒有其它客戶建立連接,那麼服務器將仍然一直被阻塞在accept調用上,不處理任何其他已就緒的socket;
解決這個問題的辦法是:
A:如果使用select來獲知何時有鏈接已就緒可以accept時,總是把監聽socket設置爲非阻塞模式,並且
B:在後面的accept調用中忽略以下錯誤:EWOULDBLOCK(源自Berkeley的實現在客戶放棄連接時出現的錯誤)、ECONNABORTED(Posix.1g的實現在客戶放棄連接時出現的錯誤)、EPROTO(SVR4的實現在客戶放棄連接時出現的錯誤)和EINTR(如果信號被捕獲).


非阻塞connect
在一個TCP套接口被設置爲非阻塞之後調用connect,connect會立即返回EINPROGRESS錯誤,表示連接操作正在進行中,但是仍未完成;同時TCP的三路握手操作繼續進行;在這之後,我們可以調用select來檢查這個鏈接是否建立成功;非阻塞connect有三種用途:
1.我們可以在三路握手的同時做一些其它的處理.connect操作要花一個往返時間完成,而且可以是在任何地方,從幾個毫秒的局域網到幾百毫秒或幾秒的廣域網.在這段時間內我們可能有一些其他的處理想要執行;
2.可以用這種技術同時建立多個連接.在Web瀏覽器中很普遍;
3.由於我們使用select來等待連接的完成,因此我們可以給select設置一個時間限制,從而縮短connect的超時時間.在大多數實現中,connect的超時時間在75秒到幾分鐘之間.有時候應用程序想要一個更短的超時時間,使用非阻塞connect就是一種方法;
非阻塞connect聽起來雖然簡單,但是仍然有一些細節問題要處理:
1.即使套接口是非阻塞的,如果連接的服務器在同一臺主機上,那麼在調用connect建立連接時,連接通常會立即建立成功.我們必須處理這種情況;
2.源自Berkeley的實現(和Posix.1g)有兩條與select和非阻塞IO相關的規則:
A:當連接建立成功時,套接口描述符變成可寫;
B:當連接出錯時,套接口描述符變成既可讀又可寫;
注意:當一個套接口出錯時,它會被select調用標記爲既可讀又可寫;

非阻塞connect有這麼多好處,但是處理非阻塞connect時會遇到很多可移植性問題;

處理非阻塞connect的步驟:
第一步:創建socket,返回套接口描述符;
第二步:調用fcntl把套接口描述符設置成非阻塞;
第三步:調用connect開始建立連接;
第四步:判斷連接是否成功建立;
A:如果connect返回0,表示連接簡稱成功(服務器可客戶端在同一臺機器上時就有可能發生這種情況);
B:調用select來等待連接建立成功完成;
如果select返回0,則表示建立連接超時;我們返回超時錯誤給用戶,同時關閉連接,以防止三路握手操作繼續進行下去;
如果select返回大於0的值,則需要檢查套接口描述符是否可讀或可寫;如果套接口描述符可讀或可寫,則我們可以通過調用getsockopt來得到套接口上待處理的錯誤(SO_ERROR),如果連接建立成功,這個錯誤值將是0,如果建立連接時遇到錯誤,則這個值是連接錯誤所對應的errno值(比如:ECONNREFUSED,ETIMEDOUT等).
"讀取套接口上的錯誤"是遇到的第一個可移植性問題;如果出現問題,getsockopt源自Berkeley的實現是返回0,等待處理的錯誤在變量errno中返回;但是Solaris會讓getsockopt返回-1,errno置爲待處理的錯誤;我們對這兩種情況都要處理;

這樣,在處理非阻塞connect時,在不同的套接口實現的平臺中存在的移植性問題,首先,有可能在調用select之前,連接就已經建立成功,而且對方的數據已經到來.在這種情況下,連接成功時套接口將既可讀又可寫.這和連接失敗時是一樣的.這個時候我們還得通過getsockopt來讀取錯誤值;這是第二個可移植性問題;
移植性問題總結:
1.對於出錯的套接口描述符,getsockopt的返回值源自Berkeley的實現是返回0,待處理的錯誤值存儲在errno中;而源自Solaris的實現是返回0,待處理的錯誤存儲在errno中;(套接口描述符出錯時調用getsockopt的返回值不可移植)
2.有可能在調用select之前,連接就已經建立成功,而且對方的數據已經到來,在這種情況下,套接口描述符是既可讀又可寫;這與套接口描述符出錯時是一樣的;(怎樣判斷連接是否建立成功的條件不可移植)

這樣的話,在我們判斷連接是否建立成功的條件不唯一時,我們可以有以下的方法來解決這個問題:
1.調用getpeername代替getsockopt.如果調用getpeername失敗,getpeername返回ENOTCONN,表示連接建立失敗,我們必須以SO_ERROR調用getsockopt得到套接口描述符上的待處理錯誤;
2.調用read,讀取長度爲0字節的數據.如果read調用失敗,則表示連接建立失敗,而且read返回的errno指明瞭連接失敗的原因.如果連接建立成功,read應該返回0;
3.再調用一次connect.它應該失敗,如果錯誤errno是EISCONN,就表示套接口已經建立,而且第一次連接是成功的;否則,連接就是失敗的;

被中斷的connect:
如果在一個阻塞式套接口上調用connect,在TCP的三路握手操作完成之前被中斷了,比如說,被捕獲的信號中斷,將會發生什麼呢?假定connect不會自動重啓,它將返回EINTR.那麼,這個時候,我們就不能再調用connect等待連接建立完成了,如果再次調用connect來等待連接建立完成的話,connect將會返回錯誤值EADDRINUSE.在這種情況下,應該做的是調用select,就像在非阻塞式connect中所做的一樣.然後,select在連接建立成功(使套接口描述符可寫)或連接建立失敗(使套接口描述符既可讀又可寫)時返回.














http://blog.csdn.net/yusiguyuan/article/details/24111021

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章