socket编程的select模型
???? 其實(shí),socket編程提供了很多的模型來(lái)處理這種情形,我們只要按照模型去實(shí)現(xiàn)我們的代碼就可以解決這個(gè)問(wèn)題。主要有select模型和重疊I/o模型,以及完成端口模型。這次,我們主要介紹下select模型,該模型又分為普通select模型,wsaasyncselect模型,wsaeventselect模型。我們將通過(guò)樣例代碼的方式逐一介紹。
一、select模型
使用該模型時(shí),在服務(wù)端我們可以開辟兩個(gè)線程,一個(gè)線程用來(lái)監(jiān)聽客戶端的連接
請(qǐng)求,另一個(gè)用來(lái)處理客戶端的請(qǐng)求。主要用到的函數(shù)為select函數(shù)。如:
全局變量:fd_set g_fdClientSock;線程1處理函數(shù):
SOCKET listenSock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(7788);sin.sin_addr.S_un.S_addr = INADDR_ANY;int nRet = bind( listenSock, (sockaddr*)&sin, (int)(sizeof(sin)));if ( nRet == SOCKET_ERROR ){DWORD errCode = GetLastError();return;}listen( listenSock, 5);int clientNum = 0;sockaddr_in clientAddr;int nameLen = sizeof( clientAddr );while( clientNum < FD_SETSIZE ){SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );FD_SET( clientSock, &g_fdClientSock);clientNum++;}線程2處理函數(shù):
fd_set fdRead;FD_ZERO( &fdRead );int nRet = 0;char* recvBuffer =(char*)malloc( sizeof(char) * 1024 );if ( recvBuffer == NULL ){return;}memset( recvBuffer, 0, sizeof(char) * 1024 ); while ( true ){fdRead = g_fdClientSock;nRet = select( 0, &fdRead, NULL, NULL, NULL );if ( nRet != SOCKET_ERROR ){for ( int i = 0; i < g_fdClientSock.fd_count; i++ ){if ( FD_ISSET(g_fdClientSock.fd_array[i],&fdRead) ){memset( recvBuffer, 0, sizeof(char) * 1024 );nRet = recv( g_fdClientSock.fd_array[i], recvBuffer, 1024, 0);if ( nRet == SOCKET_ERROR ){closesocket( g_fdClientSock.fd_array[i] );FD_CLR( g_fdClientSock.fd_array[i], &g_fdClientSock );}else{//todo:后續(xù)處理 }}}}} if ( recvBuffer != NULL ){free( recvBuffer );}該模型有個(gè)最大的缺點(diǎn)就是,它需要一個(gè)死循環(huán)不停的去遍歷所有的客戶端套接字集合,詢問(wèn)是否有數(shù)據(jù)到來(lái),這樣,如果連接的客戶端很多,勢(shì)必會(huì)影響處理客戶端請(qǐng)求的效率,但它的優(yōu)點(diǎn)就是解決了每一個(gè)客戶端都去開辟新的線程與其通信的問(wèn)題。如果有一個(gè)模型,可以不用去輪詢客戶端套接字集合,而是等待系統(tǒng)通知,當(dāng)有客戶端數(shù)據(jù)到來(lái)時(shí),系統(tǒng)自動(dòng)的通知我們的程序,這就解決了select模型帶來(lái)的問(wèn)題了。
二、WsaAsyncSelect模型
WsaAsyncSelect模型就是這樣一個(gè)解決了普通select模型問(wèn)題的socket編程模型。它是在有客戶端數(shù)據(jù)到來(lái)時(shí),系統(tǒng)發(fā)送消息給我們的程序,我們的程序只要定義好消息的處理方法就可以了,用到的函數(shù)只要是WSAAsyncSelect,如:
首先,我們定義一個(gè)Windows消息,告訴系統(tǒng),當(dāng)有客戶端數(shù)據(jù)到來(lái)時(shí),發(fā)送該消息給我們。
#define UM_SOCK_ASYNCRECVMSG WM_USER + 1在我們的處理函數(shù)中可以如下監(jiān)聽客戶端的連接:
SOCKET listenSock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(7788);sin.sin_addr.S_un.S_addr = INADDR_ANY;int nRet = bind( listenSock, (sockaddr*)&sin, (int)(sizeof(sin)));if ( nRet == SOCKET_ERROR ){DWORD errCode = GetLastError(); return;}listen( listenSock, 5); int clientNum = 0;sockaddr_in clientAddr;int nameLen = sizeof( clientAddr );while( clientNum < FD_SETSIZE ){SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );//hWnd為接收系統(tǒng)發(fā)送的消息的窗口句柄 WSAAsyncSelect( clientSock, hWnd, UM_SOCK_ASYNCRECVMSG, FD_READ | FD_CLOSE );clientNum++;}?
接下來(lái),我們需要在我們的窗口添加對(duì)UM_SOCK_ASYNCRECVMSG消息的處理函數(shù),在該函數(shù)中真正接收客戶端發(fā)送過(guò)來(lái)的數(shù)據(jù),在這個(gè)消息處理函數(shù)中的wparam參數(shù)表示的是客戶端套接字,lparam參數(shù)表示的是發(fā)生的網(wǎng)絡(luò)事件如:
SOCKET clientSock = (SOCKET)wParam; if ( WSAGETSELECTERROR( lParam ) ){closesocket( clientSock );return;}switch ( WSAGETSELECTEVENT( lParam ) ){case FD_READ:{char recvBuffer[1024] = {'\0'};int nRet = recv( clientSock, recvBuffer, 1024, 0 );if ( nRet > 0 ){szRecvMsg.AppendFormat(_T("Client %d Say:%s\r\n"), clientSock, recvBuffer );}else{//client disconnectszRecvMsg.AppendFormat(_T("Client %d Disconnect!\r\n"), clientSock );}} break;case FD_CLOSE:{closesocket( clientSock );szRecvMsg.AppendFormat(_T("Client %d Disconnect!\r\n"), clientSock );}break;}??? 可以看到WsaAsyncSelect模型是非常簡(jiǎn)單的模型,它解決了普通select模型的問(wèn)題,但是它最大的缺點(diǎn)就是它只能用在windows程序上,因?yàn)樗枰粋€(gè)接收系統(tǒng)消息的窗口句柄,那么有沒(méi)有一個(gè)模型既可以解決select模型的問(wèn)題,又不限定只能是windows程序才能用呢?下面我們來(lái)看看WsaEventSelect模型。
三、WsaEventSelect模型
WsaEventSelect模型是一個(gè)不用主動(dòng)去輪詢所有客戶端套接字是否有數(shù)據(jù)到來(lái)的模型,它也是在客戶端有數(shù)據(jù)到來(lái)時(shí),系統(tǒng)發(fā)送通知給我們的程序,但是,它不是發(fā)送消息,而是通過(guò)事件的方式來(lái)通知我們的程序,這就解決了WsaAsyncSelect模型只能用在windows程序的問(wèn)題。
該模型的實(shí)現(xiàn),我們也可以開辟兩個(gè)線程來(lái)進(jìn)行處理,一個(gè)用來(lái)接收客戶端的連接請(qǐng)求,一個(gè)用來(lái)與客戶端進(jìn)行通信,用到的主要函數(shù)有WSAEventSelect,WSAWaitForMultipleEvents,WSAEnumNetworkEvents實(shí)現(xiàn)方式如下:
首先定義三個(gè)全局?jǐn)?shù)組
SOCKET g_SockArray[MAX_NUM_SOCKET];//存放客戶端套接字 WSAEVENT g_EventArray[MAX_NUM_SOCKET];//存放該客戶端有數(shù)據(jù)到來(lái)時(shí),觸發(fā)的事件 UINT32 g_totalEvent = 0;//記錄客戶端的連接數(shù)線程1處理函數(shù)如下:
SOCKET listenSock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(7788);sin.sin_addr.S_un.S_addr = INADDR_ANY;int nRet = bind( listenSock, (sockaddr*)&sin, (int)(sizeof(sin)));if ( nRet == SOCKET_ERROR ){DWORD errCode = GetLastError();return;}listen( listenSock, 5);sockaddr_in clientAddr;int nameLen = sizeof( clientAddr );while( g_totalEvent < MAX_NUM_SOCKET ){SOCKET clientSock = accept( listenSock, (sockaddr*)&clientAddr, &nameLen );if ( clientSock == INVALID_SOCKET ){continue;}g_SockArray[g_totalEvent] = clientSock;if( (g_EventArray[g_totalEvent] = WSACreateEvent()) == WSA_INVALID_EVENT ){continue;}WSAEventSelect( clientSock, g_EventArray[g_totalEvent],FD_READ | FD_CLOSE );g_totalEvent++;}??? 線程2的處理函數(shù)如下:
int nIndex = 0; char* recvBuffer =(char*)malloc( sizeof(char) * 1024 );if ( recvBuffer == NULL ){return;}memset( recvBuffer, 0, sizeof(char) * 1024 ); while( true ){nIndex = WSAWaitForMultipleEvents( g_totalEvent, g_EventArray, FALSE, WSA_INFINITE,FALSE );if ( nIndex == WSA_WAIT_FAILED ){continue;}else{ WSAResetEvent( g_EventArray[ nIndex - WSA_WAIT_EVENT_0]);SOCKET clientSock = g_SockArray[ nIndex - WSA_WAIT_EVENT_0 ];WSANETWORKEVENTS wsaNetWorkEvent;int nRet = WSAEnumNetworkEvents( clientSock, g_EventArray[nIndex - WSA_WAIT_EVENT_0], &wsaNetWorkEvent );if ( SOCKET_ERROR == nRet ){continue;}else if ( wsaNetWorkEvent.lNetworkEvents & FD_READ ){if ( wsaNetWorkEvent.iErrorCode[FD_READ_BIT] != 0 ){//occur errorclosesocket( clientSock );}else {memset( recvBuffer, 0, sizeof(char) * 1024 );nRet = recv( clientSock, recvBuffer, 1024, 0);if ( nRet == SOCKET_ERROR ){closesocket( clientSock );}else{//todo:對(duì)接收到的客戶端數(shù)據(jù)進(jìn)行處理}}}else if( wsaNetWorkEvent.lNetworkEvents & FD_CLOSE ){if ( wsaNetWorkEvent.iErrorCode[FD_CLOSE_BIT] != 0 ){//occur errorclosesocket( clientSock );}else{closesocket( clientSock );} }}}if ( recvBuffer != NULL ){free( recvBuffer );}?
???? 該模型通過(guò)一個(gè)死循環(huán)里面調(diào)用WSAWaitForMultipleEvents函數(shù)來(lái)等待客戶端套接字對(duì)應(yīng)的Event的到來(lái),一旦事件通知到達(dá),就通過(guò)該套接字去接收數(shù)據(jù)。雖然WsaEventSelect模型的實(shí)現(xiàn)較前兩種方法復(fù)雜,但它在效率和兼容性方面是最好的。
??? 以上三種模型雖然在效率方面有了不少的提升,但它們都存在一個(gè)問(wèn)題,就是都預(yù)設(shè)了只能接收64個(gè)客戶端連接,雖然我們?cè)趯?shí)現(xiàn)時(shí)可以不受這個(gè)限制,但是那樣,它們所帶來(lái)的效率提升又將打折扣,那又有沒(méi)有什么模型可以解決這個(gè)問(wèn)題呢?我們的下一篇重疊I/0模型將解決這個(gè)問(wèn)題
總結(jié)
以上是生活随笔為你收集整理的socket编程的select模型的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: windows套接字IOCP模型
- 下一篇: 生产者/消费者模式