套接字描述符的就緒條件

     在使用select,poll,epoll等I/O複用模型時,我們一直在說當某個套接字描述符準備好讀,或準備好寫,或者在描述符上發生一個待處理的異常條件時,會觸發相應的可讀可寫事件,下面對套接字描述的可讀可寫,以及異常條件做一個總結。

一,套接字可讀

     滿足以下幾個條件中的任何一個時,一個套接字準備好讀。
1,套接字接收緩衝區中的數據字節數大於等於套接字緩衝區低水位標記的當前大小。對此套接字進行讀操作不會阻塞且返回準備好讀入的數據大小。可以通過SO_RCVLOWAT來設置指定套接字的低水位標記。對於TCP,UDP默認爲1。

int nRecvLowAT= 32;
socklen_t len = sizeof(nRecvLowAT);
setstockopt(nSocket,SOL_SOCKET,SO_RCVLOWAT,(void*)&nRecvLowAT,len);
//注意,windows和linux的setstockopt第三個參數類型有區別
//windows下是char*,linux下是void*

2,該套接字接收到了FIN,但是沒有給對端ACK,此時讀處於半關閉狀態,對這樣的套接字進行讀操作將不會阻塞且返回0(表示讀不到任何數據)。

3,該套接字是一個監聽套接字,且已完成的連接數大於0(收到了另一端的連接請求)。對這樣的套接字再調用accept通常不會阻塞,且accept後應該將新的連接套接字再次丟入到套接字集合中進行監聽。

4,該套接字上有一個套接字錯誤待處理,對這樣的套接字進行讀操作將不會阻塞,且返回-1,同時errno設置成具體的錯誤值。同時,這些錯誤還可以通過SO_ERROR套接字選項獲取:


int error = 0;
socklen_t len = sizeof(error);
getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (void*)&error, &len);

二,套接字可寫

     滿足以下幾個條件中的任何一個時,一個套接字準備好寫。
1,該套接字發送緩衝區中的可用空間字節數大於等於套接字發送緩衝區低水位標記的當前大小值,並且該套接字已連接,或該套接字不需連接(UDP套接字)。這意味着如果我們把這樣的套接字設置爲非阻塞, 寫操作將不會阻塞且返回準備好讀入的數據大小。

對阻塞式套接字進行讀操作,只要有數據總會返回一個大於0的數(實際讀取的字節數);
對阻塞式套接字進行寫操作,將會一直阻塞到所有數據都會被內核接受爲止
(比如發送緩衝區大小爲8192字節,但是write 8193字節的數據時,write將會阻塞,
等待最後一個字節可用空間。)

可以通過SO_SNDLOWAT來設置指定套接字的低水位標記。對於TCP,UDP默認爲2048。

int nSendLowAT= 32;
socklen_t len = sizeof(nSendLowAT);
setstockopt(nSocket,SOL_SOCKET,SO_SNDLOWAT,(void*)&nSendLowAT,len);

2,該套接字的寫半部關閉。當一個套接字收到FIN後,第一次對其調用write方法時, 如果發送緩衝沒問題, 會返回正確寫入(發送)。但發送的報文會導致對端發送RST報文, 因爲對端的socket已經調用了close, 完全關閉, 既不發送, 也不接收數據. 所以,第二次調用write方法(假設在收到RST之後), 會生成SIGPIPE信號,導致進程退出,此信號一般需要屏蔽。

3,使用非阻塞connect的套接字已建立連接,或者connect失敗。這樣的套接字會觸發寫操作。

4,該套接字上有一個套接字錯誤待處理,對這樣的套接字進行讀操作將不會阻塞,且返回-1,同時errno設置成具體的錯誤值。同時,這些錯誤還可以通過SO_ERROR套接字選項獲取(同上)。

三,套接字異常

     如果一個套接字上存在帶外數據或者仍處於帶外標記,那麼它有異常條件會被觸發。同時當某個套接字上發生錯誤時,它將由select標記爲既可讀又可寫(上面描述的可讀可寫條件的第四點)。

四,代碼演示

     不得不說,開源代碼能夠開源,它們的細節考慮確實周到,此處以Teamtalk的服務端代碼爲例,它的服務端底層都是使用共用的BaseSocket和EventDispatch來實現的,先看看它調用epoll_wait的地方:
在這裏插入圖片描述
     在一個線程中,while循環調用epoll_wait等待套接字的可讀,可寫,異常被觸發。再根據EPOLL類型分別調用OnRead(),OnWrite(),OnClose()函數。我們先看可讀事件OnRead()的處理:
在這裏插入圖片描述
     它做了三件事:
1,處理新的連接,並將新連接再次丟入大哦epoll中,這個在_AcceptNewSocket()中實現。
2,判斷套接字是否異常,讀取數據是否<=0
3,套接字上接收緩衝區中的數據字節數大於等於套接字緩衝區低水位標記的當前大小時,觸發業務回調。
     而這幾點把我們上面講述的套接字可讀的幾種情況全部包含進去了,考慮非常周全。

     同樣的,我們再看可寫事件OnWrite()的處理:
在這裏插入圖片描述     它也做了三件事:
1,如果該套接字已經連接上了,且發送緩衝區中的可用空間字節數大於等於套接字發送緩衝區低水位標記的當前大小值,那麼調用業務的回調函數。
2,如果該套接字處於正在連接中,判斷該套接字是否異常,如果異常,則關閉。
3,如果該套接字處於正在連接中,且該套接字正常,那麼觸發第一次連接事件。
     這幾點也把套接字可寫的幾種情況全部考慮進去。

發佈了76 篇原創文章 · 獲贊 76 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章