select函数
select函数在之前高级IO中有提过。
该函数允许进程指示内核等待多个时间中的任何一个发生,并只在有一个或多个时间发生或经历一段指定的时间后才唤醒它,也就是说我们调用select告知内核对那些描述符感兴趣以及等待多长时间。
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfd, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict tvptr);
具体的函数参数就不赘述了
服务器示例:
#include<iostream>
#include<list>
using namespace std;
int create_server(const char* ip, unsigned short port, int backlog)
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd < 0)
{
perror("socket");
return fd;
}
struct sockaddr_in addr;
addr.sin_port = htons(port);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip);
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return -2;
}
listen(fd, backlog);
return fd;
}
int doAccept(int fd, struct sockaddr* addr,socklen_t* addrlen)
{
while(1)
{
int newfd = accept(fd, addr, addrlen);
if(newfd <= 0 && errno == EINTR)
continue;
return newfd;
}
}
int main()
{
int server = create_server("0.0.0.0", 9988, 250);
int maxfd = server;
// 保存所有的客户连接socket
list<int> clients;
while(1)
{
fd_set set;
struct timeval tv;
maxfd = server;
// 初始化set,把server放入监听的文件描述符集合
FD_ZERO(&set);
FD_SET(server, &set);
// 遍历数组找到最大文件描述符
for(list<int>::iterator it = clients.begin();it != clients.end();++it)
{
FD_SET(*it, &set);
if(*it > maxfd)
maxfd = *it;
}
tv.tv_sec = 2;
tv.tv_usec = 0;
// 设置监听
int ret = select(maxfd + 1, &set, NULL, NULL, &tv);
if(ret > 0)
{
// 判断客户端是否在监听列表里
if(FD_ISSET(server, &set))
{
int newfd = doAccept(server, NULL, NULL);
clients.push_back(newfd);
}
// 遍历数组
for(list<int>::iterator it = clients.begin();it!= clients.end();)
{
int newfd = *it;
// 判断哪个描述符有事件发生
if(FD_ISSET(newfd, &set))
{
char buf[2048];
ret =read(newfd, buf, sizeof(buf));
if(ret > 0)
{
printf("%s, fd=%d\n",buf,newfd);
it++;
}
else if(ret < 0 && errno == EINTR)
{
it++;
}
else
{
printf("return is %d\n",ret);
close(newfd);
// 当erase后it自动向前移动,所以不用++
it = clients.erase(it);
continue;
}
}
}
}
}
}
select模型分析
- 可监控的文件描述符个数取决与sizeof(fd_set)的值。
- 将fd加入select监控集的同时,还要再使用一个数据结构list保存放到select监控集中的fd,一是用于再select返回后,list作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从list取得fd逐一加入,扫描list的同时取得fd最大值maxfd,用于select的第一个参数。
- 可见select模型必须在select前循环list(加fd,取maxfd),select返回后循环list(FD_ISSET判断是否有事件发生)。