【Linux】网络高级IO

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:折纸花满衣
🏠个人专栏:Linux

在这里插入图片描述


👉🏻五种IO模型

在这里插入图片描述

网络高级IO的五大模型主要包括:

  1. 阻塞IO(Blocking IO)

    • 描述:在内核将数据准备好之前,系统调用会一直等待。所有的套接字,默认都是阻塞方式。
    • 示例:服务端在处理客户端的连接和数据时,会阻塞在acceptread操作上,等待建立连接和读取数据。
    • 特点:两个阶段(等待读就绪和读数据)都是阻塞的。
      在这里插入图片描述
  2. 非阻塞IO(Non-blocking IO)

    • 描述:如果内核还未将数据准备好,系统调用仍然会直接返回,并返回EWOULDBLOCK错误码。
    • 示例:虽然可以通过多线程实现伪非阻塞,但真正的非阻塞IO需要操作系统提供非阻塞的支持。
    • 特点:用户进程发起请求后,如果数据未准备好,则立即返回,不会阻塞用户进程。
      在这里插入图片描述
  3. IO多路复用(IO Multiplexing)

    • 描述:通过一种机制(如select、poll、epoll等)同时监控多个文件描述符的就绪状态,从而避免阻塞在单个文件描述符上。
    • 示例:select是其中一种实现方式,它使用事件集合方式来监控多个文件描述符。
    • 特点:能够同时等待多个文件描述符的就绪状态,提高IO效率。
      在这里插入图片描述
  4. 信号驱动IO(Signal-driven IO)

    • 描述:内核将数据准备好的时候,使用SIGIO信号通知应用程序进行IO操作。
    • 示例:在处理僵尸进程时,可以使用信号来通知父进程回收子进程的退出信息。
    • 特点:通过信号机制来通知应用程序进行IO操作,避免了轮询的开销。
      在这里插入图片描述
  5. 异步IO(Asynchronous IO)

    • 描述:由内核在数据拷贝完成时,通知应用程序。
    • 示例:类似于钓鱼的比喻,异步IO是“帮你钓鱼”,即内核完成数据拷贝后通知应用程序。
    • 特点:应用程序不需要等待数据拷贝完成,内核在数据拷贝完成后会主动通知应用程序。
      在这里插入图片描述

这五大模型各有特点,适用于不同的场景和需求。在实际应用中,需要根据具体情况选择合适的IO模型来提高程序的性能和效率。

参考文章:浅谈5种IO模型

👉🏻消息通信的同步异步与进程线程的同步异步有什么不同?

消息通信的同步异步与进程线程的同步异步在概念和应用上存在一些差异,以下是具体的分析和归纳:

🌹 消息通信的同步与异步

  1. 定义

    • 同步:在消息通信中,同步意味着发送方发送消息后,会等待接收方的响应,即一次调用,一次返回。发送方在收到接收方的响应之前,不会继续执行其他操作。
    • 异步:异步通信中,发送方发送消息后不会立即等待接收方的响应,而是继续执行其他操作。接收方在接收到消息后,会通过某种方式(如回调函数、事件通知等)通知发送方。
  2. 特点

    • 同步
      • 严格按照顺序执行,发送方和接收方之间保持紧密的同步关系。
      • 适用于需要确保消息被正确处理并获取结果的情况。
      • 可能会降低系统效率,因为发送方需要等待接收方的响应。
    • 异步
      • 发送方和接收方之间相对独立,发送方发送消息后可以继续执行其他任务。
      • 适用于需要并行处理多个任务或不需要立即获取结果的情况。
      • 可以提高系统效率,因为发送方不需要等待接收方的响应。

🌹 进程与线程的同步与异步

  1. 定义

    • 同步:在进程或线程中,同步指的是多个任务按照特定的顺序依次执行,即一个任务执行完毕后再开始执行下一个任务。
    • 异步:异步则是指多个任务可以同时执行,不需要等待前一个任务完成。
  2. 特点

    • 同步
      • 确保任务的顺序执行,有助于管理资源和避免竞态条件。
      • 可能会降低系统效率,因为需要等待前一个任务完成才能开始下一个任务。
    • 异步
      • 提高系统效率,允许多个任务同时执行。
      • 需要额外的机制来管理任务之间的依赖关系和协调资源的访问。

🌹总结

消息通信的同步异步与进程线程的同步异步在概念上有所相似,但应用场景和关注点有所不同。消息通信主要关注消息发送和接收之间的同步或异步关系,而进程线程的同步异步则关注任务之间的执行顺序和并发性。在实际应用中,需要根据具体的需求和场景来选择合适的同步异步方式。

👉🏻非阻塞IO

fcntl函数

fcntl函数是计算机中用于文件描述符控制的一种函数,它允许对已打开的文件性质进行修改。以下是fcntl函数的用法介绍:

🥕 一、函数声明

#include <fcntl.h>
#include <unistd.h>

int fcntl(int fd, int cmd, ...);

fcntl`函数接受三个参数:

  1. fd:文件描述符,代表要操作的已打开文件。
  2. cmd:操作命令,指定要对文件描述符进行的操作类型。
  3. :可选参数,根据cmd的值,可能需要一个int argstruct flock *lock作为第三个参数。

🥕 二、功能介绍

fcntl函数根据cmd参数的值执行不同的操作,主要有以下几种:

  1. F_DUPFD

    • 复制一个现有的文件描述符。
    • 查找大于或等于参数arg的最小且仍未使用的文件描述符,并复制参数fd的文件描述符。
    • 成功时返回新复制的文件描述符。
  2. F_GETFD/F_SETFD

    • F_GETFD:取得与文件描述符fd联合的close-on-exec标志。
    • F_SETFD:设置close-on-exec标志。如果文件描述符设置了此标志,则在执行exec()相关函数时,文件将被关闭。
  3. F_GETFL/F_SETFL

    • F_GETFL:取得文件描述符状态标志。
    • F_SETFL:设置文件描述符状态标志。可以更改的标志包括O_APPEND(追加写)、O_NONBLOCK(非阻塞IO)和O_ASYNC(异步IO通知)。
  4. F_GETLK/F_SETLK/F_SETLKW

    • 用于获取、设置和等待文件锁。
    • F_SETLKW与F_SETLK功能相同,但无法建立锁定时会阻塞等待。
  5. F_GETOWN/F_SETOWN

    • 用于获取/设置异步IO的所有权,即哪个进程或线程将接收SIGIO和SIGURG信号。

🥕 三、返回值

  • 如果成功,根据cmd的值,fcntl可能返回不同的值。
  • 如果出错,所有命令都返回-1,并设置全局变量errno以指示错误。

🥕四、使用实例

在网络编程中,fcntl常被用于将文件描述符设置为非阻塞模式,以便在数据未就绪时不会阻塞进程。以下是一个简单的示例,展示了如何使用fcntl将标准输入设置为非阻塞模式:

#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>

int main(void) {
    int flags, n;
    //char buf[10];

    // 获取stdin的当前标志
    flags = fcntl(STDIN_FILENO, F_GETFL);
    if (flags == -1) {
        perror("fcntl error");
        return 1;
    }

    // 添加O_NONBLOCK标志
    flags |= O_NONBLOCK;

    // 设置stdin为非阻塞模式
    if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) {
        perror("fcntl error");
        return 1;
    }

    // 接下来的read调用将不会阻塞,如果数据未就绪,将返回-1并设置errno为EAGAIN或EWOULDBLOCK
    // ...
	while (1) {
	 char buf[1024] = {0};
 	 ssize_t read_size = read(0, buf, sizeof(buf) - 1);
 		
 		if (read_size < 0) {
 		perror("read:");
 		sleep(1);
 		continue;
	 }
	 printf("input:%s\n", buf);
 }
    return 0;
}

🥕五、总结

fcntl是一个功能强大的函数,它允许程序对文件描述符进行精细的控制。通过fcntl,程序可以改变文件的性质、设置锁、更改异步IO行为等。在编写涉及文件操作或网络编程的程序时,fcntl是一个值得了解和掌握的函数。

👉🏻I/O多路转接之select

select函数

select函数是一个用于I/O多路复用的系统调用,它允许程序监视多个文件描述符的状态变化(例如可读、可写或发生异常)。这对于实现高效的I/O操作,尤其是非阻塞I/O和服务器程序中的并发处理非常有用。

🥕 一、函数声明

在POSIX兼容的系统中,select函数的声明通常如下:

#include <sys/select.h>
#include <sys/time.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

🥕 二、参数说明

  • nfds:指定被监听的文件描述符集合中最大文件描述符加1。通常设置为监听的文件描述符集合中的最大值加1。
  • readfds:指向一个文件描述符集合的指针,该集合中的文件描述符用于监视读状态变化。
  • writefds:指向一个文件描述符集合的指针,该集合中的文件描述符用于监视写状态变化。
  • exceptfds:指向一个文件描述符集合的指针,该集合中的文件描述符用于监视异常状态变化。
  • timeout:指向一个timeval结构的指针,该结构指定了select函数的超时时间。如果设置为NULL,则select会无限期地等待。

🥕 三、返回值

  • 成功时,select返回就绪的文件描述符的总数。
  • 如果超时,返回0。
  • 如果出错,返回-1并设置errno以指示错误。

🥕 四、文件描述符集合

fd_set是一个文件描述符集合,它通常通过一系列宏来操作,例如FD_ZEROFD_SETFD_CLRFD_ISSET。这些宏定义在<sys/select.h>头文件中。

🥕 五、使用实例

以下是一个简单的select使用示例,用于监视标准输入(stdin)的可读状态:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/select.h>

int main(void) {
    fd_set readfds;
    struct timeval tv;
    int ret;

    // 初始化文件描述符集合
    FD_ZERO(&readfds);
    FD_SET(STDIN_FILENO, &readfds);

    // 设置超时时间为5秒
    tv.tv_sec = 5;
    tv.tv_usec = 0;

    // 调用select函数
    ret = select(STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
    if (ret == -1) {
        perror("select error");
        exit(EXIT_FAILURE);
    } else if (ret == 0) {
        printf("No data within 5 seconds.\n");
    } else {
        // 检查标准输入是否可读
        if (FD_ISSET(STDIN_FILENO, &readfds)) {
            // 读取数据...
            // ...
            printf("Data is available on stdin.\n");
        }
    }

    return 0;
}

🥕六、select缺点

  • 每次调用select, 都需要手动设置fd集合, 从接口使用角度来说也非常不便.
  • 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
  • 同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
  • select支持的文件描述符数量太小

🥕七、总结

select函数是一个强大的工具,允许程序同时监视多个文件描述符的状态变化。然而,在处理大量文件描述符时,select可能会遇到性能瓶颈,因为它需要遍历所有被监视的文件描述符。在这种情况下,更现代的替代品(如pollepoll)可能更适合。不过,对于许多常见的应用场景,select仍然是一个简单而有效的解决方案。

select使用示例: 检测标准输入输出

#include <stdio.h>
#include <unistd.h>
#include <sys/select.h>
int main()
{
    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET(0, &read_fds);
    for (;;)
    {
        printf("> ");
        fflush(stdout);
        int ret = select(1, &read_fds, NULL, NULL, NULL);
        if (ret < 0)
        {
            perror("select");
            continue;
        }
        if (FD_ISSET(0, &read_fds))
        {
            char buf[1024] = {0};
            read(0, buf, sizeof(buf) - 1);
            printf("input: %s", buf);
        }
        else
        {
            printf("error! invaild fd\n");
            continue;
        }
        FD_ZERO(&read_fds);
        FD_SET(0, &read_fds);
    }
    return 0;
}

在这里插入图片描述

👉🏻I/O多路转接之poll

poll函数

poll函数是Linux中用于I/O多路复用的系统调用之一,类似于select函数,但它在处理大量文件描述符时更加灵活和高效。以下是poll函数的详细用法介绍:

🥕 一、函数声明

#include <poll.h>

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

🥕二、参数说明

  1. fds:指向pollfd结构数组的指针,每个pollfd结构表示一个需要监视的文件描述符。

    • fd:待监视的文件描述符。
    • events:指定要监视的事件类型,如POLLIN(数据可读)、POLLOUT(数据可写)等。
    • revents:函数返回时,表示实际发生的事件类型。
  2. nfdsfds数组中的元素数量,即要监视的文件描述符的数量。

  3. timeout:指定poll函数的超时时间(以毫秒为单位)。

    • -1:表示poll调用将阻塞等待,直到至少有一个文件描述符上发生事件。
    • 0:表示poll调用将立即返回,无论是否有文件描述符上发生事件。
    • 正整数:表示poll调用将在指定的毫秒数内等待,如果在此期间没有文件描述符上发生事件,则超时返回。

🥕 三、返回值

  • 正整数:表示在指定时间内,有多少个文件描述符上的事件已经就绪。
  • 0:表示在指定的超时时间内,没有任何文件描述符上的事件发生。
  • -1:表示函数调用失败,此时会设置全局变量errno以指示错误。

🥕 四、使用步骤

  1. 创建一个pollfd结构数组,并设置每个元素的fdevents字段。
  2. 调用poll函数,传入pollfd结构数组、数组长度和超时时间。
  3. 检查poll函数的返回值,以及每个pollfd结构的revents字段,以确定哪些文件描述符上的事件已经就绪。
  4. 根据需要处理就绪的文件描述符上的事件。

🥕 五、示例代码

以下是一个简单的示例代码,展示了如何使用poll函数来监视标准输入(stdin)的可读状态:

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

int main(void) {
    struct pollfd fds[1];
    char buffer[1024];
    int n;

    // 初始化pollfd结构数组
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN; // 监视可读事件

    // 调用poll函数,设置超时时间为5秒
    int timeout = 5000; // 5秒转换为毫秒
    n = poll(fds, 1, timeout);

    if (n == -1) {
        perror("poll error");
        exit(EXIT_FAILURE);
    } else if (n == 0) {
        printf("No data within 5 seconds.\n");
    } else {
        // 检查标准输入是否可读
        if (fds[0].revents == POLLIN) {
            // 读取数据
            ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
            if (bytes_read > 0) {
                buffer[bytes_read] = '\0'; // 添加字符串终止符
                printf("Read %zd bytes: %s", bytes_read, buffer);
            } else {
                perror("read error");
            }
        }
    }

    return 0;
}

🥕 六、总结

poll函数提供了一种高效的方式来监视多个文件描述符的状态变化。与select函数相比,poll在处理大量文件描述符时更加灵活和高效,因为它没有select函数中的文件描述符数量限制。然而,对于非常大的文件描述符集合,更现代的替代品(如epoll)可能更加适合。

poll示例: 使用poll监控标准输入

#include <poll.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
    struct pollfd poll_fd;
    poll_fd.fd = 0;
    poll_fd.events = POLLIN;

    for (;;)
    {
        int ret = poll(&poll_fd, 1, 5000);
        if (ret < 0)
        {
            perror("poll");
            continue;
        }
        if (ret == 0)
        {
            printf("poll timeout\n");
            continue;
        }
        if (poll_fd.revents == POLLIN)
        {
            char buf[1024] = {0};
            read(0, buf, sizeof(buf) - 1);
            printf("stdin:%s", buf);
        }
    }
}

在这里插入图片描述

👉🏻I/O多路转接之epoll

简单介绍

I/O多路转接之epoll

epoll是Linux内核为处理大批量文件描述符而作的改进的poll,是Linux下多路复用IO接口select/poll的增强版本。它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。以下是关于epoll的详细解释:

一、epoll的特点和优势

  1. 高效性

    • 相较于select和poll,epoll使用了基于事件驱动的方式,仅对活跃的文件描述符进行操作,避免了线性扫描整个文件描述符集合,因此效率更高。
    • epoll通过内核与用户空间共享一个事件表,当文件描述符的状态发生变化时,内核会通知用户空间,从而减少了不必要的系统调用。
    • epoll支持边缘触发(Edge Triggered)模式,使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,进一步提高应用程序效率。
  2. 无限制的文件描述符数量

    • select和poll有文件描述符数量的限制,而epoll没有这样的限制,仅受系统中进程能打开的最大文件数目限制。
  3. 内存使用优化

    • epoll使用mmap()文件映射内存加速内核与用户空间的消息传递,避免了内核与用户空间之间的数据拷贝,提高了效率。

二、epoll的接口和工作原理

epoll提供了三个主要的系统调用:epoll_create()、epoll_ctl()和epoll_wait()。

  • epoll_create():创建一个epoll实例,并返回一个文件描述符。
  • epoll_ctl():用于向epoll实例中添加、修改或删除文件描述符的监视事件。
  • epoll_wait():用于等待注册在epoll实例上的文件描述符的事件发生。当事件发生时,epoll_wait()会返回,并告知哪些文件描述符上的事件已经就绪。

epoll使用红黑树来管理待检测的文件描述符集合,这使得在添加、删除和查找文件描述符时具有对数时间复杂度,从而提高了效率。

三、epoll的使用场景

当需要同时监视多个文件描述符(如sockets、文件、管道等)上的事件,并在有事件发生时通知应用程序进行相应的处理时,epoll是一个非常好的选择。特别是在处理大量并发连接但只有少量活跃连接的情况下,epoll的性能优势尤为明显。

四、总结

epoll作为Linux内核提供的一种高效的多路复用IO接口,其特点在于高效性、无限制的文件描述符数量和内存使用优化。通过epoll,可以方便地同时监视多个文件描述符上的事件,并在事件发生时进行高效的处理。因此,在高并发、低延迟的应用场景中,epoll是一个值得考虑的解决方案。

epoll_create()、epoll_ctl()和epoll_wait()

当使用epoll进行I/O多路复用时,主要涉及到三个系统调用:epoll_create(), epoll_ctl(), 和 epoll_wait()。以下是这些函数的原型和参数解释:

😉 1. epoll_create()

函数原型

int epoll_create(int size);
int epoll_create1(int flags); // 这是 epoll_create 的一个扩展版本

参数解释

  • size(对于epoll_create):这个参数是告诉内核这个监听的数目最大值。注意这个值只是内核初始分配内部数据结构的大小,并不是限制。在Linux 2.6.8及以后的版本中,这个参数被忽略,但是为了代码的可移植性,通常还是传递一个合适的大小值,比如 1。
  • flags(对于epoll_create1):这是一个位掩码,用于修改epoll实例的行为。常用的标志有EPOLL_CLOEXEC(当执行exec()函数时,关闭文件描述符)。

返回值:

  • 成功时返回一个非负整数,即新的epoll文件描述符。
  • 失败时返回-1,并设置全局变量errno以指示错误。

作用

创建一个新的epoll实例,并返回一个文件描述符,用于后续通过epoll_ctl()添加、修改或删除要监视的文件描述符,以及通过epoll_wait()等待文件描述符上的事件。

😉 2. epoll_ctl()

函数原型

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数解释

  • epfd:由epoll_create()epoll_create1()返回的文件描述符。
  • op:操作码,可以是以下三种之一:
    • EPOLL_CTL_ADD:注册新的文件描述符到epfd
    • EPOLL_CTL_MOD:修改已经注册的文件描述符的监听事件。
    • EPOLL_CTL_DEL:从epfd中注销一个文件描述符。
  • fd:需要添加、修改或删除的文件描述符。
  • event:指向epoll_event结构的指针,描述了要监听的事件和与之关联的数据。

返回值:

  • 成功时返回0。
  • 失败时返回-1,并设置全局变量errno以指示错误。

作用

用于向epoll实例中添加、修改或删除要监视的文件描述符及其相关的事件。

😉 3. epoll_wait()

函数原型

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数解释

  • epfd:由epoll_create()epoll_create1()返回的文件描述符。
  • events:指向epoll_event结构数组的指针,用于存储从内核返回的事件。
  • maxevents:告诉内核这个events数组有多大,这个值不能大于创建epoll_create时的size
  • timeout:等待超时时间(毫秒),-1 表示永远等待。

返回值:

  • 成功时返回发生事件的文件描述符的数量,这些事件被存储在events数组中。
  • 如果在timeout毫秒内没有事件发生,返回0。
  • 失败时返回-1,并设置全局变量errno以指示错误。

作用

等待在epfd上注册的文件描述符上的事件。当这些事件中的任何一个发生时,epoll_wait()将返回,并将所有触发的事件存储在events数组中。

注意:在使用epoll时,还需要了解epoll_event结构体,它描述了注册到epoll实例中的事件和与之关联的数据。这个结构体通常包含两个成员:events(表示要监听的事件类型)和data(用户定义的数据,通常用于在事件触发时识别是哪个文件描述符触发了事件)。

epoll_event结构体

epoll_event 结构体是 epoll 机制中用于注册、修改和接收文件描述符上事件的重要数据结构。它定义在 <sys/epoll.h> 头文件中,并用于 epoll_ctl() 函数中注册感兴趣的事件和 epoll_wait() 函数中接收已触发的事件。

🍚epoll_event 结构体的定义通常如下:

typedef union epoll_data {
    void    *ptr;
    int      fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
};

🍚 结构体成员解释

  1. events

    • 这是一个位掩码,表示你感兴趣的事件类型。常见的事件类型有:
      • EPOLLIN:当相应的文件描述符可读时触发。
      • EPOLLOUT:当相应的文件描述符可写时触发。
      • EPOLLPRI:当相应的文件描述符有优先读取数据可读时触发(不常用)。
      • EPOLLERR:当相应的文件描述符发生错误时触发。
      • EPOLLHUP:当相应的文件描述符被挂起时触发(如 TCP 连接被对方关闭)。
      • EPOLLET:设置文件描述符为边缘触发(Edge Triggered)模式。默认是水平触发(Level Triggered)模式。
      • 以及其他一些不太常用的事件。
  2. data

    • 这是一个联合体,允许用户关联任意类型的数据到事件上。这样,当事件触发时,你可以通过这个联合体来识别是哪个文件描述符触发了事件。
      • ptr:一个指向任意类型数据的指针。
      • fd:一个文件描述符。这通常用于存储与事件关联的文件描述符,但请注意,这与 epoll_event 结构体中的 events 成员中引用的文件描述符不同。
      • u32u64:无符号的 32 位和 64 位整数。你可以使用这些字段来存储自定义的整数数据。

🍚 使用方法

epoll_ctl() 调用中,你会创建一个 epoll_event 结构体实例,并设置其 events 成员为你感兴趣的事件类型,以及 data 成员为你想要关联的数据。然后,你将这个结构体的指针传递给 epoll_ctl()

epoll_wait() 调用中,你会传递一个 epoll_event 结构体数组以及它的大小给该函数。当 epoll_wait() 返回时,它会更新这个数组中的 epoll_event 结构体实例,以反映实际触发的事件。然后,你可以遍历这个数组,检查每个 epoll_event 结构体的 events 成员来确定哪些事件被触发了,并使用 data 成员来获取与事件关联的数据。

epoll示例: 使用epoll监控标准输入

为了使用epoll来监控标准输入(通常是文件描述符0,即stdin),我们可以编写一个简单的程序来演示epoll_create(), epoll_ctl(), 和 epoll_wait() 的使用。以下是一个简单的C程序示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/epoll.h>

#define MAX_EVENTS 10

int main(void) {
    int epfd, nfds;
    struct epoll_event ev, events[MAX_EVENTS];

    // 创建一个 epoll 实例
    epfd = epoll_create1(0);
    if (epfd == -1) {
        perror("epoll_create1");
        exit(EXIT_FAILURE);
    }

    // 配置要监控的事件
    ev.events = EPOLLIN; // 监听可读事件
    ev.data.fd = STDIN_FILENO; // 标准输入的文件描述符

    // 向 epoll 实例添加监控的文件描述符
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) {
        perror("epoll_ctl: add");
        exit(EXIT_FAILURE);
    }

    // 等待事件发生
    for (;;) {
        nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); // 阻塞等待
        if (nfds == -1) {
            perror("epoll_wait");
            break;
        }

        // 遍历所有触发的事件
        for (int n = 0; n < nfds; ++n) {
            if (events[n].data.fd == STDIN_FILENO) {
                // 读取标准输入
                char buffer[1024];
                ssize_t bytes_read = read(STDIN_FILENO, buffer, sizeof(buffer) - 1);
                if (bytes_read > 0) {
                    // 去掉换行符并打印
                    buffer[bytes_read] = '\0';
                    char *newline = strchr(buffer, '\n');
                    if (newline) *newline = '\0';
                    printf("Read from stdin: %s\n", buffer);
                } else if (bytes_read == 0) {
                    printf("EOF reached on stdin\n");
                    break;
                } else {
                    perror("read");
                    break;
                }
            }
        }
    }

    // 关闭 epoll 文件描述符
    close(epfd);

    return 0;
}

在这个程序中,我们首先使用epoll_create1()创建一个epoll实例。然后,我们使用epoll_ctl()添加一个事件来监听标准输入(stdin)的可读事件。在无限循环中,我们使用epoll_wait()等待事件发生。当标准输入上有数据可读时,我们读取这些数据并打印出来。如果读取到文件结束符(EOF),或者读取操作失败,我们退出循环。最后,我们关闭epoll文件描述符并退出程序。
在这里插入图片描述


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长
在这里插入图片描述
在这里插入图片描述

  • 57
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值