int select(
int nfds, // 忽略,仅是为了与Berkeley 套接字兼容
fd_set* readfds, // 指向一个套接字集合,用来检查其可读性
fd_set* writefds, // 指向一个套接字集合,用来检查其可写性
fd_set* exceptfds, // 指向一个套接字集合,用来检查错误
const struct timeval* timeout // 指定此函数等待的最长时间,如果为NULL,则最长时间为无限大
);
函数调用成功,返回发生网络事件的所有套接字数量的总和。如果超过了时间限制,返回0,失败则返回SOCKET_ERROR
FD_ZERO(*set) 初始化set 为空集合。集合在使用前应该总是清空。
FD_CLR(s, *set) 从set 移除套接字s。
FD_ISSET(s, *set) 检查s 是不是set 的成员,如果是返回TRUE。
FD_SET(s, *set) 添加套接字到集合
select 函数返回之后,如果有下列事件发生,其对应的套接字就会被标识:
(1)readfds 集合。
数据可读。
连接已经关闭、重启或者中断。
如果listen 已经被调用,并且有一个连接未决,accept 函数将成功。
(2)writefds 集合。
数据能够发送。
如果一个非阻塞连接调用正在被处理,连接已经成功。
(3)exceptfds 集合。
如果一个非阻塞连接调用正在被处理,连接失败。
OOB 数据可读。
当 select 返回时,它通过移除没有未决I/O 操作的套接字句柄修改每个fd_set 集合。例如,想要测试套接字s 是否可读时,必须将它添加到readfds 集合,然后等待select 函数返回。当select 调用完成后再确定s 是否仍然还在readfds 集合中,如果还在,就说明s 可读了。3个参数中的任意两个都可以是NULL
添加到fd_set 结构的套接字数量是有限制的,默认情况下,最大值是FD_SETSIZE,它在winsock2.h 文件中定义为64
#include <WINSOCK2.H>
#include <STDIO.H>
#pragma comment(lib, "Ws2_32.lib")
// 1. 初始化一个套接字集合, 添加sListen到该集合
// 2. 将fdSock的一个拷贝传递给select进行检测
// 3. 通过比较fdSock与fdRead, 确定有哪些未决IO, 并进一步处理
void main()
{
unsigned short listenPort = 8000;
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2), &wsaData);
SOCKET sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(listenPort);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
bind(sListen, (sockaddr*)&sin, sizeof(sin));
listen(sListen, 5);
printf("sock is listenning ... \n");
// select 模型处理
// 1. 初始化一个套接字集合, 添加sListen到该集合
fd_set fdSock;
FD_ZERO(&fdSock);
FD_SET(sListen, &fdSock);
while (TRUE)
{
BOOL bExit = FALSE;
// 2. 将fdSock的一个拷贝传递给select进行检测
fd_set fdRead = fdSock;
timeval timeout;
timeout.tv_sec = 3;
timeout.tv_usec = 0;
int iRet = select(0, &fdRead, NULL, NULL, &timeout);
if (SOCKET_ERROR == iRet)
{
printf("SOCKET_ERROR is return form select\n");
break;
}
else if (0 == iRet)
{
printf("one time out\n");
}
else
{
// 3. 通过比较fdSock与fdRead, 确定有哪些未决IO, 并进一步处理
for (int i=0; i<(int)fdSock.fd_count; i++)
{
if ( FD_ISSET(fdSock.fd_array[i], &fdRead) )
{
if ( fdSock.fd_array[i] == sListen ) // 监听套接字可读
{
if ( fdSock.fd_count < FD_SETSIZE )
{
sockaddr_in addrRemote;
int addrLen = sizeof(addrRemote);
SOCKET sRemote = accept(sListen, (sockaddr*)&addrRemote, &addrLen);
FD_SET(sRemote, &fdSock);
printf("one client connect, current client count %d\n", fdSock.fd_count);
}
else
{
printf("too much connection\n");
}
}
else
{
char szBuff[256];
int iRecv = recv(fdSock.fd_array[i], szBuff, sizeof(szBuff)-1, 0);
if (iRecv > 0) // 可读
{
szBuff[iRecv] = '\0';
printf("[sock %d]recv data: %s\n", fdSock.fd_array[i], szBuff);
if ( strstr(szBuff, "EXIT") != NULL )
{
bExit = TRUE;
break;
}
}
else if (SOCKET_ERROR == iRecv) // 出错
{
printf("one error from recv: error code %d\n", WSAGetLastError());
closesocket(fdSock.fd_array[i]);
FD_CLR(fdSock.fd_array[i], &fdSock);
}
else // 连接关闭、重启或者中断
{
closesocket(fdSock.fd_array[i]);
FD_CLR(fdSock.fd_array[i], &fdSock);
printf("one client disconnect, sock %d, current client count %d\n", fdSock.fd_array[i], fdSock.fd_count);
}
}
}
}
if (bExit)
break;
}
}
for (int i=0; i<(int)fdSock.fd_count; i++)
{
closesocket(fdSock.fd_array[i]);
}
printf("exit server\n");
WSACleanup();
}