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个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。
两种高性能IO设计模式
在传统的网络服务设计模式中,有两种比较经典的模式:
一种是多线程,一种是线程池。
对于多线程模式,也就说来了client,服务器就会新建一个线程来处理该client的读写事件,如下图所示:
这种模式虽然处理起来简单方便,但是由于服务器为每个client的连接都采用一个线程去处理,使得资源占用非常大。因此,当连接数量达到上限时,再有用户请求连接,直接会导致资源瓶颈,严重的可能会直接导致服务器崩溃。
因此,为了解决这种一个线程对应一个客户端模式带来的问题,提出了采用线程池的方式,也就说创建一个固定大小的线程池,来一个客户端,就从线程池取一个空闲线程来处理,当客户端处理完读写操作之后,就交出对线程的占用。因此这样就避免为每一个客户端都要创建线程带来的资源浪费,使得线程可以重用。
但是线程池也有它的弊端,如果连接大多是长连接,因此可能会导致在一段时间内,线程池中的线程都被占用,那么当再有用户请求连接时,由于没有可用的空闲线程来处理,就会导致客户端连接失败,从而影响用户体验。因此,线程池比较适合大量的短连接应用。
因此便出现了下面的两种高性能IO设计模式:Reactor和Proactor。
在Reactor模式中,会先对每个client注册感兴趣的事件,然后有一个线程专门去轮询每个client是否有事件发生,当有事件发生时,便顺序处理每个事件,当所有事件处理完之后,便再转去继续轮询,如下图所示:
从这里可以看出,上面的五种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;
}