Linux 网络编程的5种IO模型:异步IO模型

Linux 网络编程的5种IO模型:异步IO模型

todo :
多路复用epoll 阅读例程
信号驱动
异步IO

5.异步IO模型

  异步IO模型才是最理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要关心实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。

  也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用iO函数进行实际的读写操作。

  注意,异步IO是需要操作系统的底层支持,在Java 7中,提供了Asynchronous IO。简称AIO

前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。

img

两种高性能IO设计模式

在传统的网络服务设计模式中,有两种比较经典的模式:

  一种是多线程,一种是线程池。

  对于多线程模式,也就说来了client,服务器就会新建一个线程来处理该client的读写事件,如下图所示:

img

这种模式虽然处理起来简单方便,但是由于服务器为每个client的连接都采用一个线程去处理,使得资源占用非常大。因此,当连接数量达到上限时,再有用户请求连接,直接会导致资源瓶颈,严重的可能会直接导致服务器崩溃。

  因此,为了解决这种一个线程对应一个客户端模式带来的问题,提出了采用线程池的方式,也就说创建一个固定大小的线程池,来一个客户端,就从线程池取一个空闲线程来处理,当客户端处理完读写操作之后,就交出对线程的占用。因此这样就避免为每一个客户端都要创建线程带来的资源浪费,使得线程可以重用。

  但是线程池也有它的弊端,如果连接大多是长连接,因此可能会导致在一段时间内,线程池中的线程都被占用,那么当再有用户请求连接时,由于没有可用的空闲线程来处理,就会导致客户端连接失败,从而影响用户体验。因此,线程池比较适合大量的短连接应用。

  因此便出现了下面的两种高性能IO设计模式:Reactor和Proactor。

在Reactor模式中,会先对每个client注册感兴趣的事件,然后有一个线程专门去轮询每个client是否有事件发生,当有事件发生时,便顺序处理每个事件,当所有事件处理完之后,便再转去继续轮询,如下图所示:

img

从这里可以看出,上面的五种IO模型中的多路复用IO就是采用Reactor模式。注意,上面的图中展示的 是顺序处理每个事件,当然为了提高事件处理速度,可以通过多线程或者线程池的方式来处理事件。Java NIO使用的就是这种

  在Proactor模式中,当检测到有事件发生时,会新起一个异步操作,然后交由内核线程去处理,当内核线程完成IO操作之后,发送一个通知告知操作已完成,可以得知,异步IO模型采用的就是Proactor模式。Java AIO使用的这种。

异步IO

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

#include <aio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h>


static char *memBuffer;
static int sFileDesc;
static struct sigaction sOldSigAction;

static void MySigQuitHandler(int sig)
{
    printf("Signal Quit! The number is: %d\n", sig);
}

static void MyFileReadCompleteProcedure(int sig, siginfo_t *si, void *ucontext)
{
    printf("The file length is: %zu, and the content is: %s\n", strlen(memBuffer), memBuffer);
    int status = close(sFileDesc);
    if(status == 0)
        puts("File closed successfully!");
    else
        printf("The error code is: %d\n", status);

    free(memBuffer);

    // 还原原来的SIGUSR1信号行为
    if(sigaction(SIGUSR1, &sOldSigAction, NULL) == -1)
        puts("SIGUSR1 signal restore failed!");
}

int main(void)
{
    struct sigaction sigAction = { .sa_flags = SA_RESTART, .sa_handler = &MySigQuitHandler };

    sigemptyset(&sigAction.sa_mask);

    if (sigaction(SIGQUIT, &sigAction, NULL) == -1)
    {
        puts("Signal failed!");
        return -1;
    }

    sigAction.sa_sigaction = &MyFileReadCompleteProcedure;
    if(sigaction(SIGUSR1, &sigAction, &sOldSigAction) == -1)
    {
        puts("Signal failed!");
        return -1;
    }

    const char *filePath = "myfile.txt";

    const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
    sFileDesc = open(filePath, O_RDONLY, mode);
    if(sFileDesc == -1)
    {
        printf("The file: %s cannot be opened!\n", filePath);
        return -1;
    }

    const long fileLength = lseek(sFileDesc, 0, SEEK_END);
    lseek(sFileDesc, 0, SEEK_SET);

    memBuffer = malloc(fileLength + 1);
    memBuffer[fileLength] = '\0';

    struct aiocb aioBuffer;
    aioBuffer.aio_fildes = sFileDesc;
    aioBuffer.aio_offset = 0;
    aioBuffer.aio_buf = memBuffer;
    aioBuffer.aio_nbytes = fileLength;
    aioBuffer.aio_reqprio = 0;
    aioBuffer.aio_sigevent = (struct sigevent){.sigev_notify = SIGEV_SIGNAL, .sigev_signo = SIGUSR1, .sigev_value.sival_ptr = memBuffer };

    aio_read(&aioBuffer);

    getchar();

    return 0;
}

猜你喜欢

转载自www.cnblogs.com/schips/p/12575933.html