IO多路转接 select、poll、epoll(Linux C)

    科技2025-09-17  36

    IO多路转接

    文章目录

    IO多路转接selectpollepollepoll使用实例

    用来监视文件描述符号。

    函数:

    select( ); 移植性好,太古老了。以事件为单位组织文件描述符的监视

    poll( ); 以文件描述符为单位组织事件。

    epoll( ); Linux方言,对于poll的优化。

    select

    SYNOPSIS /* According to POSIX.1-2001, POSIX.1-2008 */ #include <sys/select.h> /* According to earlier standards */ #include <sys/time.h> #include <sys/types.h> #include <unistd.h> /* nfds 所要监视的文件描述符最大的+1 不是数量!!! readfds 希望关心的读情况文件描述符集合 writefds 希望关心的写情况文件描述符集合 exceptfds 希望关心的异常情况文件描述符集合 timeout 超时设置 不设置的话为死等 */ int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); 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); //清空 #include <sys/select.h> int pselect(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timespec *timeout, const sigset_t *sigmask); RETURN VALUE On success, select() and pselect() return the number of file d escriptors contained in the three returned descriptor sets (that is, the total number of bits that are set in readfds, writefds, exceptfds) which may be zero if the timeout expires before anything interesting happens. On error, -1 is returned, and errno is set to indicate the error; the file descriptor sets are unmodified, and timeout becomes undefined.

    select 问题:监视文件描述符的集合和返回结果的集合为同一个变量,监视现场和监视结果放在同一个位置,对于大量多次次循环操作需要重复布置大量监视现场;nfds可能溢出。以事件为单位组织文件描述符,监视种类过于单一。

    poll

    NAME poll, ppoll - wait for some event on a file descriptor SYNOPSIS #include <poll.h> /* fds 文件描述符结构体数组起点地址 nfds 文件描述符结构体的长度!!! timeout 超时设置ms 0 非阻塞 -1 阻塞 */ int poll(struct pollfd *fds, nfds_t nfds, int timeout); //return 有多少个事件发生 失败 -1 并设置erron #define _GNU_SOURCE /* See feature_test_macros(7) */ #include <signal.h> #include <poll.h> int ppoll(struct pollfd *fds, nfds_t nfds, const struct timespec *tmo_p, const sigset_t *sigmask); DESCRIPTION poll() performs a similar task to select(2): it waits for one of a set of file descriptors to become ready to perform I/O. The set of file descriptors to be monitored is specified in the fds argument, which is an array of structures of the following form: struct pollfd { int fd; /* file descriptor */ short events; /* requested events */ short revents; /* returned events */ }; // events revents 的选项 以下都是位图 POLLIN There is data to read. POLLPRI There is some exceptional condition on the file descriptor. Possibilities include: * There is out-of-band data on a TCP socket (see tcp(7)). * A pseudoterminal master in packet mode has seen a state change on the slave (see ioctl_tty(2)). * A cgroup.events file has been modified (see cgroups(7)). POLLOUT Writing is now possible, though a write larger that the available space in a socket or pipe will still block (unless O_NONBLOCK is set). POLLRDHUP (since Linux 2.6.17) Stream socket peer closed connection, or shut down writing half of connection. The _GNU_SOURCE feature test macro must be defined (before including any header files) in order to obtain this definition. POLLERR Error condition (only returned in revents; ignored in events). This bit is also set for a file descriptor referring to the write end of a pipe when the read end has been closed. POLLHUP Hang up (only returned in revents; ignored in events). Note that when reading from a channel such as a pipe or a stream socket, this event merely indicates that the peer closed its end of the channel. Subsequent reads from the channel will return 0 (end of file) only after all outstanding data in the channel has been consumed. POLLNVAL Invalid request: fd not open (only returned in revents; ignored in events). When compiling with _XOPEN_SOURCE defined, one also has the following, which convey no further information beyond the bits listed above: POLLRDNORM Equivalent to POLLIN. POLLRDBAND Priority band data can be read (generally unused on Linux).

    epoll

    NAME epoll - I/O event notification facility SYNOPSIS #include <sys/epoll.h> DESCRIPTION The epoll API performs a similar task to poll(2): monitoring multiple file descriptors to see if I/O is possible on any of them. The epoll API can be used either as an edge-triggered or a level-triggered interface and scales well to large numbers of watched file descriptors. The following system calls are provided to create and manage an epoll instance: * epoll_create(2) creates a new epoll instance and returns a file descriptor referring to that instance. (The more recent epoll_create1(2) extends the functionality of epoll_create(2).) * Interest in particular file descriptors is then registered via epoll_ctl(2). The set of file descriptors currently registered on an epoll instance is sometimes called an epoll set. * epoll_wait(2) waits for I/O events, blocking the calling thread if no events are currently available.

    epoll_create

    NAME epoll_create, epoll_create1 - open an epoll file descriptor SYNOPSIS #include <sys/epoll.h> /* size 以size宽度处理?随便给个正数? return 成功返回文件描述符 失败返回-1 */ int epoll_create(int size); int epoll_create1(int flags); DESCRIPTION epoll_create() creates a new epoll(7) instance. Since Linux 2.6.8, the size argument is ignored, but must be greater than zero; see NOTES below. epoll_create() returns a file descriptor referring to the new epoll instance. This file descriptor is used for all the subsequent calls to the epoll interface. When no longer required, the file descriptor returned by epoll_create() should be closed by using close(2). When all file descriptors referring to an epoll instance have been closed, the kernel destroys the instance and releases the associated resources for reuse.

    epoll_ctl

    NAME epoll_ctl - control interface for an epoll file descriptor SYNOPSIS #include <sys/epoll.h> /* epfd epoll实例id op 操作 指定动作 fd opt操作文件描述符 指定对象 event 事件 指定事件 */ int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //opt 的行为 EPOLL_CTL_ADD Register the target file descriptor fd on the epoll instance referred to by the file descriptor epfd and associate the event event with the internal file linked to fd. EPOLL_CTL_MOD Change the event event associated with the target file descriptor fd. EPOLL_CTL_DEL Remove (deregister) the target file descriptor fd from the epoll instance referred to by epfd. The event is ignored and can be NULL (but see BUGS below).

    epoll_wait

    NAME epoll_wait, epoll_pwait - wait for an I/O event on an epoll file descriptor SYNOPSIS #include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); int epoll_pwait(int epfd, struct epoll_event *events, int maxevents, int timeout, const sigset_t *sigmask);

    epoll使用实例

    /****************************************************************************** * @file relay.c * @brief 非阻塞有限状态机数据中继简单实现, * 可以通过状态机实现两个终端的数据同步 * 使用epoll监视文件名描述符行为 解决忙等问题 * @author wangs7__ * @date 2020/10/08 * *******************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <math.h> #include <sys/epoll.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #define TTY1 "/dev/tty11" //设备1 #define TTY2 "/dev/tty12" //设备2 #define BUFSIZE 1024 //缓冲区大小 //状态图 enum{ STATE_R = 1, //写状态 STATE_W , //读状态 STATE_AUTO, STATE_Ex, //出错状态 STATE_T //退出态 }; //状态机结构体 struct rel_fsm_st { int state; //状态描述变量 int sfd; //源文件描述符 int dfd; //目标文件描述符 int len; //读取的字节数 int pos; //写指针 char buf[BUFSIZE]; //读写缓冲区 char *errstr; //报错提示 }; /** * @name fsm_driver * @brief 状态机驱动函数,推动状态机运转 * @param fsm 状态机结构体指针 * @return void */ static void fsm_driver(struct rel_fsm_st *fsm){ int ret; //写函数返回值 //开关选择语句 状态机分支 switch (fsm->state) { //R态------------------------------------------------------------------- case STATE_R: fsm->len = read(fsm->sfd, fsm->buf, BUFSIZE);//从源文件读取字符 if(fsm->len == 0){ //读取文件为空 转T态 fsm->state = STATE_T; } else if (fsm->len < 0){ //读取失败 if(errno == EAGAIN){ //假错 保持R态 fsm->state = STATE_R; } else{ //真错 转Ex态 fsm->errstr = "read()"; fsm->state = STATE_Ex; } } else{ //读成功 转W态 fsm->pos = 0; //写指针初始化 fsm->state = STATE_W; } break; //W态------------------------------------------------------------------- case STATE_W: ret = write(fsm->dfd, fsm->buf+fsm->pos, fsm->len);//从缓冲区向目标文件写入 if(ret < 0){ //写错误 if(errno == EAGAIN){ //假错 保持写 fsm->state = STATE_W; } else{ //真错 转Ex态 fsm->errstr = "write()"; fsm->state = STATE_Ex; } } else{ //写成功 fsm->len -= ret; //写剩余长度 fsm->pos += ret; //写指针位置更新 if(fsm->len == 0){ //写完 转读态 fsm->state = STATE_R; } else{ //未写完 保持写 fsm->state = STATE_W; } } break; //Ex态------------------------------------------------------------------ case STATE_Ex: perror(fsm->errstr); //报错提示 fsm->state = STATE_T; //转T态 break; //T态------------------------------------------------------------------- case STATE_T: /* do something */ break; //--------------------------------------------------------------------- default: abort(); break; } } /** * @name relay * @brief 数据中继函数 * @param fd1 设备1文件描述符 * @param fd2 设备2文件描述符 * @return void */ void relay(int fd1, int fd2){ int fd1_save, fd2_save; //原始文件打开状态 struct rel_fsm_st fsm12, fsm21; //1->>2状态机 和 2->1状态机 int epfd; struct epoll_event ev; //添加非阻塞打开方式 fd1_save = fcntl(fd1, F_GETFL); fcntl(fd1, F_SETFL, fd1_save | O_NONBLOCK); fd2_save = fcntl(fd2, F_GETFL); fcntl(fd2, F_SETFL, fd2_save | O_NONBLOCK); //初始化状态机 fsm12.state = STATE_R; fsm12.sfd = fd1; fsm12.dfd = fd2; fsm21.state = STATE_R; fsm21.sfd = fd2; fsm21.dfd = fd1; epfd = epoll_create(10); ev.events = 0; ev.data.fd = fd1; epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &ev); ev.events = 0; ev.data.fd = fd2; epoll_ctl(epfd, EPOLL_CTL_ADD, fd2, &ev); //状态机运转 while (fsm12.state != STATE_T ||fsm21.state != STATE_T){ // 布置监视任务 ev.events = 0; ev.data.fd = fd1; if(fsm12.state == STATE_R){ ev.events |= EPOLLIN; } if(fsm21.state == STATE_W){ ev.events |= EPOLLOUT; } epoll_ctl(epfd, EPOLL_CTL_MOD, fd1, &ev); ev.events = 0; ev.data.fd = fd2; if(fsm12.state == STATE_W){ ev.events |= EPOLLOUT; } if(fsm21.state == STATE_R){ ev.events |= EPOLLIN; } epoll_ctl(epfd, EPOLL_CTL_MOD, fd2, &ev); //监视 if(fsm12.state < STATE_AUTO || fsm21.state < STATE_AUTO){ while( epoll_wait(epfd, &ev, 1, -1) ){ if (errno == EINTR){ continue; } perror("epoll_wait:"); exit(1); } } //查看监视结果 if( ev.data.fd == fd1 && ev.events & EPOLLIN \ || ev.data.fd == fd2 && ev.events & EPOLLOUT\ || fsm12.state > STATE_AUTO) fsm_driver(&fsm12);//驱动 1->2 if( ev.data.fd == fd2 && ev.events & EPOLLIN \ || ev.data.fd == fd1 && ev.events & EPOLLOUT\ || fsm21.state > STATE_AUTO) fsm_driver(&fsm21);//驱动 2->1 } //回复文件原始打开状态 fcntl(fd1, F_SETFL, fd1_save); fcntl(fd2, F_SETFL, fd2_save); close(epfd); } //主函数 int main(int argc, char *argv[]){ int fd1, fd2; fd1 = open(TTY1, O_RDWR); /* 如果出错 */ write(fd1, "TTY1\n", 5); fd2 = open(TTY2, O_RDWR | O_NONBLOCK); /* 如果出错 */ write(fd2, "TTY2\n", 5); //数据中继驱动函数 relay(fd1, fd2); close(fd1); close(fd2); exit(0); }
    Processed: 0.025, SQL: 8