IO多路复用-poll的详解

    xiaoxiao2023-12-03  193

    既然已经有select实现IO多路转接了,为什么还要有poll? 之前说过select有很多的缺点: 而poll的出现解决了select的两个问题: 1、可监控的文件描述符个数有上限 2、参数即作为输入参数又做输出

    poll函数的声明:

    int poll(struct pollfd* fds, nfds_t nfds, int timeout)

    参数解释: 1、fds:监听结构列表,包含三部分内容(文件描述符,监听的事件集合,返回的事件集合) struct pollfd结构: events和recvents的取值,这里仅介绍可读和可写: 数据可读:POLLIN 数据可写:POLLOUT 2、fds数组长度 3、timeout:等待时间,单位是毫秒 所以poll的使用,一般是先定义一个struct pollfd类型的数组,假设要监视0号文件描述符的读事件,就把数组的第一个元素的fd设为0,events设为POLLIN,而revents为返回,当数组的第一个元素的revents变为POLLIN,表示0号文件描述符的读事件就绪。

    返回值解释: 大于0:监听的文件描述符就绪,poll返回 等于0:超时返回 小于0:出错

    socket就绪条件

    读就绪

    sochet内核中,接收缓冲区中的字节数,大于等于低水位标记SO_RECVLOWAT,此时可以无阻塞的读该文件描述符,并且返回值大于0 socket TCP通信中,对端关闭连接,此时对该socket读,则返回0 监听的socket上有新的连接请求时 socket上有未处理的错误时

    写就绪

    socket内核中,发送缓冲区中的可用字节数(发送缓冲区的空闲位置大小),大于等于低水位标记,SO_SNDLOWAT,此时可以无阻塞的写,并且返回值大于0 socket的写操作被关闭(close或者shutdown),对于一个写操作被关闭的socket进行写操作,会触发SIGPIPE信号 socket使用非阻塞connect连接成功或失败之后 socket上有未读取的错误

    异常就绪

    socket上收到带外数据(关于带外数据,和TCP紧急模式相关,在TCP报头中有一个紧急指针的字段)

    编写poll代码: 一、检测标准输入

    1 #include<cstdio> 2 #include<unistd.h> 3 #include<poll.h> 4 5 int main(){ 6 // 因为只关心一个文件描述符,所以就不定义数组了,第二个参数填1就好 7 // 关心0号文件描述符(标准输入)的读事件 8 struct pollfd poll_fd; 9 poll_fd.fd = 0; // 标准输入 10 poll_fd.events = POLLIN; // 读事件 11 // poll_fd.revents为输出型,返回事件的集合 12 while(1){ 13 int ret = poll(&poll_fd,1,3000); // 数组大小为1,等待时间为3000ms(3秒) 14 if(ret < 0){ 15 printf("poll error\n"); 16 continue; 17 } 18 if(ret == 0){ 19 printf("poll timeout\n"); 20 continue; 21 } 22 if(poll_fd.revents == POLLIN){// 当0的读事件就绪时 23 char buf[1024] = {0}; 24 read(0,buf,sizeof(buf)-1); 25 printf("input:%s\n", buf); 26 } 27 } 28 return 0; 29 }

    运行效果为超过3秒不往标准输入写数据,poll会立刻超时返回,如果想标准输入写数据,则标准输入的读会就绪,然后把数据回显.

    二、模拟实现poll服务器

    //初始化监控列表数组,把fd设为-1,监听和返回事件设为0 void Initpolled(struct pollfd* fd_list,int size) { int i = 0; for(i = 0; i<size; i++){ fd_list[i].fd = -1; fd_list[i].events = 0; fd_list[i].revents = 0; } } //按顺序往监听列表添加fd为sock,events为POLLIN的元素 void Add(int sock,struct pollfd* fd_list,int size) { int i = 0; for(i = 0; i<size; i++){ if(fd_list[i].fd < 0){//当这个元素的fd为-1时,说明没有被添加 fd_list[i].fd = sock; fd_list[i].events = POLLIN; break; } } } int main(int argc,char* argv[]) { //建立监听套接字 if(argc != 3){ printf("./server [ip] [port]\n"); return 1; } int listen_sock = socket(AF_INET,SOCK_STREAM,0); if(listen_sock < 0){ perror("socket"); return 2; } struct sockaddr_in local; local.sin_family = AF_INET; local.sin_addr.s_addr = inet_addr(argv[1]); local.sin_port = htons(atoi(argv[2])); if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) < 0){ perror("bind"); return 3; } if(listen(listen_sock,5) < 0){ perror("listen"); return 4; } //建一个polled结构的数组 struct pollfd fd_list[1024]; //初始化polled Initpolled(fd_list,sizeof(fd_list)/sizeof(fd_list[0])); //把listen_sock添加进数组的第0个,关心读 Add(listen_sock,fd_list,sizeof(fd_list)/sizeof(fd_list[0])); while(1){ int ret = poll(fd_list,sizeof(fd_list)/sizeof(fd_list[0]),2000);//等待时间为2秒 if(ret < 0){ perror("poll"); continue; } if(ret == 0){ printf("poll超时!\n"); continue; } //else,则有fd的就绪 int i = 0; for(i = 0; i<sizeof(fd_list)/sizeof(fd_list[0]); i++){ if(fd_list[i].fd == -1)//fd为-1则继续往后走 continue; if(!(fd_list[i].revents & POLLIN)){//不是读就绪则继续走 continue; } if(fd_list[i].fd == listen_sock){//listen_sock就绪,accept建立新连接 struct sockaddr_in client; socklen_t len = sizeof(client); int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len); if(new_sock < 0){ perror("new_sock"); continue; } //把new_sock按顺序放入数组 Add(new_sock,fd_list,sizeof(fd_list)/sizeof(fd_list[0])); } else{//处理new_sock就绪,可以读了 char buf[1024]; ssize_t s = read(fd_list[i].fd,buf,sizeof(buf)-1); if(s < 0){ perror("read"); continue; } printf("client >: $s\n",buf); } } } return 0; }

    客户端和select相同。 poll的优点: 不同于select使用三个位图来表示三个fdset的方式,poll使用一个pollfd的指针实现

    1、pollfd结构包含了要监视的enent和发生的event,不再使用select“参数-值”传递的方式,接口使用比select更加方便 2、poll并没有最大数量限制(但是数量过大后性能也是会下降)

    poll的缺点 poll中监听的文件描述符增多时:

    1、和select一样,poll返回后,需要轮询pollfd来获取就绪的文件描述符 2、每次调用poll函数都需要把大量的pollfd结构从用户态拷贝到内核中 3、同时连接的大量客户端在同一时刻可能只有很少的处于就绪状态,因此随着监视的文件描述符数量的增长,其效率也会线性下降

    1、poll返回后也需要轮询fd_list列表来获取就绪的文件描述符 2、需要把polled从用户态拷贝至内核态 3、虽然没有数量限制,但是数量过大,性能也会线性下降

    最新回复(0)