当套接字创建时,默认情况下是工作在阻塞模式。在阻塞模式下,执行i/o的winsock调用(如send()和recv())一直到操作完成时才返回。比如调用recv()函数,如果对应的缓冲区没有数据到来。调用者将会一直等待下去,直到有数据到达为止。
如果有多个套接字连接时。为了避免出现一对套接字长时间占用,而其他套接字长时间得不到响应。就必须采用其他方式。可以采用多线程方式。即一个线程对应一个连接。通过server的CPU时间片轮转从而是每个套接字得到响应。除此以外,可以采用套接字i/o的异步方式在一个或者多个套接字上管理I/O。这样的I/O模型有6种。阻塞(blocking)模型、选择(select)模型、WSAAsyncSelect模型、WSAEventSelect模型、重叠(overlapped)模型、和完成端口(completion port)模型。
1>阻塞(blocking)模型
对于以下函数调用:
int iResult = recv(s, buffer,1024);
这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到buffer里后recv函数才会返 回,不然就会一直阻塞在那里。
在单线程的程序里出现这种情况会导致主线程(单线程程序里只有一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永 远没数据发送过来,那么程序就会被永远锁死。这个问题可以用多线程解决。
2>select模型
一般如果如果对某个套接字进行收取,如果没有数据,对阻塞模式而言,就会阻塞到有数据为止。
对于select模式,就先查询缓冲区是否有数据,如果有再收,这样就不会阻塞了。select就是这个原理,传入一个套接字集合作为参数,select函数检查套接字的缓冲区,返回满足条件的套接字集合,然后你对每一个满足条件的套接字调用收取函数,就能无阻塞的收取数据了。
比如下面代码:
.................
//初始化一个套接字集合fdSocket,添加套接字句柄到这个集合
fd_set fdSocket; //所有可用套接字集合
FD_ZERO(&fdSocket);
FD_SET(sListen,&fdSocket);
while (1)
{
//将fdSocket的一个拷贝fdRead传递给select函数
//当有事件发生时,select函数移除fdRead集合中没有未决i/o操作的套接字句柄,并返回
fd_set fdRead = fdSocket;
int nRet = ::select(0, &fdRead, NULL, NULL, NULL);
if (nRet > 0)
{
//确定又哪些套接字有未决i/o,并进一步处理这些i/o
for (int i = 0; i < (int)fdSocket.fd_count; ++i)
{
if (FD_ISSET(fdSocket.fd_array[i], &fdRead))
{
if (fdSocket.fd_array[i] == sListen) //监听的套接字收到连接
{
if (fdSocket.fd_count < FD_SETSIZE)
{
sockaddr_in addrRemote;
int nAddrLen = sizeof(addrRemote);
SOCKET sNew = ::accept(sListen, (SOCKADDR *)&addrRemote, &nAddrLen);
FD_SET(sNew, &fdSocket);
printf("接收到连接(%s)/n", ::inet_ntoa(addrRemote.sin_addr));
}
else
{
printf("Too much connections!/n");
return 0;
}
}
else
{//其他套接字有数据(处于可读状态),于是服务器端接收数据
char szText[256];
int nRecv = ::recv(fdSocket.fd_array[i], szText, strlen(szText), 0);
if (nRecv > 0)
{
szText[nRecv] = '/0';
printf("接收到数据:%s/n", szText);
}
else
{
::closesocket(fdSocket.fd_array[i]);
FD_CLR(fdSocket.fd_array[i], &fdSocket);
}
}
}
}
}
else
{
printf("Failed select().../n");
break;
}
}
..........