探索 poll 的威力

举报
白茶加冰 发表于 2023/09/23 23:03:11 2023/09/23
【摘要】 1.poll函数原型以下是 poll() 函数的原型:#include <poll.h>int poll(struct pollfd *fds, nfds_t nfds, int timeout);其中:struct pollfd 是一个结构体,用于表示要监视的文件描述符及其所关注的事件。poll() 函数使用 fds 参数传递一个指向 struct pollfd 数组的指针,该数组中的每...

1.png

1.poll函数原型

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

#include <poll.h>

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 是一种多路复用的系统调用,用于监视多个文件描述符上的事件。它具有以下特点:

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

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

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

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

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

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

三.poll特点

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

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

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

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

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

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

四.poll编程步骤

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

  1. 创建并初始化 struct pollfd 数组:对于要监视的每个文件描述符,需要创建一个对应的 struct pollfd 结构体,并设置相关的参数。
#include <poll.h>

struct pollfd fds[2];
fds[0].fd = fd1;
fds[0].events = POLLIN;    // 监视可读事件
fds[1].fd = fd2;
fds[1].events = POLLOUT;   // 监视可写事件

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

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

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

  1. 检查 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 <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <poll.h>
#include <sys/time.h>
#include <unistd.h>

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 < size; ++i)
                        {
                            if (buf[i] >= 'a' && buf[i] <= 'z')
                                buf[i] = buf[i] + ('A' - 'a');
                            else
                                buf[i] = buf[i] + ('a' - 'A');
                        }
                        printf("%s\n", buf);
                        send(fds[i].fd, buf, sizeof(buf), 0);
                    }
                    if (--count == 0)
                        break;
                }
            }
        }
    }
    close(socked);
    return 0;
}

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    //1.socket建立文件描述符
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket is err");
    }

    //2.connect连接服务器
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    int flage = connect(fd, (struct sockaddr *)(&saddr), sizeof(saddr));
    if (flage < 0)
    {
        perror("connect is err");
    }

    //3.服务器端不断发送数据,接受服务器转化后的数据
    char buf[1024] = {0};
    while (1)
    {
        //memset(buf,0,sizeof(buf));
        fgets(buf, sizeof(buf), stdin);
        if (strncmp(buf,"quit#",5)==0)
        {
            break;
        }
        
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';
        send(fd, buf, sizeof(buf), 0);
        flage = recv(fd, buf, sizeof(buf), 0);
        if (flage < 0)
        {
            perror("recv is err");
        }
        else
        {
            fprintf(stdout, "%s\n", buf);
        }
    }
    close(fd);
    return 0;
}

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。