阻塞式的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
#include
#include
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,但是实现方式“扭曲”,效率低下,不推荐使用,尤其是服务端。