本文为UNP第6章学习笔记
一, select函数
#include <sys/select.h>
int select(int maxfd,fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
参数:
- maxfd: select管理的最大fd+1, 也就是说你必须自己算出最大的fd是多大
- readset: 需要监控可读的fd集合, 只要read/accept操作不会阻塞,就算可读状态, 包括对方已关闭(返回0),套接字出错(返回负值),可读(返回正值),有新连接(可accept)
- writeset 需要监控可写的fd集合,wrte操作不阻塞或非阻塞socket中connect已完成,就算可写, 包括对方已关闭(SIGPIPE信号),套接字出错(返回负值),可写, connect完成或出错
- exceptset 异常条件的fd集合,通常用不到, 注意socket出错不算异常,而算是可读或可写
- timeout: NULL:阻塞 为0的timeval: 立刻返回 非0的timeval: 带定时器的阻塞
返回值:
- >0: 就绪fd的数目
- =0:超时
- <0:出错
二, fd_set相关宏
fd_set用来表示一组fd集合, 一般是一个long int数组,可能的实现如下:
struct fd_set{
long int fd_bits[16];
};
fd_set用每个位是1/0来记录fd, 16个long int共有 16*64=1024位, 因此select只能处理最多1024个fd.
fd_set可能有不同的实现,但我们只需要关注这4个操作fd_set的宏:
void FD_ZERO(fd_set* fdset);
//将整个fdset置0
void FD_SET(int fd,fd_set* fdset);
//将某个fd加到fdset中
void FD_CLR(int fd,fd_set* fdset);
//将某个fd从fdset中删除
int FD_ISSET(int fd,fd_set* fdset);
//判断某个fd是否在fdset中
三,select的性质
1,select最多只支持1024个fd
2,每次select完成后会清空各fd_set种未就绪的fd,因此:
1,你每次select都要重新用FD_SET加入fd
2,你需要对每个监控的fd调用一次FD_ISSET来判断这个fd是否就绪
这些性质导致select的性能随监控fd数量的增加线性下降, 更好也更流行的方法是epoll, 同时epoll的使用也更为简单
四,用select实现的单线程服务器示例:
代码不够简洁,总之:1,有一个数据结构记录所监控的fd 2,计算maxfd
//...... init listenfd
set<int> connected_fds;
fd_set rset;
int nready;
int maxfd;
vector<int> del_fds;
while(1)
{
FD_ZERO(&rset);
FD_SET(listenfd,&rset);
maxfd=listenfd;
for(set<int>::iterator iter=connected_fds.begin();iter!=connected_fds.end();iter++)
{
FD_SET(*iter,&rset);
maxfd = maxfd>*iter ? maxfd : *iter;
}
nready = select(maxfd+1,&rset,0,0,0);
if(FD_ISSET(listenfd,&rset))
{
printf("a new connection\n");
newfd = accept(listenfd,(sockaddr*)&client_addr,&client_addr_len);
if(newfd<=0)
{
printf("accept failed:%s\n",strerror(errno));
return -1;
}
connected_fds.insert(newfd);
}
for(set<int>::iterator iter=connected_fds.begin();iter!=connected_fds.end();iter++)
{
if(FD_ISSET(*iter,&rset))
{
printf("connection %d was selected\n",*iter);
ret = read(*iter,recv_buf,sizeof(recv_buf));
if(ret==0)
{
printf("connection %d closed by remote\n",*iter);
close(*iter);
del_fds.push_back(*iter);
}else if(ret <0){
printf("connection %d err: %s\n",*iter,strerror(errno));
return -1;
}else{
recv_buf[ret]='\0';
printf("recv:[%s]\n",recv_buf);
strcpy(send_buf,recv_buf);
write(*iter,send_buf,strlen(send_buf));
}
}
}
for(vector<int>::iterator iter=del_fds.begin();iter!=del_fds.end();iter++)
{
connected_fds.erase(*iter);
}
del_fds.clear();
}
五,poll函数
#include <poll.h>
int poll(struct pollfd* fdarray, unsigned long nfds, int timeout);
参数:
- fdarray: 记录fd信息的数组指针
- nfds: 数组长度
- timeout: 作用与select最后一个参数相同,注意这是一个int,单位是毫秒. INFTIM:阻塞 0 非阻塞 >0:超时时间
poll提供的功能与select基本相同, 可以看错是使用了另一种方式组织管理fd信息,
struct pollfd{
int fd;
//fd
short events;
//监控的fd状态 一般为 POLLIN或POLLOUT
short revents;
//返回就绪的状态
};
如果fd<0, 那么poll将自动忽略这个pollfd
六, poll的性质
- 因为poll函数管理fd的数据是由用户自由分配的数组,因此poll没有管理fd数量的限制
- poll不会修改未就绪的pollfd信息, 但你仍需需要检查每个pollfd的revents来判断该fd是否就绪
比较而言, select的使用比poll更为广泛. 不过相比之下,新一代的epoll有明显的优势