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

IO多路转接

文章目录

    • IO多路转接
      • select
      • poll
      • epoll
      • epoll使用实例

用来监视文件描述符号。

函数:

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

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

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

select

SYNOPSIS
       /* According to POSIX.1-2001, POSIX.1-2008 */
       #include 

       /* According to earlier standards */
       #include 
       #include 
       #include 
		
    	/*
    	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 

       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 
		/*
		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 
       #include 

       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 

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 
		/*
		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 
		/*
		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 

       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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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);
}

你可能感兴趣的:(高级IO,linux,epoll)