阻塞式的Socket很容易理解,但是使用起来非常别扭。Windows提供了选择(Select)I/O模型,进行异步Socket通信.
Select模型
int select( _In_ int nfds,//忽略. 此参数为了与Berkeley sockets兼容. _Inout_ fd_set *readfds,//检查可读性fd_set指针. _Inout_ fd_set *writefds,//检查可写性fd_set指针. _Inout_ fd_set *exceptfds,//检查错误fd_set指针 _In_ const struct timeval *timeout//超时 );本函数用于确定一个或多个套接口的状态。对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息。用fd_set结构来表示一组等待检查的套接口。在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select()返回满足条件的套接口的数目。
下面是代码:
#include <Winsock2.h> #include <stdio.h> #include <process.h> static const int PORT = 7777; static const int BUFFER_LENGTH = 128 ; static int g_TotalConn = 0 ; static SOCKET g_ClientSocket[FD_SETSIZE] ; unsigned int __stdcall WorerkThread(void *); bool InitWSA() ; int main() { if(!InitWSA()) { return -1 ; } SOCKET sockListen = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP) ; SOCKADDR_IN addrSrv ; addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_LOOPBACK) ; addrSrv.sin_family = AF_INET ; addrSrv.sin_port = htons(PORT) ; bind (sockListen, (SOCKADDR *)&addrSrv, sizeof(SOCKADDR)) ; listen (sockListen, 3) ; _beginthreadex(NULL, 0, WorerkThread, NULL, 0, NULL) ; SOCKADDR_IN addrClient ; int len = sizeof (addrClient) ; while (true) { SOCKET client = accept(sockListen, (SOCKADDR *)&addrClient, &len) ; g_ClientSocket[g_TotalConn++] = client ; } closesocket(sockListen) ; WSACleanup () ; return 0 ; } unsigned int __stdcall WorerkThread(void *) { printf("线程begin:\n") ; int i; fd_set fdread; fd_set fdwrite; int ret; struct timeval tv = {1, 0}; char szMessage[BUFFER_LENGTH]; while (true) { FD_ZERO(&fdread); FD_ZERO(&fdwrite); for (i = 0; i < g_TotalConn; i++) { FD_SET(g_ClientSocket[i], &fdread) ; FD_SET(g_ClientSocket[i], &fdwrite) ; } ret = select(0, &fdread, &fdwrite, NULL, &tv); /* if(g_TotalConn > 0 && ret == SOCKET_ERROR) { printf("Socket:an error occurred!\n") ; } */ if (ret == 0) { printf("超时\n") ; continue ; } for (i = 0; i < g_TotalConn; i++) { if (FD_ISSET(g_ClientSocket[i], &fdread)) { ret = recv(g_ClientSocket[i], szMessage, BUFFER_LENGTH, 0); if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)) { printf("Client socket %d closed.\n", i); closesocket(g_ClientSocket[i]); g_ClientSocket[i] = 0 ; if(i != g_TotalConn -1) { g_ClientSocket[i--] = g_ClientSocket[g_TotalConn -1] ; } g_TotalConn-- ; } else { szMessage[ret] = '\0'; send(g_ClientSocket[i], szMessage, strlen(szMessage), 0); printf("发送数据给Client:%s\n", szMessage) ; } } if (FD_ISSET(g_ClientSocket[i], &fdwrite)) { //可以发送数据 } } } printf("线程end:\n") ; return 0 ; } bool InitWSA() { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD( 2, 2 ); err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return false ; } if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { WSACleanup(); return false ; } return true ; }特点: 1、程序可能阻塞在select处一段指定的时间。 2、使用轮循的方式,检测Socket是否可读或可写、效率低下。
点评:虽然实现了异步IO,但是实现方式“扭曲”,效率低下,不推荐使用,尤其是服务端。