一、为什么要用Poll
由于select参数太多,较于复杂,调用起来较为麻烦;poll对其进行了优化
二、poll机制
poll也是一个系统调用,每次调用都会将所有客户端的fd拷贝到内核空间,然后进行轮询,判断IO是否就绪,然后返回就绪的客户端数量。
底层也是select的实现,但是相比于select,poll解决了其参数的限制问题。
struct pollfd{
int fd; //传入需要处理客户端的fd
short events; //判断是否可读,可写或者可出错
short revents; //返回该客户端是否真正可读,可写或者可出错
}
int poll(struct pollfd *fds, nfds_t nfds,const struct timespec *tmo_p);
/*
fds: 需要进行处理的客户端
nfds: 表示fds的数量,最大不可超过fds的边界值,因为是从0开始索引,一般 + 1
tmo_p: 超时时间,< 0 代表永久阻塞, ==0 代表立即响应,不阻塞, > 0 等待具体时间,单位毫秒
return value: 失败就是-1,成功就是fds的数量
*/
三、具体实操
1、创建pollfd数组,用于存放客户端的fd,以及事件类型
struct pollfd fds[1024]={0};
fds[socketfd].fd = socketfd; //将监听描述符加入到数组中
fds[socketfd].events = POLLIN; //监听可读事件
2、调用poll函数,阻塞等待事件发生
int maxfd = socketfd; //对集合遍历的最大值,集合也有边界,从0开始
while(true){
int nready = poll(fds, maxfd + 1, -1); //等待IO就绪
cout<<"nready:"<<nready<<endl;
if(nready < 0){ //出错处理
cout << "select error:" << strerror(errno) << endl;
continue;
}else{
//accept操作,此处省略
}
//recv操作,此处省略
}
3、遍历就绪的客户端,处理新连接,accept操作
if(fds[socketfd].revents & POLLIN){ //是否可读,可读那就接收数据=====accept
int clientfd = accept(socketfd, (struct sockaddr *)&clientaddr, &len);
cout<<"clientfd:"<<clientfd<<endl; //获取到客户端的连接描述符
fds[clientfd].fd = clientfd;
fds[clientfd].events = POLLIN | POLLERR; //监听可读事件和错误事件
if(clientfd > maxfd){ //更新遍历的边界,回收旧的连接,更新边界值
maxfd = clientfd;
}
}
4、recv操作,接收数据
//recv处理
for(int i = socketfd + 1; i <= maxfd; i++){
cout<<"i:"<<i<<endl;
if(fds[i].revents & POLLIN){ //判断IO是否可读
char buffer[1024] = {0};
int count = recv(i, buffer, 1024, 0);
if(count == 0){
cout<<"client close"<<endl;
close(i);
fds[i].fd = -1; //将无效的描述符设置为-1,防止下次遍历出错
fds[i].events = 0; //将无效的描述符的事件设置为0,防止下次遍历出错
continue;
}
cout << "buffer:" << buffer << endl;
//返回信息
count = send(i, buffer, count, 0);
}
}