在掌握了socket相关的一些函数后,套接字编程仍是比较简单的,平常工做中碰到不少的问题就是客户端/服务器模型中,如何让服务端在同一时间高效的处理多个客户端的链接,咱们的处理办法可能会是在服务端不停的监听客户端的请求,有新的请求到达时,开辟一个新的线程去和该客户端进行后续处理,可是这样针对每个客户端都须要去开辟一个新的线程,效率一定底下。编程
其实,socket编程提供了不少的模型来处理这种情形,咱们只要按照模型去实现咱们的代码就能够解决这个问题。主要有select模型和重叠I/o模型,以及完成端口模型。此次,咱们主要介绍下select模型,该模型又分为普通select模型,wsaasyncselect模型,wsaeventselect模型。咱们将经过样例代码的方式逐一介绍。windows
1、select模型数组
使用该模型时,在服务端咱们能够开辟两个线程,一个线程用来监听客户端的链接服务器
请求,另外一个用来处理客户端的请求。主要用到的函数为select函数。如:网络
全局变量:
fd_set g_fdClientSock;
线程1处理函数:socket
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处理函数:async
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:后续处理 } } } } } if ( recvBuffer != NULL ) {
free( recvBuffer );
}
该模型有个最大的缺点就是,它须要一个死循环不停的去遍历全部的客户端套接字集合,询问是否有数据到来,这样,若是链接的客户端不少,势必会影响处理客户端请求的效率,但它的优势就是解决了每个客户端都去开辟新的线程与其通讯的问题。若是有一个模型,能够不用去轮询客户端套接字集合,而是等待系统通知,当有客户端数据到来时,系统自动的通知咱们的程序,这就解决了select模型带来的问题了。函数
2、WsaAsyncSelect模型spa
WsaAsyncSelect模型就是这样一个解决了普通select模型问题的socket编程模型。它是在有客户端数据到来时,系统发送消息给咱们的程序,咱们的程序只要定义好消息的处理方法就能够了,用到的函数只要是WSAAsyncSelect,如:线程
首先,咱们定义一个Windows消息,告诉系统,当有客户端数据到来时,发送该消息给咱们。
#define UM_SOCK_ASYNCRECVMSG WM_USER + 1
在咱们的处理函数中能够以下监听客户端的链接:
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为接收系统发送的消息的窗口句柄 WSAAsyncSelect( clientSock, hWnd, UM_SOCK_ASYNCRECVMSG, FD_READ | FD_CLOSE ); clientNum++; }
接下来,咱们须要在咱们的窗口添加对UM_SOCK_ASYNCRECVMSG消息的处理函数,在该函数中真正接收客户端发送过来的数据,在这个消息处理函数中的wparam参数表示的是客户端套接字,lparam参数表示的是发生的网络事件如:
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 disconnect szRecvMsg.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模型是很是简单的模型,它解决了普通select模型的问题,可是它最大的缺点就是它只能用在windows程序上,由于它须要一个接收系统消息的窗口句柄,那么有没有一个模型既能够解决select模型的问题,又不限定只能是windows程序才能用呢?下面咱们来看看WsaEventSelect模型。
3、WsaEventSelect模型
WsaEventSelect模型是一个不用主动去轮询全部客户端套接字是否有数据到来的模型,它也是在客户端有数据到来时,系统发送通知给咱们的程序,可是,它不是发送消息,而是经过事件的方式来通知咱们的程序,这就解决了WsaAsyncSelect模型只能用在windows程序的问题。
该模型的实现,咱们也能够开辟两个线程来进行处理,一个用来接收客户端的链接请求,一个用来与客户端进行通讯,用到的主要函数有WSAEventSelect,WSAWaitForMultipleEvents,WSAEnumNetworkEvents实现方式以下:
首先定义三个全局数组
SOCKET g_SockArray[MAX_NUM_SOCKET];//存放客户端套接字 WSAEVENT g_EventArray[MAX_NUM_SOCKET];//存放该客户端有数据到来时,触发的事件 UINT32 g_totalEvent = 0;//记录客户端的链接数
线程1处理函数以下:
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的处理函数以下:
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 error closesocket( clientSock ); } else { memset( recvBuffer, 0, sizeof(char) * 1024 ); nRet = recv( clientSock, recvBuffer, 1024, 0); if ( nRet == SOCKET_ERROR ) { closesocket( clientSock ); } else { //todo:对接收到的客户端数据进行处理 } } } else if( wsaNetWorkEvent.lNetworkEvents & FD_CLOSE ) { if ( wsaNetWorkEvent.iErrorCode[FD_CLOSE_BIT] != 0 ) { //occur error closesocket( clientSock ); } else { closesocket( clientSock ); } } } } if ( recvBuffer != NULL ) { free( recvBuffer ); }
该模型经过一个死循环里面调用WSAWaitForMultipleEvents函数来等待客户端套接字对应的Event的到来,一旦事件通知到达,就经过该套接字去接收数据。虽然WsaEventSelect模型的实现较前两种方法复杂,但它在效率和兼容性方面是最好的。
以上三种模型虽然在效率方面有了很多的提高,但它们都存在一个问题,就是都预设了只能接收64个客户端链接,虽然咱们在实现时能够不受这个限制,可是那样,它们所带来的效率提高又将打折扣,那又有没有什么模型能够解决这个问题呢?咱们的下一篇重叠I/0模型将解决这个问题