文章目录
概述
网络IO,会涉及到两个系统对象,一个是用户空间调用IO的进程或者线程,另一个是内核空间的内核系统,比如发生IO操作read时候,会经历2个阶段:
- 等待数据准备就绪
- 将数据从内核拷贝到进程或者线程中。
以上两个阶段上有各种不同的情况,所以出现了多种网络IO模型。
五种网络IO模型
阻塞IO
在 linux 中,默认情况下所有的 socket 都是 blocking。关于阻塞IO,其实是用户态相内核态发送了一个请求,内核数据没有就绪的时候,用户态一直处于阻塞状态。当内核数据准备好了,它就会从内核中把数据拷贝到用户内存中,然后内核返回结果;这时候,用户态解除blocking状态,重新正常运行起来。
对于程序员来说,listen(),send(),recv()这些接口都是阻塞型的。
对于这些函数,都是阻塞的,在执行一个函数后,线程如果无法执行其他函数,那也是不太符合常理的。来一个改进方案,使用多线程(或者多进程)。多线程(或者多进程)的目的是让每个连接都有自己独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。通常来说,进程开销比线程开销要大的多,所以多线程比较常用。用pthread_create()创建新线程,fork()创建新的进程。
下面给一个阻塞IO的代码例子,如下:
cpp
int listenfd, connfd, n;
struct sockaddr_in servaddr;
char buff[MAXLNE];
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(9999);
if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
if (listen(listenfd, 10) == -1) {
printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
struct sockaddr_in client;
socklen_t len = sizeof(client);
if ((connfd = accept(listenfd, (struct sockaddr *)&client, &len)) == -1) {
printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
return 0;
}
printf("========waiting for client's request========\n");
while (1) {
n = recv(connfd, buff, MAXLNE, 0);
if (n > 0) {
buff[n] = '\0';
printf("recv msg from client: %s\n", buff);
send(connfd, buff, n, 0);
} else if (n == 0) {
close(connfd);
}
//close(connfd);
}
close(listenfd);
非阻塞IO
linux中,通过设置socket使其变成non-blocking。当对一个non-blocking socket执行读操作的时候,如果没有数据就返回-1;要么就等待数据拷贝到用户空间,然后返回OK。
多路复用IO
多路复用IO,这个应该提下select/epoll。select/epoll 的好处就在于单个 process 就可以同时处理多个网络连接的 IO。它的基本原理就是 select/epoll 这个 function会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。
当用户进程调用了select,整个进程会被block,这个时候,内核会检测所有select负责的socket,当其中的任何一个socket中的数据准备好了,select就会返回。这个时候用户进程就可以继续执行其他操作了。
异步IO
Linux下的asynchronousIO用在磁盘IO读写操作,不用于网络IO。用户进程发起一个操作之后,立刻开始去做其他的事情;从内核角度看,当它收到一个asynchronous read/其他操作后,首先立刻返回,所以不会对用户进程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户内存,当一切完成之后,会给用户进程发送一个signal,告诉用户进程操作完成。
信号驱动IO
首先我们允许套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。当数据报准备好读取时,内核就为该进程产生一个 SIGIO 信号。我们随后既可以在信号处理函数中调用 read 读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它来读取数据报。无论如何处理 SIGIO 信号,这种模型的优势在于等待数据报到达(第一阶段)期间,进程可以继续执行,不被阻塞。免去了 select 的阻塞与轮询,当有活跃套接字时,由注册的 handler 处理。
小结
这一篇写了五种网络IO模型,对于后边的学习都有很大的帮助。有兴趣,可以一起来学习学习。OK,结束。