【计算机网络】IO复用方法(三)——poll

目录

[一、poll 的基本概念](#一、poll 的基本概念)

[二、 poll 函数原型](#二、 poll 函数原型)

[三、pollfd 结构详解](#三、pollfd 结构详解)

[四、poll 的使用步骤](#四、poll 的使用步骤)

[五、poll 示例代码](#五、poll 示例代码)

[六、poll 的优缺点](#六、poll 的优缺点)

七、注意事项


一、poll 的基本概念

poll 是一种 I/O 多路复用技术,用于监视多个文件描述符的状态(可读、可写或异常)。与 select 类似,在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。但 poll 解决了 select 的一些限制,如文件描述符数量的限制。

poll 通过一个 pollfd 结构数组来管理文件描述符,支持更多的事件类型,且没有固定数量的文件描述符限制(仅受系统资源限制)。

二、 poll 函数原型

poll 函数原型:

c 复制代码
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数说明:

  • fds:pollfd 结构数组,指定我们感兴趣的所有文件描述符的可读、可写和异常事件。
  • nfds:数组长度
  • timeout:超时时间(毫秒),-1 表示无限等待

返回值:

  • 正数:就绪的文件描述符数量
  • 0:超时
  • -1:出错

三、pollfd 结构详解

pollfd 结构定义如下:

c 复制代码
struct pollfd {
    int fd;         // 文件描述符
    short events;   // 监视的事件
    short revents;  // 实际发生的事件
};

fd:指定文件描述符,

events:告诉poll监听fd上的哪些事件,是一系列的事件的按位或

revents:由内核修改,通知程序fd实际发生的哪些事件

可以在堆区malloc

常用事件标志:

  • POLLIN:数据可读
  • POLLOUT:可写
  • POLLERR:错误发生
  • POLLHUP:挂起
  • POLLNVAL:无效请求

四、poll 的使用步骤

定义一个 pollfd 结构数组,每个元素对应一个需要监视的文件描述符。结构包含文件描述符 fd、感兴趣的事件 events 和实际发生的事件 revents。

调用 poll 函数,传入 pollfd 数组、数组长度和超时时间(毫秒)。函数会阻塞直到有事件发生或超时。

检查每个 pollfd 的 revents 字段,确定哪些文件描述符就绪,并进行相应操作。

五、poll 示例代码

以下是一个简单的 poll 服务器示例,监听标准输入和套接字:

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <poll.h>
#define MAXFD 10
int socket_init();
void fds_init(struct pollfd fds[]){
    for(int i=0;i<MAXFD;i++){
        fds[i].fd=-1;
        fds[i].events=0;
        fds[i].revents=0;
    }
}
//添加元素
void fds_add(struct pollfd fds[],int fd)
{
    for(int i=0;i<MAXFD;i++){
        if(fds[i].fd=-1){
            fds[i].fd=fd;
            fds[i].events=POLLIN;//读事件
            fds[i].revents=0;
            break;//只找一个空位
        }
    }
}
//删除元素
void fds_del(struct pollfd fds[],int fd)
{
    for(int i=0;i<MAXFD;i++){
        if(fds[i].fd=fd){
            fds[i].fd=-1;
            fds[i].events=0;
            fds[i].revents=0;
            break;//只找一个空位
        }
    }
}
//监听套接字
void accept_client(int sockfd,struct pollfd fds[]){
    int c=accept(sockfd,NULL,NULL);
    if(c<0){
        return;
    }
    printf("accpect c=%d\n",c);
    fds_add(fds,c);//将c添加
}
//连接套接字
void recv_data(int c,struct pollfd fds[]){
    char buff[128];
    int n=recv(c,buff,127,0);
    if(n<=0){
        fds_del(fds,c);
        close(c);
        printf("client close\n");
        return;
    }
    printf("buff (c=%d)=%s\n",c,buff);
    send(c,"ok",2,0);
}
int main(){
    int sockfd=socket_init();//创建监听套接字,当客户端执行connect连接服务器时,读事件就绪
    if(sockfd==-1){
        exit(1);
    }
    struct pollfd fds[MAXFD];
    fds_init(fds);//空
    fds_add(fds,sockfd);
    while (1)
    {
        int n=poll(fds,MAXFD,5000);//阻塞最长等待5s,当n>0,则
        if(n==-1){
            printf("poll errpr");
        }else if(n==0){
            printf("time out");
        }else{
            for(int i=0;i<MAXFD;i++){
                if(fds[i].fd==-1){
                    continue;
                }
                if(fds[i].revents&POLLIN){
                    if(fds[i].fd==sockfd){
                        accept_client(sockfd,fds);
                    }else{
                        recv_data(fds[i].fd,fds);
                    }
                }
            }
        }
    }
    
}
int socket_init(){//创建监听套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if( sockfd == -1){//创建套接字失败
        return -1;
    }
    struct sockaddr_in saddr;//ipv4专用,制定ip端口
    memset(&saddr,0,sizeof(saddr));//清空
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//绑定,通用套接字
    if( res == -1){
        printf("bind err\n");
        return -1;
    }
    res = listen(sockfd,5);//监听队列
    if( res == -1){
        return -1;
    }
    return sockfd;
}

主函数:创建套接字,该部分主要用于指定ip端口,实时监听;定义一个数组,该数组用于创建文件描述符;首先情况该数组,将所有的值置为-1;添加,将sockfd添加到数组fds中;添加文件描述符到fds数组中,此时fds拥有的用户关心的描述符socket fd和事件(该实例只关心读事件)。此时调用库,将数组以及结构体大小传进来,得到返回值。(正数则为找到就绪的)。判断是哪一种套接字,如果是监听套接字则accpet,反之recv。

总结:找到哪个就绪的数据去处理。

六、poll 的优缺点

优点:

  • 没有文件描述符数量限制(select 通常限制为 1024)
  • 更高效,尤其在大量空闲文件描述符时
  • 更灵活的事件类型支持

缺点:

  • 每次调用仍需遍历整个文件描述符集合
  • 在 Linux 上不支持文件描述符的动态增减(epoll 支持)
  • 性能在极高并发时可能不如 epol

七、注意事项

使用 poll 时应注意:

  • 确保 pollfd 数组中的 fd 字段有效
  • 正确处理 timeout 参数,避免 CPU 空转
  • 检查 revents 时使用位与操作(&)
  • 在多线程环境中注意线程安全问题
  • 某些系统可能不支持所有事件类型
相关推荐
小无名呀2 小时前
socket_udp
linux·网络·c++·网络协议·计算机网络·udp
wusam2 小时前
计算机网络实验04:IP与ICMP数据报分析实验
网络·计算机网络·icmp分片报文
水月wwww3 小时前
ubuntu网络连接出错解决办法
linux·运维·计算机网络·ubuntu·操作系统·ubuntu网络连接
0和1的舞者12 小时前
网络通信的奥秘:网络层ip与路由详解(四)
大数据·网络·计算机网络·计算机·智能路由器·计算机科学与技术
七七七七0716 小时前
【计算机网络】深入理解ARP协议:工作原理、报文格式与安全防护
linux·服务器·网络·计算机网络·安全
CodeLongBear21 小时前
计算机网络学习笔记 | 传输层核心知识点总结(DAY03,匠心制作)
笔记·学习·计算机网络
火车叨位去19491 天前
计算机网络R2025秋(TYUT)【计算机网络】第5章 运输层
网络·tcp/ip·计算机网络
甄心爱学习1 天前
计算机网络8
计算机网络
CodeLongBear2 天前
Day02计算机网络网络层学习总结:从协议到路由全解析
学习·计算机网络·dubbo