探索 poll 的威力

2023年 9月 26日 28.3k 0

1.poll函数原型

以下是 poll() 函数的原型:

#include 

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

其中:

  • struct pollfd 是一个结构体,用于表示要监视的文件描述符及其所关注的事件。
  • poll() 函数使用 fds 参数传递一个指向 struct pollfd 数组的指针,该数组中的每个元素表示一个文件描述符及其关注的事件。
  • nfds 表示 fds 数组中元素的数量,即要监视的文件描述符的数量。
  • timeout 表示等待事件发生的超时时间。它可以有以下几种取值:
    • < 0:表示无限期等待,直到有事件发生。
    • 0:表示立即返回,非阻塞模式,只检查一次文件描述符的状态。
    • > 0:表示等待指定的毫秒数后返回,无论是否有事件发生。

poll() 函数返回一个非负整数,表示有多少个文件描述符满足了指定的事件。此时可以通过遍历 struct pollfd 数组来确定哪些文件描述符触发了事件。

需要注意的是,在调用 poll() 函数之前,必须正确设置 struct pollfd 数组中每个元素的 fd 字段(要监视的文件描述符)和 events 字段(关注的事件)。函数执行完毕后,revents 字段将被填充,用于指示实际发生的事件。

struct pollfd 是一个结构体,用于描述poll()函数中要监视的文件描述符及其关注的事件。

pollfd结构体

这是 struct pollfd 的定义:

struct pollfd {
    int fd;        // 要监视的文件描述符
    short events;  // 关注的事件(用位掩码表示)
    short revents; // 实际发生的事件(用位掩码表示)
};

struct pollfd 包含了以下字段:

  • fd:表示要监视的文件描述符。将会检查该文件描述符上是否发生了要关注的事件。
  • events:表示关注的事件。它是一个位掩码(bitmask),可以使用以下几个常量进行设置:
    • POLLIN:有数据可读(包括普通数据、TCP连接关闭或者可读的文件描述符)。
    • POLLOUT:数据可写入(仅适用于TCP/Unix域流连接)。
    • POLLERR:产生错误。
    • POLLHUP:挂起状态。
    • POLLNVAL:无效请求:文件描述符未打开。
  • revents:在调用 poll() 函数后,由系统填充,用于指示实际发生的事件。它也是一个位掩码,包含以下几个可能的常量:
    • POLLIN:文件描述符上有数据可读。
    • POLLOUT:文件描述符可写。
    • POLLERR:发生错误。
    • POLLHUP:关闭的连接或挂起状态。
    • POLLNVAL:无效请求。

在使用 poll() 函数之前,需要正确设置 struct pollfd 数组中的每个元素的 fdevents 字段。然后在调用 poll() 函数之后,通过检查 revents 字段来确定发生了哪些事件。

请注意,具体的平台可能对支持的事件类型有所不同,因此在使用时请参考相关的文档和手册。

二.poll特点

Linux 的 poll 是一种多路复用的系统调用,用于监视多个文件描述符上的事件。它具有以下特点:

  • 高效的事件驱动机制:poll可以同时监视多个文件描述符,当其中一个文件描述符就绪(可读、可写或出错)时,poll会返回该事件,从而实现了事件驱动的编程模型。

  • 跨平台支持:poll是一个通用的系统调用,在大多数类Unix系统(如Linux、FreeBSD等)上都得到支持,因此可以方便地编写可移植的代码。但是相对于select较差,poll不支持Windowsselect支持。

  • 无最大限制:poll没有对监视的文件描述符数量进行限制,可以同时监视大量的文件描述符,不受系统资源的限制。

  • 没有文件描述符数量限制:与 select 不同,poll 函数没有对文件描述符进行数量限制,因此可以同时监视大量的文件描述符。

  • 不用复制监听的文件描述符:与 select 类似,poll 不会复制文件描述符集合,也不会影响文件描述符的数量。这使得 poll 在使用过程中更加灵活。

  • 需要注意的是,poll 也有一些缺点,比如效率相对较低,无法处理大量的并发连接。在需要处理大量并发连接的场景下,更好的选择是使用 epoll 或者其他高性能的事件驱动框架。

    三.poll特点

    poll 是一个多路复用的系统调用,用于监视多个文件描述符上的事件。它具有以下特点:

  • 高效的事件驱动机制:poll 可以同时监视多个文件描述符,当其中一个文件描述符就绪时(可读、可写或异常),poll 会返回该事件,从而实现了基于事件驱动的编程模型。

  • 高效的轮询模型:与 select 不同,poll 使用了一个包含所有监视文件描述符的数组,不再需要重新构造文件描述符集合。这样可以避免 select 中所谓的"位图"传递导致的性能损失。

  • 没有文件描述符数量限制:与 select 不同,poll 函数没有对文件描述符数量进行限制,因此可以同时监视大量的文件描述符。

  • 没有文件描述符复制:与 select 类似,poll 不会复制文件描述符集合,也不会影响文件描述符的数量。这使得 poll 在使用过程中更加灵活。

  • 需要注意的是,poll 也有一些限制,例如效率相对较低,无法处理大量的并发连接。在需要处理大量并发连接的场景下,更好的选择是使用更高效的事件驱动框架,例如 epoll

    四.poll编程步骤

    使用 poll 进行编程的一般步骤如下:

  • 创建并初始化 struct pollfd 数组:对于要监视的每个文件描述符,需要创建一个对应的 struct pollfd 结构体,并设置相关的参数。
  • #include 
    
    struct pollfd fds[2];
    fds[0].fd = fd1;
    fds[0].events = POLLIN;    // 监视可读事件
    fds[1].fd = fd2;
    fds[1].events = POLLOUT;   // 监视可写事件
    

    这里示例创建了一个包含两个文件描述符的数组 fds,并设置了要监视的事件类型。

  • 调用 poll 函数进行多路复用:
  • int ret = poll(fds, 2, timeout_ms);
    

    poll 函数的第一个参数是指向 struct pollfd 数组的指针,第二个参数是数组中的元素数量,第三个参数是超时时间(以毫秒为单位)。poll 函数会在超时时间内阻塞,直到有一个或多个文件描述符就绪或超时。

  • 检查 poll 的返回值和事件:
  • if (ret > 0) {
        // 有事件就绪
        for (int i = 0; i < 2; i++) {
            if (fds[i].revents & POLLIN) {
                // 可读事件就绪
                // 处理可读事件
            }
            if (fds[i].revents & POLLOUT) {
                // 可写事件就绪
                // 处理可写事件
            }
            // 其他事件类型类似
        }
    } else if (ret == 0) {
        // 超时
    } else {
        // 发生错误
    }
    

    poll 函数返回大于 0 表示有事件就绪的文件描述符数量,等于 0 表示超时,小于 0 表示错误。通过检查 revents 字段可以确定哪些事件就绪,可以根据需要处理相应的事件。

    需要注意的是,以上只是 poll 的一般编程步骤,具体的实现可能会根据实际需求有所变化。同时,为了编写更健壮的代码,应该对返回的异常情况进行适当处理,例如处理 poll 错误或文件描述符的异常断开。

    五.编程实战

    利用poll实现一个高并发服务器,当服务连接成功后,客户端发送小写字母的字符串,服务器端发送其大写形式。

    server.c

    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include
    #include

    int main(int argc, char const *argv[])
    {
    //1.socket创建套接字
    int socked = socket(AF_INET, SOCK_STREAM, 0);
    if (socked < 0)
    {
    perror("socket is err");
    return -1;
    }

    //2.bind绑定服务器ip地址和端口号
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int len = sizeof(caddr);
    int ret = bind(socked, (struct sockaddr *)(&saddr), sizeof(saddr));
    if (ret < 0)
    {
    perror("bind is err");
    return -1;
    }

    //3.listen设置同时最大链接数
    ret = listen(socked, 5);
    if (ret < 0)
    {
    perror("listen is err");
    return -1;
    }

    //4.poll前置工作
    struct pollfd fds[2048]; //创建结构体数组

    fds[0].fd = socked; //初始化监听的文件描述符
    fds[0].events = POLLIN;

    //5.poll进行相关逻辑操作
    int count,size;
    int count_fd = 1; //监听个数
    int accepted;
    char buf[1024] = {0};
    while (1)
    {
    count = poll(fds, count_fd, -1);
    if (count < 0)
    {
    perror("poll is err");
    break;
    }
    for (int i = 0; i < count_fd; ++i)
    {
    if (fds[i].revents == POLLIN)
    {
    if (fds[i].fd == socked)
    {
    accepted = accept(socked, (struct sockaddr *)(&caddr), &len);
    printf("port:%d ip: %s\n", ntohs(caddr.sin_port), inet_ntoa(caddr.sin_addr));
    fds[count_fd].fd = accepted;
    fds[count_fd++].events = POLLIN;
    if (--count == 0)
    break;
    }
    else
    {
    int flage = recv(fds[i].fd, buf, sizeof(buf), 0);
    if (flage < 0)
    {
    perror("recv is err");
    }
    else if (flage == 0)
    {
    printf("ip:%s is close\n", inet_ntoa(caddr.sin_addr));
    close(fds[i].fd);
    fds[i] = fds[--count_fd]; //交换
    --i; //交换后的i没有验证
    }
    else
    {
    size = strlen(buf);

    for (int i = 0; i = 'a' && buf[i]

    相关文章

    服务器端口转发,带你了解服务器端口转发
    服务器开放端口,服务器开放端口的步骤
    产品推荐:7月受欢迎AI容器镜像来了,有Qwen系列大模型镜像
    如何使用 WinGet 下载 Microsoft Store 应用
    百度搜索:蓝易云 – 熟悉ubuntu apt-get命令详解
    百度搜索:蓝易云 – 域名解析成功但ping不通解决方案

    发布评论