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;
- }
来源: http://www.bubuko.com/infodetail-3478648.html