Quantcast
Channel: 小蓝博客
Viewing all articles
Browse latest Browse all 3155

C语言中通过poll实现IO多路复用

$
0
0

C语言中通过 poll实现IO多路复用

在C语言中,IO多路复用是一种常用的技术,用于提高程序的并发能力,尤其在处理多个文件描述符(如网络连接、设备等)时。poll是Linux系统提供的一个用于IO多路复用的系统调用,它能够监听多个文件描述符的状态,帮助程序处理多个输入输出事件。

一、什么是IO多路复用?

IO多路复用允许单个进程或线程同时监听多个文件描述符上的事件(如数据的可读、可写等),从而避免阻塞等待,提升程序的并发性能。传统的阻塞IO模型下,每个IO操作会阻塞当前线程,直到操作完成,效率较低。IO多路复用通过非阻塞IO事件驱动机制,允许程序在一个线程中同时处理多个IO事件。

常见的IO多路复用技术有:

  • select:最早的IO多路复用技术,支持多个文件描述符的监听。
  • poll:改进版的 select,可以处理更多的文件描述符。
  • epoll:Linux特有的IO多路复用机制,处理大规模文件描述符时效率更高。

二、poll的工作原理

poll系统调用允许程序同时监控多个文件描述符的状态,类似于 select,但它比 select更加高效,特别是在文件描述符数量较多时。

poll的基本工作原理是:通过一个结构体 pollfd数组传递待监控的文件描述符,并设置需要监听的事件类型。poll会阻塞直到某个事件发生,或在指定的超时时间内返回。

三、poll的使用

poll的原型如下:

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • fds:指向一个 pollfd结构体数组,数组中每个元素对应一个待监控的文件描述符。
  • nfdsfds数组的大小,表示待监控的文件描述符的个数。
  • timeout:等待的时间,单位为毫秒。timeout0时,poll立即返回,timeout为负数时,表示无限期等待。

pollfd结构体定义如下:

struct pollfd {
    int fd;         // 文件描述符
    short events;   // 监控的事件类型
    short revents;  // 返回发生的事件
};
  • events:指定我们希望监听的事件类型(例如,POLLIN表示文件描述符可读,POLLOUT表示文件描述符可写等)。
  • revents:返回发生的事件类型。

四、常见事件类型

poll支持以下几种事件类型:

  • POLLIN:表示文件描述符可读,即数据可以被读取。
  • POLLOUT:表示文件描述符可写,即可以向其写入数据。
  • POLLERR:表示文件描述符发生错误。
  • POLLHUP:表示文件描述符被挂起。

五、通过 poll实现多路复用

下面通过一个具体的示例来说明如何使用 poll来实现IO多路复用。

假设我们有两个文件描述符,一个用于标准输入(stdin),另一个用于网络套接字。我们希望同时监听标准输入和套接字是否可读。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#include <fcntl.h>

#define TIMEOUT 5000 // 5秒超时

int main() {
    // 创建pollfd数组
    struct pollfd fds[2];
  
    // 设置标准输入(fd=0)用于监听输入事件
    fds[0].fd = STDIN_FILENO;  // 标准输入
    fds[0].events = POLLIN;     // 监听可读事件

    // 创建一个模拟的套接字文件描述符(这里使用标准输出,实际应用中可以是套接字文件描述符)
    fds[1].fd = STDOUT_FILENO;  // 假设为网络套接字
    fds[1].events = POLLIN;     // 监听可读事件

    int ret = poll(fds, 2, TIMEOUT); // 监听两个文件描述符,超时5秒

    if (ret == -1) {
        perror("poll");
        exit(1);
    } else if (ret == 0) {
        printf("Timeout occurred! No events happened.\n");
    } else {
        if (fds[0].revents & POLLIN) {
            char buf[128];
            ssize_t bytes_read = read(STDIN_FILENO, buf, sizeof(buf) - 1);
            if (bytes_read > 0) {
                buf[bytes_read] = '\0';
                printf("Data from stdin: %s\n", buf);
            }
        }
        if (fds[1].revents & POLLIN) {
            printf("Data is ready to be read from socket (simulated output).\n");
        }
    }

    return 0;
}

六、代码解析

  1. fds[0]fds[1]初始化:首先,我们为标准输入(stdin)和模拟的套接字(这里使用 stdout)创建了两个 pollfd结构体。

    • fds[0].fd = STDIN_FILENO:将标准输入的文件描述符设置为监听的对象。
    • fds[0].events = POLLIN:监听标准输入是否有数据可读。
  2. 调用 poll:我们使用 poll(fds, 2, TIMEOUT)来监听这两个文件描述符的事件,2表示我们要监听两个文件描述符,TIMEOUT是等待的时间,单位是毫秒。
  3. 处理返回事件poll会阻塞,直到某个事件发生,或者超时返回。

    • 如果 fds[0].revents & POLLIN为真,说明标准输入上有可读数据,程序会从标准输入读取数据。
    • 如果 fds[1].revents & POLLIN为真,说明模拟的套接字有可读数据,程序会处理该事件。

七、poll的优缺点

优点缺点
相比 select支持更多的文件描述符poll仍然需要扫描整个文件描述符集合,效率较低
支持文件描述符的动态增加不适用于大规模的文件描述符集合
简单易用,API清晰当有大量文件描述符时,性能会降低

八、总结

通过 poll实现IO多路复用能够有效提高程序的并发性能,尤其适用于监听多个文件描述符的可读、可写事件。虽然 pollselect更高效,但在需要处理大量文件描述符时,epoll会提供更好的性能。poll的使用方法简单直观,但在性能方面有所限制。适用于中小规模的IO多路复用任务。


Viewing all articles
Browse latest Browse all 3155

Trending Articles