windows下多路複用IO(select,WSAAsyncSelect,WSAEventSelect)

Winsock提供的編程接口中socket默認是阻塞的,比如send,recv,connect,可以通過ioctlsocket進行設置非阻塞,server端要管理多個連接可能不是一件容易的事,windows下提供了不少模型可供使用,比如標題的三個,然後完成端口,libevent等庫,此文僅寫標題的三個,另外兩個單獨寫。先看MSDN的介紹
MSDN:
The WSAEventSelect function specifies an event object to be associated with the specified set of FD_XXX network events.
The return value is zero if the application's specification of the network events and the associated event object was successful.

The WSAAsyncSelect function requests Windows message-based notification of network events for a socket.
If the WSAAsyncSelect function succeeds, the return value is zero provided the application's declaration of interest in the network event set was successful. 

The select function determines the status of one or more sockets, waiting if necessary, to perform synchronous I/O.
The select function returns the total number of socket handles that are ready and contained in the fd_set structures, zero if the time limit expired, or SOCKET_ERROR if an error occurred

我個人理解是select需要指定一個集合,把所有需要監聽的socket添加到這個集合中,再輪詢集合,select函數返回後刪除沒有自己所關心事件的socket,然後對有用的socket進行處理。所以缺點就是如果集合內的socket數過多,則效率會直線下降。WSAAsyncSelect是最簡單的一個,但需要創建一個窗口,對於每個socket調用WSAAsyncSelect後即爲這個socket綁定了一個消息,當有關心的事件發生就會給這個窗口發送消息,然後WPARAM爲消息的句柄,LPARAM通過WSAGETSELECTEVENT與 WSAGETSELECTERROR兩個宏定義可以獲取到事件以及錯誤,然後就像處理一般消息一樣處理socket的網絡消息。WSAEventSelect與WSAAsyncSelect類似,但是它不需要創建窗口,它對事件的綁定是在一個內核對象上,然後使用一個輔助函數來得到內核對象上發生的網絡事件,然後就跟WSAAsyncSelect處理一樣了。WSAAsyncSelect與WSAEventSelect都會自動將socket設置爲非阻塞,select不會自動設置。分別看他們的工作流程

分別記錄下每個模型中的注意點:
select:
1.當使用int nFds = select(0, &readSocketFd, &writeSocketFd, NULL, NULL); 當正確調用時nFds爲當前有多少個socket上有網絡事件,加入一個socket上有兩個網絡事件,返回值爲1,不是2
2.select調用後比如有可讀事件,此時readSocketFd中只會保留那個有可讀事件的socket,其他項都會被刪除。
3.select本身不會把socket設爲非阻塞模式,所以還是有可能發生阻塞
WSAAsyncSelect:
1.每次未讀完緩衝區的recv()調用,都會重新觸發一個FD_READ消息,所以如果需要循環讀取的話則需要先關閉對FD_READ的監聽
2.FD_WRITE的觸發條件(這個應該在其他模型應該也是適用的)

  • 套接字剛建立連接時,表明準備就緒可以立即發送數據。
  • 一次失敗的send()調用後緩衝區再次可用時。如果系統緩衝區已經被填滿,那麼此時調用send()發送數據,將返回SOCKET_ERROR,使用WSAGetLastError()會得到錯誤碼WSAEWOULDBLOCK表明被阻塞。這種情況下當緩衝區重新整理出可用空間後,會嚮應用程序發送FD_WRITE消息

     所以說如果需要發送消息,直接調用send()發送即可。如果該次調用返回值爲SOCKET_ERROR且WSAGetLastError()得到錯誤碼WSAEWOULDBLOCK, 這意味着緩衝區已滿暫時無法發送,此刻需要將待發數據保存起來,等到系統發出FD_WRITE消息後嘗試重新發送。  

WSAEventSelect
1.通過 WSACreateEvent();創建的對象默認爲要手動重置爲非信號態

下面是記錄下一些socket編程時的一些經驗:

  1. 對於非阻塞socket,當發生WSAEWOULDBLOCK之後此時的操作無效,比如發送數據,其實沒有發送出去
  2. 對於send函數,有可能想要發送的長度爲10k,但實際只發了7k,此時返回值爲7k,那剩下的3k還需要自己重新發送,所以可以自己封裝一個函數
    bool CClient::SendData(char *data, int len, bool isFile)
    {
    	int totalLen = len;
    	int sendLen = 0;
    	int haveSendLen = 0;
    	while (haveSendLen != len)
    	{
    		//send返回值大於0,則爲發送的長度,=0則爲關閉了sokcet, 小於0則爲異常
    		sendLen = send(m_socket, data + haveSendLen, totalLen, 0);
    		if (sendLen>0)
    		{
    			haveSendLen += sendLen;
    			//此處用於客戶端接收不全的情況,比如發送10k,但只接收了3k,則sendLen返回3k
    			totalLen -= sendLen;
    		}
    		else if (sendLen == 0)
    		{
    			//關閉了Socket
    			::closesocket(m_socket);
    		}
    		else
    		{
    			if (WSAGetLastError() == WSAEWOULDBLOCK)
    			{
    				sendLen = 0;
    			}
    			else
    			{
    				AddLog("SendData error");
    				return false;
    			}
    		}
    
    	}
    	return true;
    }
    

     

  3. 緩衝區默認大小爲8k,最大可設置64k
  4. 對於文件的分包發送(假設服務端發送給客戶端文件),可先發送一個數據包頭,裏面包含了文件大小等其他文件信息,然後等待客戶端返回自己已經收到了這個數據包,然後正式開始發送文件時肯定需要把文件分成很多個小份來發送,可每次發送這個小份前先發送一個類似的數據包給客戶端,表明此次的包的大小,客戶端收到後確認一個應答包,服務端收到後開始正式發送第一個文件包,客戶端不斷接收,發現與數據包中給出的長度相同,則發送確認信息給服務端,然後服務端再發送下一個數據包頭,表明此次文件大小,客戶端再確認收到數據包,等待接收。。。。重複以上動作,直到全部發送完成。
  5. 如果在發送數據的過程中(send()沒有完成,還有數據沒發送)而調用了closesocket(),以前我們一般採取的措施是"從容關閉"shutdown(s,SD_BOTH),但是數據是肯定丟失了,如何設置讓程序滿足具體應用的要求(即讓沒發完的數據發送出去後在關閉socket)?
    struct linger {
        u_short    l_onoff;
        u_short    l_linger;
    };
    linger m_sLinger;
    m_sLinger.l_onoff=1;//(在closesocket()調用,但是還有數據沒發送完畢的時候容許逗留)
    // 如果m_sLinger.l_onoff=0;則功能和2.)作用相同;
    m_sLinger.l_linger=5;//(容許逗留的時間爲5秒)
    setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger))
     

上面的一些實例代碼:https://download.csdn.net/download/hlw0522/10791026

其中WSAAsyncSelect使用了MFC窗口程序,客戶端用的select,實現的文件傳輸

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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