select模型 select(选择)模型是winsock中常见的I/O模型。之所以称其为“select模型”,是由于它的 “中心思想”是利用select函数,实现对I/O的管理!最初设计该模型时,主要面向的是某些使用 Unix操作系统的计算机,它们采用的是Berkeley套接字方案。select模型已经集成到Winsock1.1中。 1.通过调用select函数可以确定一个或多个套接字的状态,判断套接字上是否有数据,或 者能否向一个套接字写入数据。 int select (int nfds, //忽略 fd_set FAR *readfds, //等待可读性检查的套接字组的地址 fd_set FAR *writefds, //等待可写性检查的套接字组的地址 fd_set FAR *exceptfds, //等待错误检查的套接字组的地址 const struct timeval FAR *timeout); //struct timeval结构体地址,select() 最多等待的时间 //返回值 0--超时,SOCKET_ERROR--失败 //说明:此函数的作用是删除fd_set结构体中没有IO操作的套接字 /*注意:在3个套接字组中至少有一个不为NULL;在非空集合中必须包含一个套接字句柄。 如果timeout设为(0,0),select() 会立即返回,允许应用程序对select操作进行“轮询”。 如: fd_set fdread; FD_ZERO(&fdread); FD_SET(s, &fdread); select(0, &fdread, NULL, NULL, NULL); if(FD_ISSET(s, &fdread)) { //套接字可读 } */ 2.管理套接字的结构体 定义: typedef struct fd_set { u_int fd_count; /* how many are SET? */ //元素的个数 SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ } fd_set; 对struct fd_set结构体操作的宏 FD_SETSIZE 容量,指定fd_array数组大小,默认为64,也可自己修改宏 FD_ZERO(*set) 置空,使数组的元素值都为3435973836,元素个数为0. FD_SET(s, *set) 添加,向 struct fd_set结构体添加套接字s FD_ISSET(s, *set) 判断,判断s是否为 struct fd_set结构体中的一员 FD_CLR(s, *set) 删除,从 struct fd_set结构体中删除成员s 3.用Select模型获取网络事件 fd_set AllSockFd; //装有所有的套接字 FD_ZERO(&AllSockFd); AllSockFd = ClientSockFd ; FD_SET(ListenSock, &AllSockFd); fd_set ReadSockFd; //读集合 fd_set WriteSockFd; //写集合 while(1) { FD_ZERO(&ReadSockFd); FD_ZERO(&WriteSockFd); ReadSockFd = AllSockFd; WriteSockFd = AllSockFd; int nRet = select(0, &ReadSockFd, &WriteSockFd, NULL, NULL); if(SOCKET_ERROR == nRet) { continue; } //有请求事件发生 if (FD_ISSET(ListenSock, &ReadSockFd)) { //接受请求 SOCKET ClientSock; u_short Port; bool nRe = (*(Pam.pListenSock)).Accept(&ClientSock, 0, &Port); if(nRe) { FD_SET(ClientSock, Pam.pClientSockFd); //设置套接字发送缓冲区80K int nBuf = SOCKET_BUFF; int nBufLen = sizeof(nBuf); int nRe = setsockopt(ClientSock, SOL_SOCKET, SO_SNDBUF, (char*)&nBuf, nBufLen); if(SOCKET_ERROR == nRe) AfxMessageBox("setsockopt error!"); //检查缓冲区是否设置成功 nRe = getsockopt(ClientSock, SOL_SOCKET, SO_SNDBUF, (char*)&nBuf, &nBufLen); if(SOCKET_BUFF != nBuf) AfxMessageBox("检查缓冲区:setsockopt error!"); else AfxMessageBox("已连接客户端!"); } } //判断是否可读或可写 for(u_int n = 0;n < ClientSockFd.fd_count;n++) { if(FD_ISSET(ClientSockFd.fd_array[n], &ReadSockFd)) //发现可读 { //接收数据 //如果失败 删除此元素 } if(FD_ISSET(ClientSockFd.fd_array[n], &WriteSockFd)) //发现可写 { //发送缓冲区未满可以发送 //如果失败 删除此元素 } } }

//////////////////////////////////////////////////////
// select.cpp文件
//select的优点是程序能够在单个线程内同时处理多个套接字连接,但是增加
//到fd_set结构的套接字是有限制的。winsock2.h定义为64,在包含winsock2.h
//之前重新定义它是可以的,但是最大不能超过1024,并且此值太大,影响服
//务器性能,因为select返回之前会检查这些集合中的套接字,并移除没有未
//决I/O操作的套接字。
//s为套接字
//FD_ZERO(*set)初始化set为空集合
//FD_CLR(s,*set)从set移除s
//FD_ISSET(s,*set)检查s是不是set的成员,如果是返回true
//FD_SET(s,*set)增加套接字到集合
#include "../common/initsock.h"
#include <stdio.h>
CInitSock theSock; // 初始化Winsock库
int main()
{
USHORT nPort = 4567; // 此服务器监听的端口号
// 创建监听套节字
SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(nPort);
sin.sin_addr.S_un.S_addr = INADDR_ANY;
// 绑定套节字到本地机器
if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf(" Failed bind() \n");
return -1;
}
// 进入监听模式
::listen(sListen, 5);
// select模型处理过程
// 1)初始化一个套节字集合fdSocket,添加监听套节字句柄到这个集合
fd_set fdSocket; // 所有可用套节字集合
FD_ZERO(&fdSocket);
FD_SET(sListen, &fdSocket);
while(TRUE)
{
// 2)将fdSocket集合的一个拷贝fdRead传递给select函数,
// 当有事件发生时,select函数移除fdRead集合中没有未决I/O操作的套节字句柄,然后返回。
fd_set fdRead = fdSocket;
int nRet = ::select(0, &fdRead, NULL, NULL, NULL);
if(nRet > 0)
{
// 3)通过将原来fdSocket集合与select处理过的fdRead集合比较,
// 确定都有哪些套节字有未决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) // (1)监听套节字接收到新连接
{
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");
continue;
}
}
else
{
char szText[256];
int nRecv = ::recv(fdSocket.fd_array[i], szText, strlen(szText), 0);
if(nRecv > 0) // (2)可读
{
szText[nRecv] = '\0';
printf("接收到数据:%s \n", szText);
}
else // (3)连接关闭、重启或者中断
{
::closesocket(fdSocket.fd_array[i]);
FD_CLR(fdSocket.fd_array[i], &fdSocket);
}
}
}
}
}
else
{
printf(" Failed select() \n");
break;
}
}
return 0;
}