Linux 下 poll 详解

在Linux系统编程中,poll 是一个强大的多路复用(I/O 多路复用)函数,用于同时监控多个文件描述符的事件,特别是在处理网络套接字或其他I/O设备时。相比于selectpoll 支持监控更多的文件描述符,并且没有像select那样的文件描述符数量限制。

一、poll函数介绍

poll 函数用于在指定的超时时间内监视一组文件描述符,并返回文件描述符上是否有指定的I/O事件发生。

函数原型

#include <poll.h>

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

  • fds :是一个数组,每个元素是一个pollfd结构,描述一个文件描述符及其要监视的事件。
  • nfds:要监视的文件描述符个数。
  • timeout:等待的超时时间(以毫秒为单位)。-1表示无限等待,0表示立即返回(非阻塞模式)。
pollfd 结构体

struct pollfd { int fd; // 要监视的文件描述符

short events; // 等待的事件

short revents; // 实际发生的事件

};

  • fd:要监视的文件描述符,例如套接字或管道。
  • events :感兴趣的事件,可以是以下的值的组合:
    • POLLIN:有数据可读。
    • POLLOUT:可以写数据(不会阻塞)。
    • POLLERR:发生错误。
    • POLLHUP:挂起事件(对方关闭连接)。
    • POLLNVAL:非法的文件描述符。
  • reventspoll返回时,实际发生的事件。
返回值
  • 成功时,返回大于0的值,表示有多少文件描述符有事件发生。
  • 如果超时且无事件发生,返回0。
  • 失败时,返回-1,并设置errno

二、poll 的使用步骤

  1. 创建并初始化pollfd数组:为需要监控的文件描述符设置监视事件。
  2. 调用poll函数 :传入pollfd数组、数组大小和超时时间。
  3. 处理事件 :根据返回的revents判断哪个文件描述符有事件发生,并做出相应处理。

三、poll 示例

下面是一个使用 poll 监视两个套接字的简单例子:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 8080
#define MAX_EVENTS 2

int main() {
    int listenfd, connfd;
    struct sockaddr_in serv_addr;
    struct pollfd fds[MAX_EVENTS];
    int nfds = 1;

    // 创建监听套接字
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(PORT);

    // 绑定并监听端口
    if (bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("bind failed");
        close(listenfd);
        exit(EXIT_FAILURE);
    }

    if (listen(listenfd, 3) < 0) {
        perror("listen failed");
        close(listenfd);
        exit(EXIT_FAILURE);
    }

    // 初始化pollfd数组
    fds[0].fd = listenfd;
    fds[0].events = POLLIN;

    printf("Waiting for connections...\n");

    while (1) {
        int ret = poll(fds, nfds, -1);  // 无限等待事件

        if (ret < 0) {
            perror("poll failed");
            exit(EXIT_FAILURE);
        }

        // 检查监听套接字是否有新连接
        if (fds[0].revents & POLLIN) {
            struct sockaddr_in client_addr;
            socklen_t addr_len = sizeof(client_addr);

            if ((connfd = accept(listenfd, (struct sockaddr*)&client_addr, &addr_len)) < 0) {
                perror("accept failed");
                exit(EXIT_FAILURE);
            }

            printf("New connection accepted\n");
        }
    }

    close(listenfd);
    return 0;
}

这个例子中,程序首先创建了一个监听套接字,然后使用 poll 函数监视这个套接字的 POLLIN 事件(有新的连接到来)。当有新连接时,程序通过 accept 函数接收连接。

四、常用API介绍

在使用poll和其他多路复用函数时,通常会涉及以下API:

  1. socket:创建一个套接字,用于网络通信。

    int socket(int domain, int type, int protocol);

  2. bind:将一个套接字绑定到一个特定的地址和端口。

    int bind(int sockfd, const struct sockaddr *addr,

    socklen_t addrlen);

  3. listen:将一个套接字设置为监听模式,等待客户端的连接。

    int listen(int sockfd, int backlog);

  4. accept:从监听套接字接受一个新的连接。

    int accept(int sockfd, struct sockaddr *addr,

    socklen_t *addrlen);

  5. connect:客户端用于连接到服务端的套接字。

    int connect(int sockfd, const struct sockaddr *addr,

    socklen_t addrlen);

  6. recvsend:分别用于接收和发送数据。

    ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t send(int sockfd, const void *buf, size_t len,

    int flags);

  7. close:关闭文件描述符。

    int close(int fd);

五、pollselect的对比

  • 灵活性poll 可以处理更多的文件描述符,不受select的硬性限制。
  • 事件通知pollpollfd数组更加直观,每个文件描述符有自己的事件和返回事件。
  • 效率poll的实现较select高效,特别是在需要监控大量文件描述符的场景中。

六、结语

poll 提供了一种高效且灵活的方式来监控多个文件描述符的事件,特别适用于网络编程和I/O密集型应用。在实际应用中,poll 被广泛应用于高并发服务器、事件驱动框架等场景中。

如果对文件描述符数量和性能要求更高,还可以考虑使用 epoll,它是 Linux 下的增强版 poll,在处理大规模并发连接时更加高效。

相关推荐
xuanzdhc1 小时前
Linux 基础IO
linux·运维·服务器
愚润求学1 小时前
【Linux】网络基础
linux·运维·网络
bantinghy2 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志3 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
帽儿山的枪手3 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
shadon1789 天前
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
linux
AWS官方合作商9 天前
AWS ACM 重磅上线:公有 SSL/TLS 证书现可导出,突破 AWS 边界! (突出新功能的重要性和突破性)
服务器·https·ssl·aws
小米里的大麦9 天前
014 Linux 2.6内核进程调度队列(了解)
linux·运维·驱动开发
程序员的世界你不懂9 天前
Appium+python自动化(三十)yaml配置数据隔离
运维·appium·自动化
算法练习生9 天前
Linux文件元信息完全指南:权限、链接与时间属性
linux·运维·服务器