五种网络IO模型

文章目录

概述

网络IO,会涉及到两个系统对象,一个是用户空间调用IO的进程或者线程,另一个是内核空间的内核系统,比如发生IO操作read时候,会经历2个阶段:

  1. 等待数据准备就绪
  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,结束。

相关推荐
捕鲸叉4 小时前
创建线程时传递参数给线程
开发语言·c++·算法
A charmer4 小时前
【C++】vector 类深度解析:探索动态数组的奥秘
开发语言·c++·算法
Peter_chq4 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
青花瓷6 小时前
C++__XCode工程中Debug版本库向Release版本库的切换
c++·xcode
幺零九零零7 小时前
【C++】socket套接字编程
linux·服务器·网络·c++
捕鲸叉7 小时前
MVC(Model-View-Controller)模式概述
开发语言·c++·设计模式
Dola_Pan8 小时前
C++算法和竞赛:哈希算法、动态规划DP算法、贪心算法、博弈算法
c++·算法·哈希算法
yanlou2338 小时前
KMP算法,next数组详解(c++)
开发语言·c++·kmp算法
小林熬夜学编程8 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
阿洵Rain9 小时前
【C++】哈希
数据结构·c++·算法·list·哈希算法