高级IO操作

news2024/10/6 2:47:03

高级I/O操作与非阻塞I/O

在操作系统中,I/O(输入/输出)操作是所有实现的基础。本文将探讨阻塞I/O与非阻塞I/O的区别,以及如何使用有限状态机来实现非阻塞I/O,并介绍数据中继的概念。

阻塞I/O与非阻塞I/O

阻塞I/O

阻塞I/O是操作系统中默认的I/O操作方式。在阻塞I/O中,如果系统调用(如read()或write())无法立即执行,进程将被阻塞,直到可以进行I/O操作为止。这意味着,如果一个进程正在等待I/O操作,它将无法进行任何其他操作。

非阻塞I/O

非阻塞I/O允许进程在I/O操作无法立即执行时继续进行其他操作。在非阻塞模式下,如果读操作时设备数据不充足,或写数据时缓冲区空间不足,系统会返回一个EAGAIN错误,告诉进程当前无法进行I/O操作,进程可以稍后再试。

有限状态机编程

有限状态机(Finite State Machine, FSM)是一种用来处理复杂流程的编程模型。它适用于流程结构化的场景,也可以用于处理复杂且非结构化的流程。

有限状态机解决的问题是复杂流程。
简单流程:自然流程是结构化的,按照人类顺序思维解决的问题。
复杂流程:自然流程不是结构化的,比如先前的MultiButton。

实现非阻塞I/O

要在Linux操作系统下实现非阻塞I/O,可以使用O_NONBLOCK标志来设置文件描述符。以下是一个简单的示例,展示如何打开一个文件并以非阻塞方式读取数据。

int fd = open("file.txt", O_RDONLY | O_NONBLOCK);
if (fd < 0) {
    perror("open()");
    exit(1);
}

数据中继原理解析

数据中继是指在两个设备之间进行数据交换的过程。在数据中继中,一个设备作为源设备,另一个设备作为目标设备。数据中继的实现通常涉及两个状态机,一个用于读取数据(源设备)另一个用于写入数据(目标设备)。

在这里插入图片描述

假设打开两个设备,要在两个设备之间进行数据交换(数据中继)
两个设备也有其它数据来源
要实现的功能:
读左然后写右和读右然后写左
要是用阻塞的话左边一直没数据来会卡在读左等待
分成两个任务一个读左然后写右,一个读右然后写左

具体实例
在linux操作系统下实现终端设备界面相互切换。实现读取fd1的数据写入的fd2中,读取fd2的数据写入到fd1当中。
状态机简单示意图如下所示:
在这里插入图片描述

非阻塞IO

简单流程:自然流程是结构化的

复杂流程:自然流程不是结构化的

完成数据中继,就像copy文件的程序

非阻塞操作:

在Linux中,一切皆文件,文件读写操作默认是阻塞的。但是可以通过设置O_NONBLOCK标志将读写操作设置为非阻塞方式。如果读操作时设备数据不足或者写操作时缓冲区空间不足,系统会返回-EAGAIN错误,但不会阻塞线程。

例子

int fd = open("file.txt", O_RDONLY | O_NONBLOCK);
if (fd < 0) {
    perror("open()");
    exit(1);
}

fcntl
fcntl函数的作用是获取和设置文件的访问模式和状态标志。

int fcntl(int fd, int cmd, ... /* arg */);

参数说明:

  • fd:文件描述符。
  • cmd:控制命令,如F_GETFL和F_SETFL。
  • F_GETFL (void):返回文件访问模式和文件状态标志。
  • F_SETFL (int):设置文件状态标志为指定值,忽略文件访问模式和文件创建标志。

在Linux中,F_SETFL命令可以改变O_APPEND、O_ASYNC、O_DIRECT、O_NOATIME和O_NONBLOCK标志,但不能改变O_DSYNC和O_SYNC标志。

relay函数编写

  1. 获取文件原有状态。
  2. 在原有状态基础上添加非阻塞状态。
relay(int fd1, int fd2) {
    int fd1save = fcntl(fd1, F_GETFL); // 获取文件状态
    fcntl(fd1, F_SETFL, fd1save | O_NONBLOCK); // 设置文件为非阻塞状态
    int fd2save = fcntl(fd2, F_GETFL); // 获取文件状态
    fcntl(fd2, F_SETFL, fd2save | O_NONBLOCK); // 设置文件为非阻塞状态
}

定义两个状态机,一个负责从源文件读取数据到目标文件,另一个负责从目标文件读取数据到源文件。

// 状态机状态枚举
enum {
    STATE_R = 1, // 读态
    STATE_W,    // 写态
    STATE_EX,   // 异常终止态
    STATE_T     // 退出态
};

// 状态机结构体
struct fsm_st {
    int state;    // 当前状态
    int sfd;      // 源文件描述符
    int dfd;      // 目标文件描述符
    int len;      // 读取长度
    int pos;      // 位置
    char *errstr; // 报错信息
    char buf[BUFSIZE]; // 缓冲区
};

// 初始化状态机
static void relay(int fd1, int fd2) {
    struct fsm_st fsm12, fsm21;
    int fd1save = fcntl(fd1, F_GETFL);
    fcntl(fd1, F_SETFL, fd1save | O_NONBLOCK);
    int fd2save = fcntl(fd2, F_GETFL);
    fcntl(fd2, F_SETFL, fd2save | O_NONBLOCK);
    fsm12.state = STATE_R;
    fsm12.sfd = fd1;
    fsm12.dfd = fd2;
    fsm21.state = STATE_R;
    fsm21.sfd = fd2;
    fsm21.dfd = fd1;
    // ...
}

// 当不是退出态时,推动状态机
while (fsm12.state != STATE_T && fsm21.state != STATE_T) {
    fsm_driver(&fsm12);
    fsm_driver(&fsm21);
}

// 恢复起始默认状态
fcntl(fd1, F_SETFL, fd1save);
fcntl(fd2, F_SETFL, fd2save);
}

fsm_driver推动状态机
-----------------

```c
static void fsm_driver(struct fsm_st *fsm) {
    int ret;

    switch (fsm->state) {
        case STATE_R:
            fsm->len = read(fsm->sfd, fsm->buf, BUFSIZE);
            if (fsm->len == 0) {
                fsm->state = STATE_T;
            } else if (fsm->len < 0) {
                fsm->errstr = "read()";
                fsm->state = STATE_EX;
            } else {
                fsm->pos = 0;
                fsm->state = STATE_W;
            }
            break;

        case STATE_W:
            ret = write(fsm->dfd, fsm->buf + fsm->pos, fsm->len);
            if (ret < 0) {
                fsm->errstr = "write()";
                fsm->state = STATE_EX;
            } else {
                fsm->pos += ret;
                fsm->len -= ret;
                if (fsm->len == 0) {
                    fsm->state = STATE_R;
                } else {
                    fsm->state = STATE_W;
                }
            }
            break;

        case STATE_EX:
            perror(fsm->errstr);
            fsm->state = STATE_T;
            break;

        case STATE_T:
            // 执行一些清理工作
            break;

        default:
            abort();
    }
}

完整代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#define TTY1    "/dev/tty11"
#define TTY2    "/dev/tty12"

#define BUFSIZE 1024

/*状态机状态枚举类型*/
enum
{
    STATE_R=1,      //读态
    STATE_W,     //写态
    STATE_Ex,     //异常终止态
    STATE_T         //退出态
};

/*状态机结构体*/
struct fsm_st
{
    int state; //当前状态机的状态
    
    int sfd;//源文件描述符
    
    int dfd;//目标文件描述符
    
    int len;//读取长度
    
    int pos;//位置
   
    char * errstr; //报错信息
   
    char buf[BUFSIZE]; //buf缓冲区
};

/**************推动状态机****************/
static void fsm_driver(struct fsm_st*fsm)
{
    int ret;

    switch (fsm->state)
    {
    /*状态机读取*/
    case STATE_R:
        /*读取到的源fd存储到buf中*/
        fsm->len = read(fsm->sfd,fsm->buf,BUFSIZE);
        /*如果读取0字节,退出状态机*/
        if (fsm->len == 0)
        fsm->state = STATE_T;
        /*如果读取<0字节,进行状态判断*/
        else if (fsm->len < 0)
        {
           /*如果读取<0字节,二次判断*/
            if (errno == EAGAIN)
                fsm->state =STATE_R;
            else
            {
                /*宕机退出*/
                fsm->errstr = "read()";
                fsm->state =STATE_Ex;
            }
        }
        else
        /*都没有报错,说明读取正常,则开始状态机写入*/
        {
        /*******初始化写入的位置为0***************/
            fsm->pos = 0;
            fsm->state =STATE_W;
        }
        break;

   /*状态机写入*/
    case STATE_W:
        /*写入读取到的数据len*/
        ret = write(fsm->dfd,fsm->buf+fsm->pos,fsm->len);
        /*写入读取到的数据<0*/
        if(ret < 0)
        {
            /*假的错误*/
            if (errno == EAGAIN)
                /*再次进入写入*/
                fsm->state = STATE_W;
            else
            /*真正读取错误*/
            {
                /*读取错误*/
                fsm->errstr = "read()";
                /*宕机退出*/
                fsm->state =STATE_Ex;
            }
        }
        else
      /***************坚持写够len个字节数***************/
        {
           /*******从pos的位置继续向下写入字节***************/
            fsm->pos += ret;
            fsm->len -= ret;
            /*如果写入完成*/
            if(fsm->len == 0)
                /*返回读态*/
                fsm->state = STATE_R;
            /*否则返回写态,继续写够len个字节*/
            else
                fsm->state = STATE_W;
        }
        break;

        /*宕机退出*/
    case STATE_Ex:
        perror(fsm->errstr);
        fsm->state = STATE_T;
        break;
        /*完整退出*/
    case STATE_T:
        /*do sth*/
        break;
    default:
        /*如果都不是以上任意一个状态,发送异常*/
        abort();
        break;
    }

}

static void relay(int fd1,int fd2)
{
    struct fsm_st fsm12,fsm21;  //定义结构体读左写右,读右写左
    
    /*首先保证文件是以非阻塞实现的*/
    int fd1_save = fcntl(fd1,F_GETFL);    //获取文件状态
    fcntl(fd1,F_SETFL,fd1_save|O_NONBLOCK);//追加文件描述符的状态为非阻塞
    
    int fd2_save = fcntl(fd2,F_GETFL);  //获取文件状态
    fcntl(fd2,F_SETFL,fd1_save|O_NONBLOCK); //追加文件描述符的状态为非阻塞
    /******************************/

    /*初始化状态机*/
    fsm12.state = STATE_R;
    fsm12.sfd = fd1;
    fsm12.dfd = fd2;
    
    fsm21.state = STATE_R;
    fsm21.sfd = fd2;
    fsm21.dfd = fd1;
    /**************/
    
    /*当不是退出态,推动状态机*/
    while (fsm12.state != STATE_T ||fsm21.state != STATE_T )
    {
        fsm_driver(&fsm12);
        fsm_driver(&fsm21);
    }
    /************************/

    /*恢复起始默认状态*/
    fcntl(fd1,F_SETFL,fd1_save);
    fcntl(fd2,F_SETFL,fd2_save);
    /******************/
}

int main()
{
    int fd1,fd2;
    /*模拟用户打开设备*/
    fd1 = open(TTY1,O_RDWR);
    if(fd1 < 0)
    {
        perror("open()");
        exit(1);
    }
    
    write(fd1,"TTY1\n",5);
    /*模拟用户打开设备,以非阻塞方式打开设备*/
    fd2 = open(TTY2,O_RDWR|O_NONBLOCK);
    if(fd2 < 0)
    {
        perror("open()");
        exit(1);
    }
    
    write(fd2,"TTY2\n",5);
    /*中继引擎函数*/
    relay(fd1,fd2);

    close(fd2);
    close(fd1);

    exit(0);
}

测试:
要用root用户执行
ctl+atl+F11和ctl+atl+F12来回切换观察
ctl+atl+F1回到图像界面

IO多路转接


解决IO密集型任务中忙等的问题,监视多个文件描述符的行为,当当前文件描述符发生了我们感兴趣的行为时,才去做后续操作。常见的IO多路转接函数有select()poll()epoll()等。

select()

可以实现安全的休眠(替代sleep)前面都给NULL,只设置最后的timeout

  • select() 兼容性好 但设计有缺陷 以事件为单位组织文件描述符

  • nfds的类型问题

  • 参数没有const修饰 也就是函数会修改 fdset 任务和结果放在一起

  • 监视的事件太过单一 读 写 异常(异常的种类非常多)

  • 原函数:

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);
  • 参数:
  • fd_set 文件描述符集合
    • void FD_CLR(int fd, fd_set *set); 在指定集合中删除指定文件描述符
    • int FD_ISSET(int fd, fd_set *set); 判断一个文件描述符是否在集合
    • void FD_SET(int fd, fd_set *set); 添加文件描述符到集合
    • void FD_ZERO(fd_set *set); 集合置0
  • nfds 要监视文件描述符里最大的再加1
  • readfds 所关心的可以发生读的状态的集合(当里面有文件描述符发生读就返回)
  • writerfds 所关心的可以发生写的状态的集合(当里面有文件描述符发生写就返回)
  • exceptfds 所关心异常的情况
  • timerout 超时设置(不设置会发生忙等)
    • struct timeval {
      time_t tv_sec; /* seconds /秒
      suseconds_t tv_usec; / microseconds */微秒
      };
  • 返回值 返回发生行为的文件描述符的个数,发生行为的文件描述符会覆盖回原来的集合
  • 会有假错,因为是阻塞的会被信号打断

忙等与非阻塞IO

  • 忙等:会消耗CPU资源,当没有数据可读时会一直消耗CPU。
  • 非阻塞IO:不会消耗CPU,当没有数据可读时会立即返回。

IO多路转接示例

#include <sys/select.h>

static int max(int a, int b) {
    if (a > b) return a;
    return b;
}

static void relay(int fd1, int fd2) {
    int old_fd1, old_fd2;
    fd_set rset, wset;

    old_fd1 = fcntl(fd1, F_GETFL);
    fcntl(fd1, F_SETFL, old_fd1 | O_NONBLOCK);

    old_fd2 = fcntl(fd2, F_GETFL);
    fcntl(fd2, F_SETFL, old_fd2 | O_NONBLOCK);

    struct fsm_st fsm12, fsm21; // 读左写右 读右写左

    fsm12.state = STATE_R;
    fsm12.sfd = fd1;
    fsm12.dfd = fd2;

    fsm21.state = STATE_R;
    fsm21.sfd = fd2;
    fsm21.dfd = fd1;

    while (fsm12.state != STATE_T || fsm21.state != STATE_T) {
        // 布置监视任务
        FD_ZERO(&rset);
        FD_ZERO(&wset);

        if (fsm12.state == STATE_R)
            FD_SET(fsm12.sfd, &rset);
        if (fsm12.state == STATE_W)
            FD_SET(fsm12.sfd, &wset);

        if (fsm21.state == STATE_R)
            FD_SET(fsm21.sfd, &rset);
        if (fsm21.state == STATE_W)
            FD_SET(fsm21.sfd, &wset);

        // 监视
        struct timeval ts;
        ts.tv_sec = 0;
        ts.tv_usec = 2;
        int maxfd = max(fd1, fd2);

        if (fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO) {
            if (select(maxfd + 1, &rset, &wset, NULL, &ts) < 0) {
                if (errno == EINTR)
                    continue;
                perror("select()");
                exit(1);
            }
        }

        // 查看监视结果
        if (FD_ISSET(fd1, &rset) || FD_ISSET(fd2, &wset) || fsm12.state > STATE_AUTO) {
            fsm_driver(&fsm12);
        }
        if (FD_ISSET(fd2, &rset) || FD_ISSET(fd1, &wset) || fsm21.state > STATE_AUTO) {
            fsm_driver(&fsm21);
        }
    }

    // 恢复原来的文件描述符状态
    fcntl(fd1, F_SETFL, old_fd1);
    fcntl(fd2, F_SETFL, old_fd2);
}

enum {
    STATE_R = 1,
    STATE_W,
    STATE_AUTO,
    STATE_Ex,
    STATE_T
};

在这个例子中,我们使用了select()函数来监视两个文件描述符fd1fd2。当其中一个文件描述符准备好读或写时,相应的状态机fsm12fsm21就会被推进。这里增加了一个STATE_AUTO状态,用于在EXT状态之外的其他状态时,触发读写操作。这样可以避免在异常或退出状态时进行不必要的读写操作。

poll()


poll()函数用于等待文件描述符上的事件。它以文件描述符为单位组织事件,相比select()更加可移植。

原函数

#include <poll.h>

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

作用

  • fds: 指向struct pollfd数组的指针,用于指定要监视的文件描述符及其对应的事件。
  • nfds: 要监视的文件描述符数量。
  • timeout: 超时时间,单位为毫秒。-1表示阻塞直到有事件发生,0表示非阻塞立即返回,大于0表示等待指定时间。

参数

  • struct pollfd: 用于指定文件描述符和事件。
    • fd: 文件描述符。
    • events: 所关心的事件,如POLLIN(可读)、POLLOUT(可写)等。
    • revents: 发生的事件。

返回值

  • 返回就绪文件描述符的个数。
例子
#include <poll.h>

static void relay(int fd1, int fd2) {
    int old_fd1, old_fd2;
    struct fsm_st fsm12, fsm21; // 读左写右 读右写左
    struct pollfd pfd[2];

    old_fd1 = fcntl(fd1, F_GETFL);
    fcntl(fd1, F_SETFL, old_fd1 | O_NONBLOCK);

    old_fd2 = fcntl(fd2, F_GETFL);
    fcntl(fd2, F_SETFL, old_fd2 | O_NONBLOCK);

    fsm12.state = STATE_R;
    fsm12.sfd = fd1;
    fsm12.dfd = fd2;

    fsm21.state = STATE_R;
    fsm21.sfd = fd2;
    fsm21.dfd = fd1;

    pfd[0].fd = fd1;
    pfd[1].fd = fd2;

    while (fsm12.state != STATE_T || fsm21.state != STATE_T) {
        // 布置监视任务
        pfd[0].events = 0; // 事件清零
        if (fsm12.state == STATE_R) // 1可读
            pfd[0].events |= POLLIN;
        if (fsm21.state == STATE_W) // 1可写
            pfd[0].events |= POLLOUT;

        pfd[1].events = 0; // 事件清零
        if (fsm21.state == STATE_R) // 2可读
            pfd[1].events |= POLLIN;
        if (fsm12.state == STATE_W) // 2可写
            pfd[1].events |= POLLOUT;

        // 监视
        if (fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO) {
            while (poll(pfd, 2, -1) < 0) {
                if (errno == EINTR)
                    continue;
                perror("poll()");
                exit(1);
            }
        }

        // 查看监视结果
        if (pfd[0].revents & POLLIN || pfd[1].revents & POLLOUT || fsm12.state > STATE_AUTO) {
            fsm_driver(&fsm12);
        }
        if (pfd[1].revents & POLLIN || pfd[0].revents & POLLOUT || fsm21.state > STATE_AUTO) {
            fsm_driver(&fsm21);
        }
    }

    // 恢复原来的文件描述符状态
    fcntl(fd1, F_SETFL, old_fd1);
    fcntl(fd2, F_SETFL, old_fd2);
}

在这个例子中,我们使用了poll()函数来监视两个文件描述符fd1fd2poll()通过struct pollfd结构体来指定要监视的文件描述符和对应的事件。当文件描述符上发生的事件匹配我们设置的事件时,poll()会返回就绪文件描述符的个数。

epoll

epoll 是 Linux 特有的 I/O 多路复用机制,它是对 poll 机制的增强和优化,因此不具有跨平台性。

epoll_create()

原函数:

#include <sys/epoll.h>

int epoll_create(int size);

作用:创建一个 epoll 实例。

参数:size 参数可以随意给一个大于 0 的数,用于指定 epoll 实例的最大监听数。

返回值:返回新创建的 epoll 实例的文件描述符。

epoll_ctl()

原函数:

#include <sys/epoll.h>

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

作用:控制 epoll 实例,对指定 epoll 实例 epfd 中的文件描述符 fd 执行操作 op(添加、修改、删除)。

参数:

  • epfd:epoll 实例的文件描述符。
  • op:操作类型,如 EPOLL_CTL_ADDEPOLL_CTL_MODEPOLL_CTL_DEL
  • fd:要操作的文件描述符。
  • event:指定的事件,包括 eventsdata 两个字段。

epoll_event 结构

  • events:所需监听的事件类型,如 EPOLLIN(可读)、EPOLLOUT(可写)等。
  • data:用户数据,可以是文件描述符,也可以是与文件描述符相关联的其他数据。
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 */
};

epoll_wait()

原函数:

#include <sys/epoll.h>

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

作用:等待文件描述符上的事件。

参数:

  • epfd:epoll 实例的文件描述符。
  • events:用于存放等待的事件。
  • maxevents:最多可以返回的事件数。
  • timeout:超时时间,-1 表示阻塞直到有事件发生,0 表示非阻塞立即返回,正数表示等待指定时间。

返回值:返回就绪的事件数。

示例
#include <sys/epoll.h>

static void relay(int fd1, int fd2) {
    int old_fd1, old_fd2;
    struct fsm_st fsm12, fsm21; // 读左写右 读右写左
    struct epoll_event ev;

    old_fd1 = fcntl(fd1, F_GETFL);
    fcntl(fd1, F_SETFL, old_fd1 | O_NONBLOCK);

    old_fd2 = fcntl(fd2, F_GETFL);
    fcntl(fd2, F_SETFL, old_fd2 | O_NONBLOCK);

    fsm12.state = STATE_R;
    fsm12.sfd = fd1;
    fsm12.dfd = fd2;

    fsm21.state = STATE_R;
    fsm21.sfd = fd2;
    fsm21.dfd = fd1;

    int epfd = epoll_create(2);
    if (epfd < 0) {
        perror("epoll_create()");
        exit(1);
    }
    ev.data.fd = fd1;
    ev.events = 0;
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &ev);
    ev.data.fd = fd2;
    ev.events = 0;
    epoll_ctl(epfd, EPOLL_CTL_ADD, fd2, &ev);

    while (fsm12.state != STATE_T || fsm21.state != STATE_T) {
        ev.data.fd = fd1;
        ev.events = 0;
        // 布置监视任务
        if (fsm12.state == STATE_R) // 1可读
            ev.events |= EPOLLIN;
        if (fsm21.state == STATE_W) // 1可写
            ev.events |= EPOLLOUT;
        epoll_ctl(epfd, EPOLL_CTL_MOD, fd1, &ev);

        ev.data.fd = fd2;
        ev.events = 0;
        if (fsm21.state == STATE_R) // 2可读
            ev.events |= EPOLLIN;
        if (fsm12.state == STATE_W) // 2可写
            ev.events |= EPOLLOUT;
        epoll_ctl(epfd, EPOLL_CTL_MOD, fd2, &ev);

        // 监视
        if (fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO) {
            while (epoll_wait(epfd, &ev, 1, -1) < 0) {
                if (errno == EINTR)
                    continue;
                perror("poll()");
                exit(1);
            }
        }

        // 查看监视结果
        if (ev.data.fd == fd1 && ev.events & EPOLLIN ||
            ev.data.fd == fd2 && ev.events & EPOLLOUT ||
            fsm12.state > STATE_AUTO) {
            fsm_driver(&fsm12);
        }
        if (ev.data.fd == fd2 && ev.events & EPOLLIN ||
            ev.data.fd == fd1 && ev.events & EPOLLOUT ||
            fsm21.state > STATE_AUTO) {
            fsm_driver(&fsm21);
        }
    }

    // 恢复原来的文件描述符状态
    fcntl(fd1, F_SETFL, old_fd1);
    fcntl(fd2, F_SETFL, old_fd2);

    close(epfd);
}

在这个示例中,我们使用 epoll 来监视两个文件描述符 fd1 和 fd2。我们首先使用 epoll_create 创建一个 epoll 实例,然后使用 epoll_ctl 添加这两个文件描述符。

在主循环中,我们根据状态机的当前状态来更新 epoll 实例中文件描述符的事件监听。然后,我们使用 epoll_wait 来等待文件描述符上的事件。当有事件发生时,我们根据事件更新状态机,并处理相应的读写操作。

最后,当状态机达到退出状态时,我们关闭 epoll 实例,并恢复文件描述符到非阻塞状态。

这个例子展示了如何使用 epoll 来实现非阻塞的 I/O 操作,适用于处理多个文件描述符的场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1853053.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Day7 —— 大数据技术之Hive

Hive快速入门系列 Hive的概述什么是Hive&#xff1f;使用Hive的原因 Hive架构Hive安装Hive配置文件修改启动Hive以命令行方式启动&#xff08;在$HIVE_HOME/bin目录下&#xff09;以JDBC连接启动&#xff08;beeline方式连接&#xff09; Hive基本操作Hive数据库操作Hive表操作…

小柴带你学AutoSar系列一、基础知识篇(6)车规级MCU入门RH850

flechazohttps://www.zhihu.com/people/jiu_sheng 小柴带你学AutoSar总目录https://blog.csdn.net/qiansh

已解决javax.swing.text.BadLocationException异常的正确解决方法,亲测有效!!!

已解决javax.swing.text.BadLocationException异常的正确解决方法&#xff0c;亲测有效&#xff01;&#xff01;&#xff01; 目录 问题分析 出现问题的场景 报错原因 解决思路 解决方法 分析错误日志 检查索引合法性 捕获并处理异常 优化代码逻辑 确保线程安全操作…

使用AGG里面的clip_box函数裁剪画布, 绘制裁剪后的图形

// 矩形裁剪图片, 透明 void agg_testImageClipbox_rgba32(unsigned char* buffer, unsigned int width, unsigned int height) {// 创建渲染缓冲区 agg::rendering_buffer rbuf;// BMP是上下倒置的&#xff0c;为了和GDI习惯相同&#xff0c;最后一个参数是负值。rbuf.attach…

网吧管理系统带万字文档java项目基于springboot+vue的网吧管理系统java课程设计java毕业设计

文章目录 网吧管理系统一、项目演示二、项目介绍三、万字项目文档四、部分功能截图五、部分代码展示六、底部获取项目源码带万字文档&#xff08;9.9&#xffe5;带走&#xff09; 网吧管理系统 一、项目演示 网吧管理系统 二、项目介绍 基于springbootvue的网吧管理系统 系…

ENVI+SARscape操作:将tif DEM数据转换为SARscape可读写的格式

1、导入 tif DEM 数据 将下载的 tif DEM 数据&#xff08;ALOS DEM或SRTM DEM&#xff09;&#xff0c;直接将要导入的 tif DEM 数据拖入ENVI软件中&#xff0c;即可自动打开&#xff1b; 2、数据拼接 在 Toolbox 中&#xff0c;点击 Mosaicking->Seamless Mosaic&#xf…

任务调度SpringTask入门

任务调度简介 1.1什么是任务调度 在企业级应用中&#xff0c;经常会制定一些“计划任务”&#xff0c;即在某个时间点做某件事情&#xff0c;核心是以时间为关注点&#xff0c;即在一个特定的时间点&#xff0c;系统执行指定的一个操作。常见的任务调度框架有Quartz和SpringTa…

如何在Android中实现多线程与线程池?

目录 一、Android介绍二、什么是多线程三、什么是线程池四、如何在Android中实现多线程与线程池 一、Android介绍 Android是一种基于Linux内核的开源操作系统&#xff0c;由Google公司领导开发。它最初于2007年发布&#xff0c;旨在为移动设备提供一种统一、可扩展的操作系统。…

嵌入式实验---实验六 I2C传输实验

一、实验目的 1、掌握STM32F103I2C传输程序设计流程&#xff1b; 2、熟悉STM32固件库的基本使用。 二、实验原理 1、本案例利用I/O端口通过KEY01按键来控制STM32F103R6向24C02写入“hello”&#xff0c;通过另外一个按键KEY02来控制STM32F103R6从24C02读取“hello”&#x…

机器学习:线性回归模型学习路线

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…

乳腺癌患者的生存分析:从传统方法到DeepSurv

一、引言 乳腺癌作为全球女性中最常见的癌症之一&#xff0c;其早期诊断和治疗对于提高患者生存率至关重要。随着医学技术和数据分析方法的不断发展&#xff0c;乳腺癌患者的生存分析逐渐成为研究的热点。通过生存分析&#xff0c;我们可以更好地理解疾病进展、预测患者预后&am…

【Oracle】实验二 体系结构、存储结构与各类参数

【实验目的】 理解Oracle体系结构了解初始化参数文件以及初始化参数的含义掌握查看三类数据字典视图和动态性能视图的方法 【实验内容】 如何查看初始化参数&#xff1f;有哪几种方法&#xff1f;初始化参数文件有几种&#xff1f;默认的保存位置在哪里&#xff1f;在SQL*Pl…

L57---112.路径总和(广搜)---Java版

1.题目描述 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径&#xff0c;这条路径上所有节点值相加等于目标和 targetSum 。如果存在&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。叶子节点 是指…

DVWA 靶场 Weak Session IDs 通关解析

前言 DVWA代表Damn Vulnerable Web Application&#xff0c;是一个用于学习和练习Web应用程序漏洞的开源漏洞应用程序。它被设计成一个易于安装和配置的漏洞应用程序&#xff0c;旨在帮助安全专业人员和爱好者了解和熟悉不同类型的Web应用程序漏洞。 DVWA提供了一系列的漏洞场…

https://curl.trillworks.com不能用的解决方法

gitee源码:https://gitee.com/Project0ne/curlconverter 首先打开上面的链接 然后下载文件 下载文件到本地 然后安装node.js(Node.js official website.)不会的自行百度,这里不做过多赘述。 在curlconverter文件夹下面打开终端(在文件夹下面右键-在终端打开) 输入 npm…

文本分析-中文停用词集合(结合百度停用词表、哈工大停用词表、四川大学机器智能实验室停用词库、中文停用词表等)

将这4个停用词表进行组合并去重。 # _*_coding:utf-8 _*_import os #得到当前文件路径 current_pathos.getcwd() #获取文件列表 list_fileos.listdir(current_path) #用来存储停用词 temp_stopwords[] for file in list_file:file_tailfile.split(.)[-1]#只要txt格式的文件if f…

Kylin系列:架构和高级功能详解

目录 一、Kylin的架构 1.1 总体架构概述 1.2 数据源 1.3 元数据存储 1.4 构建引擎 1.5 存储引擎 1.6 查询引擎 1.7 用户接口 二、Kylin的高级功能 2.1 多维立方体(Cube) 2.1.1 Cube的定义 2.1.2 Cube的构建 2.2 查询优化 2.3 数据模型和星型模式 2.3.1 数据模…

我做了个Hexo博客

最近花了两个周末的时间边学变做Hexo博客&#xff0c;最终成品地址如下&#xff1a; https://blog.mybatis.io 下面先说说做博客的经过&#xff0c;想做Hexo博客一开始是因为看到了 hexo-theme-icarus 主题&#xff0c;这个主题样式如下&#xff1a; 首页 内容页 这个主题是…

字节豆包全新图像Tokenizer:生成图像最低只需32个token,最高提速410倍

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 更多资源欢迎关注 在生成式模型的迅速发展中&#xff0c;Image Tokenization 扮演着一个很重要的角色&#xff0c;例如Diffusion依赖的VAE或者是Transformer依赖的VQGAN。这些Tokenizers会将图像编码至一个更为紧凑的隐…

AI味太重怎么办?1个超简单的方法就能解决

我们知道随着GPT技术的迅速发展&#xff0c;解决了我们大部分写作的难题。但是很多小伙伴想必都会遇到同样的问题&#xff0c;就是写出来的文章太正式-我们叫这“AI味”。 这AI味让人感觉内容虽然条理清楚&#xff0c;但就是缺了点人情味&#xff0c;读起来不够亲切。 其实&a…