select主要通过维护一个套接字队列,来完成单线程的IO复用,利用以下几个函数实现
NAME
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous
I/O multiplexing
函数声明
SYNOPSIS
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
int pselect(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, const struct timespec *timeout,
const sigset_t *sigmask);
//用来删除描述词组set中指定描述fd 的位置为0
void FD_CLR(int fd, fd_set *set);
//用来测试描述词组set中相关fd 的位是否为真
int FD_ISSET(int fd, fd_set *set);
//向描述词组set中添加描述符,设置描述词组set中相关fd的位
void FD_SET(int fd, fd_set *set);
// 用来清除描述词组set的全部位。
void FD_ZERO(fd_set *set);
nfds:监视对象文件描述符数量。
readfds: 将所有关注“是否存在待读取数据”的文件描述符注册到fd_set变量,并传递其地址值。
writefds: 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set变量,并传递其地址值。
exceptfds: 将所有关注“是否发生异常”的文件描述符注册到fd_set变量,并传递其地址值。
timeout: 调用select后,为防止陷入无限阻塞状态,传递超时信息。
在select函数中将超时参数 timeout设置为0的时候,表示不等待,检查描述符后立即返回,这种立即返回被称为轮询,即非阻塞式IO.
timeout为空表示是阻塞式IO,直到有描述符准备好才返回.或者指定一个时间段再返回.
返回0表示超时,出错返回-1,有就绪运算符返回数目
fd_set
select的描述符个数受限制可以通过修改FD_SETSIZE来改变描述符集的大小,每次修改之后需要配对内核进行重新编译,一般最大为1024
select记录描述符的数据结构是描述符集,实际上就是一个整数数组,假设32位整数,每一位代表一个描述符.这样的结构相当于一个bitmap.
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
由于描述符是按位进行操作的,直接操作描述符集较为复杂,因此通过宏定义FD_SET、FD_CLR、FD_ZERO、FD_ISSET来设置描述符集。
举例
fd_set fd_sock;//套接字描述符的集合
SOCKADDR_IN addSrv;
addSrv.sin_family=AF_INET;
addSrv.sin_port=htons(this->port);
addSrv.sin_addr.Sun.S_addr=htonl(INADDR_ANY);
sockSrv=WSASocket(AF_INET,SOCK_STREAM,0);//
if(-1==bind(sockSrv,(SOCKADDR*)addrSrv,sizeof(addrSrv)))
{
cout<<"绑定失败"<<endl;
closesocket(sockSrv);
WSACleanup();
}
if(-1==listen(sockSrv,5))
{
cout<<"监听失败"<<endl;
}
else{
cout<<"等待客户端连接"<<endl;
}
while(true)
{
// 初始化fdsock,将监听套接字加入到套接字集合
FD_ZERO(&fd_sock);
//监听套接字加入描述符集合
FD_SET(sockSrv, fd_sock);
if(select(0,&fd_sock,NULL,NULL,NULL)>0)//返回的是变化套接字的个数
{
for(int i=0;i<fd_sock.fd_count;i++)
{
if(FD_ISSET(fd_sock.fd_array[i],&fd_sock))
{
//监听套接字活跃
if(fd_sock.fd_array[i]==sockSrv)
{
newsock=accept(sockSrv,(SOCKADDR*)&clientAddr,&len);
if(newsock==INVALID_SOCKET)
{
printf("服务器端:accept函数调用失败\n");
for(int j = 0;j < m_dlg->fdsock.fd_count;j++)
{
closesocket(m_dlg->fdsock.fd_array[j]);
}
WSACleanup();
}
}
else//其他套接字 表示客户端发来数据
{
int size = recv(fdsock.fd_array[i], recv_Buff, sizeof(recv_Buff), 0);
if(size < 0)
{
printf("服务器端:接收信息失败\n");
}
else if(size == 0)
{
printf("服务器端:客户端对方已关闭\n");
}
else
{
send(fdsock.fd_array[i],send_Buf,sizeof(send_Buf),0);
break;
}
// 关闭套接字
closesocket(m_dlg->fdsock.fd_array[i]);
//清除已关闭套接字
FD_CLR(m_dlg->fdsock.fd_array[i], &(m_dlg->fdsock));
}
}//有活跃套接字
}//遍历fd_set队列
}//select活跃
}//while