TCP非阻塞accept和非阻塞connect

2021-06-07 03:36:25 字數 3364 閱讀 5368

**:

非阻塞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在連線建立成功(使套介面描述符可寫)或連線建立失敗(使套介面描述符既可讀又可寫)時返回.

關於accept非阻塞

最近在做socket程式設計,wifi測試時發現距離遠了之後,拿近了後,熱點會再次連上,但程式裡的socket不會重連,後來發現問題的根源。如下 當乙個已完成的連線準備好被accept的時候,select會把監聽socket標記為可讀 因此,如果用select等待外來的連線時,應該不需要把監聽soc...

將ACCEPT改成非阻塞型

這個專案中寫的程式是與遠端進行socket通訊,用到accept來接受遠端的連線請求,一直以來在程式裡對accept用的都是阻塞方式,接收到乙個新的連線請求後,就建立乙個新的執行緒處理與客戶端的通訊任務。今天由於需要實現伺服器端設定客戶端心跳包週期的功能,如果每個執行緒都去查詢資料庫裡心跳包週期有沒...

學習TCP阻塞 非阻塞

不管是阻塞還是非阻塞模式,send 返回的資料長度,只是表示拷貝到協議棧緩衝區中的資料長度,並不是實際傳送的資料量或對方接收的資料量。對於recv 只是從緩衝區中獲取接收到的資料。傳送方先將資料拷貝到協議棧緩衝區,tcp會保證緩衝區中的資料傳送到接收方的緩衝區。至於資料如何可靠的到達,底層協議已經給...