• 网络编程之异步IO


    Linux的I/O模型有下面几种:
    1. 同步阻塞I/O: 用户进程进行I/O操作,一直阻塞到I/O操作完成为止。
    2. 同步非阻塞I/O: 用户程序可以通过设置文件描述符的属性O_NONBLOCK,I/O操作可以立即返回,无数据准备好时,返回一个错误,一般采用轮询方式,查看某个操作是否就绪。
    3.I/O复用: 用户进程可以对I/O事件进行阻塞,但是I/O操作并不阻塞。通过select/poll/epoll等函数调用来达到此目的。

    4.信号驱动式I/O:让内核在描述符就绪时,发送SIGIO信号给用户进程。 对于UDP而言,数据报文到达,套接字发生错误时,会产生SIGIO信号。

    对于TCP而言,产生SIGIO信号的条件(太多所以基本不会用这种方式)如下:

      1)监听套接字上,某个连接完成;

      2) 某个断连接请求已经发起;

      3) 某个断连接已经完成;

           4)数据到达套接字,即TCP缓冲区;

      5)数据从套接字缓冲区发送走,即TCP缓冲区有可写空间;

      6)  发生异步错误;

       信号驱动I/O,要求进程执行一下三个步骤:

            1) 建立SIGIO的信号处理函数

            2)设置套接字的属主,通常为fcntl函数的F_SETOWN设置

            3)开启该套接字的信号驱动式I/O, fcntl的F_SETFL命令打开 O_ASYNC

     5. 异步事件非阻塞I/O: 也叫做异步I/O(AIO),用户程序可以通过向内核发出I/O请求命令,不用等带I/O事件真正发生,可以继续做另外的事情,等I/O操作完成,

           内核会通过函数回调或者信号机制通知用户进程。这样很大程度提高了系统吞吐量。

         前4种I/O,都由用户进程,调用I/O函数,实现数据在内核和用户进程缓冲器间的拷贝,数据拷贝期间用户进程或多或少会阻塞一段时间(数据拷贝)。

    而异步I/O,是内核完成数据拷贝之后才会通知用户进程 ,在此期间用户进程可以进行其他的操作。

    下面就AIO做详细介绍:
    要使用aio的功能,需要include头文件aio.h,在编译连接的时候需要加入POSIX实时扩展库rt.下面就aio库的使用做介绍。
    1. AIO整个过程所使用的数据存放在一个结构体中,struct aiocb,aio control block.看看头文件中的定义:

    /* Asynchronous I/O control block.  */
    struct aiocb
    {
      int aio_fildes;                /* File desriptor.  */ 
      int aio_lio_opcode;       /* Operation to be performed.*/
      int aio_reqprio;                  

      volatile void *aio_buf;    /* Location of buffer.  */
      size_t aio_nbytes;        /* Length of transfer.  */ 
      struct sigevent aio_sigevent; /* Signal number and value.  */ 

    }

    struct sigevent {

      int sigev_notify; //Notification type.     

      int sigev_signo; //Signal number. 

      union sigval sigev_value; //Signal value.

      void (*sigev_notify_function)(union sigval); //Notification function. 

      pthread_attr_t *sigev_notify_attributes; //Notification attributes.   // 指针,用于说明创建的线程属性

    };

    union sigval

    {

      int sival_int;

      void *sival_ptr;

    };

    a)  sigev_notify 取值:  

      SIGEV_NONE 什么也不做 ;

      SIGEV_SIGNAL 事件发生时,将sigev_signo 指定的信号(A queued signal)发送给指定的进程. ;

      SIGEV_THREAD 事件发生时,内核会(在此进程内)以sigev_notification_attributes为线程属性创建一个线程,

                                        并且让它执行sigev_notify_function,传入sigev_value作为为一个参数

    b) sigev_value

    在sigev_notify = SIGEV_THREAD 时使用,作为sigev_notify_function 的参数

    c)*sigev_notify_function)(union sigval)

    函数指针(指向通知执行函数),在sigev_notify = SIGEV_THREAD 时使用, 其他情况下置为NULL.

    d)sigev_notify_attributes

    指向线程属性的指针,在sigev_notify = SIGEV_THREAD 时使用,指定创建线程的属性, 其他情况下置为NULL. 

    2. int aio_read(struct aiocb *aiocbp);

    异步读操作,向内核发出读的命令,传入的参数是一个aiocb的结构,比如
    struct aiocb myaiocb;
    memset(&aiocb , 0x00 , sizeof(myaiocb));
    myaiocb.aio_fildes = fd;
    myaiocb.aio_buf = new char[1024];
    myaiocb.aio_nbytes = 1024;
    if (aio_read(&myaiocb) != 0)
    {
      printf("aio_read error:%s/n" , strerror(errno));
      return false;
    }

    3. int aio_write(struct aiocb *aiocbp);
    异步写操作,向内核发出写的命令,传入的参数仍然是一个aiocb的结构,当文件描述符的O_APPEND
    标志位设置后,异步写操作总是将数据添加到文件末尾。如果没有设置,则添加到aio_offset指定的
    地方,比如:
    struct aiocb myaiocb;
    memset(&aiocb , 0x00 , sizeof(myaiocb));
    myaiocb.aio_fildes = fd;
    myaiocb.aio_buf = new char[1024];
    myaiocb.aio_nbytes = 1024;
    myaiocb.aio_offset = 0;
    if (aio_write(&myaiocb) != 0)
    {
      printf("aio_read error:%s/n" , strerror(errno));
      return false;
    }

    4. int aio_error(const struct aiocb *aiocbp);
    如果该函数返回0,表示aiocbp指定的异步I/O操作请求完成。
    如果该函数返回EINPROGRESS,表示aiocbp指定的异步I/O操作请求正在处理中。
    如果该函数返回ECANCELED,表示aiocbp指定的异步I/O操作请求已经取消。
    如果该函数返回-1,表示发生错误,检查errno。

    5. ssize_t aio_return(struct aiocb *aiocbp);
    这个函数的返回值相当于同步I/O中,read/write的返回值。只有在aio_error调用后
    才能被调用。

    6. int aio_cancel(int fd, struct aiocb *aiocbp);
    取消在文件描述符fd上的aiocbp所指定的异步I/O请求。
    如果该函数返回AIO_CANCELED,表示操作成功。
    如果该函数返回AIO_NOTCANCELED,表示取消操作不成功,使用aio_error检查一下状态。
    如果返回-1,表示发生错误,检查errno.

    7. int lio_listio(int mode, struct aiocb *restrict  list[],int nent, struct sigevent *sig);

    使用该函数,在很大程度上可以提高系统的性能,因为再一次I/O过程中,OS需要进行
    用户态和内核态的切换,如果我们将更多的I/O操作都放在一次用户太和内核太的切换中,
    减少切换次数,换句话说在内核尽量做更多的事情。这样可以提高系统的性能。

    用户程序提供一个struct aiocb的数组,每个元素表示一次AIO的请求操作。需要设置struct aiocb
    中的aio_lio_opcode数据成员的值,有LIO_READ,LIO_WRITE和LIO_NOP。
    nent表示数组中元素的个数。最后一个参数是对AIO操作完成后的通知机制的设置。

    8. 设置AIO的通知机制,有两种通知机制:信号和回调
    (1).信号机制
     首先我们应该捕获SIGIO信号,对其作处理:
     struct sigaction sig_act;
     sigempty(&sig_act.sa_mask);
     sig_act.sa_flags = SA_SIGINFO;
      sig_act.sa_sigaction = aio_handler;
      
      struct aiocb myaiocb;
      bzero( (char *)&myaiocb, sizeof(struct aiocb) );
      myaiocb.aio_fildes = fd;
      myaiocb.aio_buf = malloc(BUF_SIZE+1);
      myaiocb.aio_nbytes = BUF_SIZE;
      myaiocb.aio_offset = next_offset;
      
      myaiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
      myaiocb.aio_sigevent.sigev_signo = SIGIO;
      myaiocb.aio_sigevent.sigev_value.sival_ptr = &myaiocb;

      ret = sigaction( SIGIO, &sig_act, NULL );

     信号处理函数的实现:
     void aio_handler( int signo, siginfo_t *info, void *context )
     {
      struct aiocb *req;
      
      if (info->si_signo == SIGIO) {
        req = (struct aiocb *)info->si_value.sival_ptr;
        
        if (aio_error( req ) == 0) {
          ret = aio_return( req );
        }
      }
      return;
    }

    (2). 回调机制
    需要设置:
    myaiocb.aio_sigevent.sigev_notify = SIGEV_THREAD
    my_aiocb.aio_sigevent.notify_function = aio_handler;

    回调函数的原型:
    typedef void (* FUNC_CALLBACK)(sigval_t sigval);

    AIO机制为服务器端高并发应用程序提供了一种性能优化的手段。加大了系统吞吐量。

    https://www.ibm.com/developerworks/cn/linux/l-async/  一个有少量例子的讲解

  • 相关阅读:
    spring-boot快速搭建解析
    springmvc处理ajax跨域
    Spring-AOP解析
    springmvc注解
    springmvc源码分析
    Spring源码分析
    JAVA性能优化
    数据取舍
    命令行控制
    Junit常用操作
  • 原文地址:https://www.cnblogs.com/fchy822/p/8974322.html
Copyright © 2020-2023  润新知