linux下的I/O复用模型之poll详解
- poll函数
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数:
(1)结构体数组
(2)监听的最大个数
(3)时间,当值为负数时表示阻塞
结构体:
struct pollfd {
int fd; 文件描述符
short events; 传入参数:监听的事件
short revents; 传出参数:监听事件中满足条件返回的事件
};
传入参数的设置常用的有
POLLIN:监听读事件
POLLOUT:监听写事件
- poll_server代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/select.h>
#include<arpa/inet.h>
#include<poll.h>
int main()
{
//网络初始化
char recvbuf[1024];
char ip[16];
int ret;
struct pollfd clientfdArr[1024];
struct sockaddr_in addrserver,addrclient;
bzero(&addrserver,sizeof(addrserver));
bzero(&recvbuf,sizeof(recvbuf));
bzero(&ip,sizeof(ip));
addrserver.sin_family = AF_INET;
addrserver.sin_port = htons(27015);
addrserver.sin_addr.s_addr = htonl(INADDR_ANY);
//创建socket
int serverfd = socket(AF_INET,SOCK_STREAM,0);
if(serverfd == -1){
printf("create socket error\n");
return 1;
}
//绑定
ret = bind(serverfd,(struct sockaddr*)&addrserver,sizeof(addrserver));
if(ret == -1)
{
printf("bind error\n");
close(serverfd);
return 1;
}
//监听
ret = listen(serverfd,128);
if(ret == -1)
{
printf("listen error\n");
close(serverfd);
return 1;
}
printf("Poll Server Runing.....\n");
//初始化socket描述符数组
for(int i = 0; i < 1024; ++i)
clientfdArr[i].fd = -1;
clientfdArr[0].fd = serverfd;
clientfdArr[0].events = POLLIN;
while(1)
{
ret = poll(clientfdArr,1024,-1);
if(ret > 0)
{
printf("poll successful...\n");
printf("就绪的数量为:%d\t事件:某客户端请求连接.....\n",ret);
//serverfd就绪
if(clientfdArr[0].revents == POLLIN)
{
socklen_t size = sizeof(addrclient);
int clientfd = accept(serverfd,(struct sockaddr*)&addrclient,&size);
if(clientfd == -1){
printf("accept error\n");
continue;
}
//将新的clientfd加入监听集合
for(int i = 0; i < 1024; ++i)
if(clientfdArr[i].fd == -1){
clientfdArr[i].fd = clientfd;
clientfdArr[i].events = POLLIN;
break;
}
}
//clientfd就绪,数据传输
else
{
for(int i = 0; i < 1024; ++i)
{
if(clientfdArr[i].fd != -1)
{
if(clientfdArr[i].revents == POLLIN)
{
ret = recv(clientfdArr[i].fd,recvbuf,sizeof(recvbuf),0);
if(ret > 0)
{
printf("recv data is:%s\n",recvbuf);
continue;
}
else if(ret == 0)
{
printf("client normal closed...\n");
close(clientfdArr[i].fd);
clientfdArr[i].fd = -1;
break;
}
else{
printf("recv error\n");
close(clientfdArr[i].fd);
clientfdArr[i].fd = -1;
break;
}
}
}
}
}
}
else
{
printf("select error\n");
break;
}
}
close(serverfd);
return 0;
}
- 测试结果:
-
模型评价:
poll本质上和select没有区别,只是取消了最大监控文件描述符数限制和将传入传出分离,并没有从根本上解决select存在的问题.它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设d备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历.它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:
(1)大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
(2)poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
注意:select 和 poll 都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。事实上,同时连接的大量客户端在一时刻可能只有很少的处于就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。
linux学习笔记 文章被收录于专栏
linux学习笔记