精华内容
下载资源
问答
  • 网络IO
    千次阅读
    2022-04-04 19:28:58

    网络IO模型

    什么是IO

    即Input/Output,我们通过电脑访问硬盘资源本身的操作就是一种IO操作。

    什么是网络IO

    网络上的数据是通过协议流动的,两端在通信时是通过各自的套接字Scoket进行操作。所谓的网络IO就是对Socket套接字进行读取操作

    深入理解Socket套接字原理_Princesk的博客-CSDN博客

    网络上使用tcp协议通讯时两端都会先将数据包放到socket划分缓存区,网络IO操作的就是这个缓存区或者叫缓冲区。

    网络IO操作Socket的操作流程

    1. 等待网络上通过协议传输的数据分组到达,然后放到某个缓冲区

    2. 按照进程要求将目标数据移交到对应进程的缓冲区内等待进程去使用

    IO的常见模型

    • 阻塞IO,bokingio
    • 非阻塞IO,non-boking io
    • 多路复用IO,multiplexing io
    • 信号驱动式IO,signal-driven io
    • 异步IO,asynchronous io

    阻塞IO

    应用进程被阻塞,直到数据复制到应用进程缓冲区才返回。

    非阻塞IO

    应用进程执行系统调用后,内核返回一个错误码。然后应用程序继续执行,但是需要不断执行系统调用获知IO是否完成,轮询的一种。

    缺点:cpu利用率低

    多路复用IO

    由于阻塞式IO通过轮询得到的只是一个IO任务是否完成,而可能有多个任务在同时进行,因此就想到了能否轮询多个IO任务的状态,只要有任何一个任务完成,就去处理它。这就是所谓的IO多路复用

    信号驱动式IO

    应用进程使用 sigaction 系统调用,内核立即返回,应用进程可以继续执行,也就是说等待数据阶段应用进程是非阻塞的。内核在数据到达时向应用进程发送 SIGIO 信号,应用进程收到之后在信号处理程序中调用 recvfrom 将数据从内核复制到应用进程中。

    异步IO

    应用进程执行aio_read系统调用会立即返回,应用进程可以继续执行且不会阻塞,内核在所有操作完成后向该应用进程发送信号

    更多相关内容
  • 五种网络IO模型

    千次阅读 2022-07-05 09:30:09
      本文重点在于介绍五种网络IO模型  网络IO,会涉及到两个系统对象,一个是用户空间调用IO的进程或线程,另一个是内核空间的内核系统,比如发生IO操作read时,它会经历两个阶段。  因为在以上两个阶段上各有...

    前言

      本文重点在于介绍五种网络IO模型

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

    1. 等待数据准备就绪
    2. 将数据从内核拷贝到进程或者线程种

      因为在以上两个阶段上各有不同的情况,所以出现了多种网络 IO 模型。

    在这里插入图片描述

      本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接 C/C++后台高级服务器课程介绍 详细查看课程的服务。

    阻塞 IO(blocking IO)

      在 linux 中,默认情况下所有的 socket 都是 blocking,一个典型的读操作流程

    在这里插入图片描述

      当用户进程调用了 read 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据。对于 network io 来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的数据包)这个时候 kernel 就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当 kernel 一直等到数据准备好了,它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block 的状态,重新运行起来。
      所以,blocking IO 的特点就是在 IO 执行的两个阶段(等待数据和拷贝数据两个阶段)都被block 了。

      第一次接触到的网络编程都是从 listen()、send()、recv() 等接口开始的,这些接口都是阻塞型的。使用这些接口可以很方便的构建服务器/客户机的模型。下面是一个简单地“一问一答”的服务器。

    在这里插入图片描述

      大部分的 socket 接口都是阻塞型的。所谓阻塞型接口是指系统调用(一般是 IO 接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。
      实际上,除非特别指定,几乎所有的 IO 接口 ( 包括 socket 接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用 send()的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。
      一个简单的改进方案是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。具体使用多进程还是多线程,并没有一个特定的模式。传统意义上,进程的开销要远远大于线程,所以如果需要同时为较多的客户机提供服务,则不推荐使用多进程;如果单个服务执行体需要消耗较多的 CPU 资源,譬如需要进行大规模或长时间的数据运算或文件访问,则进程较为安全。通常,使用 pthread_create ()创建新线程,fork()创建新进程。
      我们假设对上述的服务器 / 客户机模型,提出更高的要求,即让服务器同时为多个客户机提供一问一答的服务。于是有了如下的模型。

    在这里插入图片描述

      在上述模型中,主线程持续等待客户端的连接请求,如果有连接,则创建新线程,并在新线程中提供为前例同样的问答服务。

      很多人可能不明白为何一个 socket 可以 accept 多次。实际上 socket 的设计者可能特意为多客户机的情况留下了伏笔,让 accept()能够返回一个新的 socket。下面是accept 接口的原型:

    int accept(int s, struct sockaddr *addr, socklen_t *addrlen);
    

      输入参数 s 是从 socket(),bind()和 listen()中沿用下来的 socket 句柄值。执行完bind()和 listen()后,操作系统已经开始在指定的端口处监听所有的连接请求,如果有请求,则将该连接请求加入请求队列。调用 accept()接口正是从 socket s 的请求队列抽取第一个连接信息,创建一个与 s 同类的新的 socket 返回句柄。新的 socket 句柄即是后续 read()和 recv()的输入参数。如果请求队列当前没有请求,则 accept() 将进入阻塞状态直到有请求进入队列。

      上述多线程的服务器模型似乎完美的解决了为多个客户机提供问答服务的要求,但其实并不尽然。如果要同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而线程与进程本身也更容易进入假死状态。

      很多人可能会考虑使用“线程池”或“连接池”。“线程池”旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。“连接池”维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统,如 websphere、tomcat 和各种数据库等。但是,“线程池”和“连接池”技术也只是在一定程度上缓解了频繁调用 IO 接口带来的资源占用。而且,所谓“池”始终有其上限,当请求大大超过上限时,“池”构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用“池”必须考虑其面临的响应规模,并根据响应规模调整“池”的大小。

      对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求,“线程池”或“连接池”或许可以缓解部分压力,但是不能解决所有问题。总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。

    阻塞 IO优缺点:

    优点:开发简单,容易入门。在阻塞等待期间,用户线程挂起,在挂起期间不会占用 CPU 资源

    缺点:一个线程维护一个 IO ,不适合大并发,在并发量大的时候需要创建大量的线程来维护网络连接,内存、线程开销非常大

    非阻塞 IO(non-blocking IO)

      Linux 下,可以通过设置 socket 使其变为 non-blocking。当对一个 non-blocking socket 执行读操作时,流程是这个样子:

    在这里插入图片描述

      从图中可以看出,当用户进程发出 read 操作时,如果 kernel 中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个 error。从用户进程角度讲 ,它发起一个read 操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个 error时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。一旦 kernel 中的数据准备好了,并且又再次收到了用户进程的 system call,那么它马上就将数据拷贝到了用户内存,然后返回,所以,在非阻塞式 IO 中,用户进程其实是需要不断的主动询问 kernel数据准备好了没有。

      在非阻塞状态下,recv() 接口在被调用后立即返回,返回值代表了不同的含义。如在本例中

    • recv() 返回值大于 0,表示接受数据完毕,返回值即是接受到的字节数;
    • recv() 返回 0,表示连接已经正常断开;
    • recv() 返回 -1,且 errno 等于 EAGAIN,表示 recv 操作还没执行完成;
    • recv() 返回 -1,且 errno 不等于 EAGAIN,表示 recv 操作遇到系统错误 errno。

      非阻塞的接口相比于阻塞型接口的显著差异在于,在被调用之后立即返回。使用如下的函数可以将某句柄 fd 设为非阻塞状态。

    fcntl( fd, F_SETFL, O_NONBLOCK );
    

      下面将给出只用一个线程,但能够同时从多个连接中检测数据是否送达,并且接收数据的模型。

    在这里插入图片描述

      可以看到服务器线程可以通过循环调用 recv()接口,可以在单个线程内实现对所有连接的数据接收工作。但是上述模型绝不被推荐。因为,循环调用 recv()将大幅度推高 CPU占用率;此外,在这个方案中 recv()更多的是起到检测“操作是否完成”的作用,实际操作系统提供了更为高效的检测“操作是否完成“作用的接口,例如 select()多路复用模式,可以一次检测多个连接是否活跃。

    非阻塞 IO优缺点:

    同步非阻塞 IO 优点:每次发起 IO 调用,在内核等待数据的过程中可以立即返回,用户线程不会阻塞,实时性好

    同步非阻塞 IO 缺点:多个线程不断轮询内核是否有数据,占用大量 CPU 资源,效率不高。一般 Web 服务器不会采用此模式

    多路复用 IO(IO multiplexing)

      IO multiplexing 这个词可能有点陌生,但是提到 select/epoll,大概就都能明白了。有些地方也称这种 IO 方式为事件驱动 IO(event driven IO)。我们都知道,select/epoll 的好处就在于单个 process 就可以同时处理多个网络连接的 IO。它的基本原理就是 select/epoll 这个 function会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。它的流程如图:

    在这里插入图片描述

      当用户进程调用了 select,那么整个进程会被 block,而同时,kernel 会“监视”所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回。这个时候用户进程再调用 read 操作,将数据从 kernel 拷贝到用户进程。

      这个图和 blocking IO 的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select 和 read),而 blocking IO 只调用了一个系统调用(read)。但是使用 select 以后最大的优势是用户可以在一个线程内同时处理多个 socket 的 IO 请求。用户可以注册多个 socket,然后不断地调用 select 读取被激活的 socket,即可达到在同一个线程内同时处理多个 IO 请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。(多说一句:所以,如果处理的连接数不是很高的话,使用select/epoll 的 web server 不一定比使用 multi-threading + blocking IO 的 webserver 性能更好,可能延迟还更大。select/epoll 的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)

      在多路复用模型中,对于每一个 socket,一般都设置成为 non-blocking,但是,如上图所示,整个用户的 process 其实是一直被 block 的。只不过 process 是被 select 这个函数 block,而不是被 socket IO 给 block。因此 select()与非阻塞 IO 类似。

    大部分 Unix/Linux 都支持 select 函数,该函数用于探测多个文件句柄的状态变化。

    下面给出 select 接口的原型:

    FD_ZERO(int fd, fd_set* fds)
    FD_SET(int fd, fd_set* fds)
    FD_ISSET(int fd, fd_set* fds)
    FD_CLR(int fd, fd_set* fds)
    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout)
    

      这里,fd_set 类型可以简单的理解为按 bit 位标记句柄的队列,例如要在某 fd_set中标记一个值为 16 的句柄,则该 fd_set 的第 16 个 bit 位被标记为 1。具体的置位、验证可使用 FD_SET、FD_ISSET 等宏实现。在 select()函数中readfds、writefds 和exceptfds 同时作为输入参数和输出参数。如果输入的 readfds 标记了 16 号句柄,则select()将检测 16 号句柄是否可读。在 select()返回后,可以通过检查 readfds 有否标记 16 号句柄,来判断该“可读”事件是否发生。另外,用户可以设置 timeout 时间。

    下面将重新模拟上例中从多个客户端接收数据的模型。

    在这里插入图片描述

      上述模型只是描述了使用 select()接口同时从多个客户端接收数据的过程;由于 select()接口可以同时对多个句柄进行读状态、写状态和错误状态的探测,所以可以很容易构建为多个客户端提供独立问答服务的服务器系统。

    在这里插入图片描述

      这里需要指出的是,客户端的一个 connect() 操作,将在服务器端激发一个“可读事件”,所以 select() 也能探测来自客户端的 connect() 行为。
      上述模型中,最关键的地方是如何动态维护 select()的三个参数 readfds、writefds和 exceptfds。作为输入参数,readfds 应该标记所有的需要探测的“可读事件”的句柄,其中永远包括那个探测 connect() 的那个“母”句柄;同时,writefds 和 exceptfds 应该标记所有需要探测的“可写事件”和“错误事件”的句柄 ( 使用 FD_SET() 标记 )。
      作为输出参数,readfds、writefds 和 exceptfds 中的保存了 select() 捕捉到的所有事件的句柄值。程序员需要检查的所有的标记位 ( 使用 FD_ISSET()检查 ),以确定到底哪些句柄发生了事件。

      上述模型主要模拟的是“一问一答”的服务流程,所以如果 select()发现某句柄捕捉到了“可读事件”,服务器程序应及时做recv()操作,并根据接收到的数据准备好待发送数据,并将对应的句柄值加入 writefds,准备下一次的“可写事件”的 select()探测。同样,如果 select()发现某句柄捕捉到“可写事件”,则程序应及时做 send()操作,并准备好下一次的“可读事件”探测准备。

      这种模型的特征在于每一个执行周期都会探测一次或一组事件,一个特定的事件会触发某个特定的响应。我们可以将这种模型归类为“事件驱动模型”。相比其他模型,使用 select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。

      但这个模型依旧有着很多问题。首先 select()接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select()接口本身需要消耗大量时间去轮询各个句柄。很多操作系统提供了更为高效的接口,如linux提供了epoll,BSD提供了kqueue,Solaris提供了/dev/poll,…。如果需要实现更高效的服务器程序,类似 epoll 这样的接口更被推荐。遗憾的是不同的操作系统特供的 epoll 接口有很大差异,所以使用类似于 epoll 的接口实现具有较好跨平台能力的服务器会比较困难。

      其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。如下例,庞大的执行体 1 的将直接导致响应事件 2 的执行体迟迟得不到执行,并在很大程度上降低了事件探测的及时性。

      幸运的是,有很多高效的事件驱动库可以屏蔽上述的困难,常见的事件驱动库有libevent 库,还有作为 libevent 替代者的 libev 库。这些库会根据操作系统的特点选择最合适的事件探测接口,并且加入了信号(signal) 等技术以支持异步响应,这使得这些库成为构建事件驱动模型的不二选择。下章将介绍如何使用 libev 库替换 select 或 epoll接口,实现高效稳定的服务器模型。

    多路复用 IO优缺点:

    优点:系统不必创建维护大量线程,只使用一个线程、一个选择器就可同时处理成千上万连接,大大减少了系统开销

    缺点:本质上,select 和 epoll 的系统调用是阻塞式的,属于同步 IO,需要在读写时间就绪后,由系统调用进行阻塞的读写

    实际上,Linux 内核从 2.6 开始,也引入了支持异步响应的 IO 操作,如 aio_read,aio_write,这就是异步 IO。

    异步 IO(Asynchronous I/O)

    Linux 下的 asynchronous IO 用在磁盘 IO 读写操作,不用于网络 IO,从内核 2.6 版本才开始引入。先看一下它的流程

    在这里插入图片描述

      用户进程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从 kernel的角度,当它收到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进程产生任何 block。然后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel 会给用户进程发送一个 signal,告诉它 read 操作完成了。

      用异步 IO 实现的服务器这里就不举例了,以后有时间另开文章来讲述。异步 IO 是真正非阻塞的,它不会对请求进程产生任何的阻塞,因此对高并发的网络服务器实现至关重要。

      到目前为止,已经将四个 IO 模型都介绍完了。现在有几个问题:blocking 和 non-blocking 的区别在哪,synchronous IO 和 asynchronous IO 的区别在哪。

      先回答最简单的这个:blocking 与 non-blocking。前面的介绍中其实已经很明确的说明了这两者的区别。调用 blocking IO 会一直 block 住对应的进程直到操作完成,而non-blocking IO 在 kernel 还在准备数据的情况下会立刻返回。

      synchronous IO 和 asynchronous IO 的区别就在于 synchronous IO 做”IO operation”的时候会将 process 阻塞。按照这个定义,之前所述的 blocking IO,non-blocking IO,IO multiplexing 都属于synchronous IO。有人可能会说,non-blocking IO 并没有被 block 啊。这里有个非常“狡猾”的地方,定义中所指的”IO operation”是指真实的 IO 操作,就是例子中的 read 这个系统调用。non-blocking IO 在执行 read 这个系统调用的时候,如果 kernel 的数据没有准备好,这时候不会 block 进程。但是当 kernel 中数据准备好的时候,read 会将数据从 kernel 拷贝到用户内存中,这个时候进程是被 block 了,在这段时间内进程是被 block的。而 asynchronous IO 则不一样,当进程发起 IO 操作之后,就直接返回再也不理睬了,直到 kernel 发送一个信号,告诉进程说 IO 完成。在这整个过程中,进程完全没有被 block。

    异步 IO 才是真正的非阻塞(两个阶段全是非阻塞)

    信号驱动 IO(signal driven I/O, SIGIO)

      首先我们允许套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。当数据报准备好读取时,内核就为该进程产生一个 SIGIO 信号。我们随后既可以在信号处理函数中调用 read 读取数据报,并通知主循环数据已准备好待处理,也可以立即通知主循环,让它来读取数据报。无论如何处理 SIGIO 信号,这种模型的优势在于等待数据报到达(第一阶段)期间,进程可以继续执行,不被阻塞。免去了 select 的阻塞与轮询,当有活跃套接字时,由注册的 handler 处理。

    在这里插入图片描述

      经过上面的介绍,会发现 non-blocking IO 和 asynchronous IO 的区别还是很明显的。在non-blocking IO 中,虽然进程大部分时间都不会被 block,但是它仍然要求进程去主动的 check,并且当数据准备完成以后,也需要进程主动的再次调用 recvfrom 来将数据拷贝到用户内存。而 asynchronous IO 则完全不同。它就像是用户进程将整个 IO 操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查 IO 操作的状态,也不需要主动的去拷贝数据。

    五种网络 IO 模型对比

      阻塞IO,非阻塞IO,多路复用IO,信号驱动IO这四种的主要区别在第一阶段,他们在第二阶段是一样的:数据从内核缓冲区复制到调用者缓冲区期间都被阻塞住。他们都是同步IO,只有同步 IO 模型才考虑阻塞和非阻塞。异步 IO 肯定是非阻塞。

    在这里插入图片描述

    展开全文
  • 网络IO模型:同步IO和异步IO,阻塞IO和非阻塞IO
  • 03 Redis 网络IO模型简介

    千次阅读 2022-02-11 04:23:03
    Redis单线程指的是网络请求模块使用了一个线程,即一个线程处理所有网络请求,其他模块该使用多线程,仍会使用了多个线程。既然是单线程模型,那么CPU不是Redis的瓶颈。Redis的瓶颈最有可能是机器内存或者网络带宽。...

    1 Redis中的单线程模型

    提起Redis,我们经常会说其底层是一个单线程模型,但这是不严谨的。Redis 单线程指的是网络请求模块使用了一个线程,即一个线程处理所有网络请求,其他模块该使用多线程,仍会使用了多个线程。既然是单线程模型,那么CPU不是Redis的瓶颈。Redis的瓶颈最有可能是机器内存或者网络带宽

     

    2 Redis中的单线程模型

    Redis基于Reactor模式开发了自己的网络事件处理器,称之为文件事件处理器(File Event Hanlder)。文件事件处理器由SocketIO多路复用程序、文件事件分派器(dispather),事件处理器(handler)四部分组成,文件事件处理器的模型如下所示:

     

    IO多路复用程序会同时监听多个socket,当被监听的socket准备好执行acceptreadwriteclose等操作时,与这些操作相对应的文件事件就会产生。IO多路复用程序会把所有产生事件的socket压入一个队列中,然后有序地每次仅一个socket的方式传送给文件事件分派器,文件事件分派器接收到socket之后会根据socket产生的事件类型调用对应的事件处理器进行处理。

    文件事件处理器分为几种:

    • 连接应答处理器:用于处理客户端的连接请求;
    • 命令请求处理器:用于执行客户端传递过来的命令,比如常见的setlpush等;
    • 命令回复处理器:用于返回客户端命令的执行结果,比如setget等命令的结果;

    事件种类:

    • AE_READABLE:与两个事件处理器结合使用。
      • 当客户端连接服务器端时,服务器端会将连接应答处理器与socketAE_READABLE事件关联起来;
      • 当客户端向服务端发送命令的时候,服务器端将命令请求处理器与AE_READABLE事件关联起来;
    • AE_WRITABLE:当服务端有数据需要回传给客户端时,服务端将命令回复处理器与socketAE_WRITABLE事件关联起来。

    Redis的客户端与服务端的交互过程如下所示:

     

    网络IO模型基本概念

    内核态

    内核态拥有完全的底层资源控制权限,可以执行任何的CPU指令,访问任何内存地址,其占有的处理机是不允许被抢占的。

    用户态

    用户程序是运行在操作系统之上,这些程序运行时称之为用户态,用户态下不能直接访问底层硬件和内存地址,只能通过委托系统调用的方式来访问底层硬件和内存。

    用户态到内核态如何切换

    从用户态切换到内核态有三种方式:

    • 系统调用:这是用户态主动要求切换到内核态的一种方式。用户进程通过系统调用申请使用操作系统提供的某些服务以便完成工作,比如,调用fork()指令实际上就是执行了一个创建新进程的系统调用。系统调用的机制其核心在于**使用了操作系统为用户特别开放的一个中断来实现的,例如Linuxint 80h中断;
    • 外设中断:当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号。这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序。如果先前执行的是用户态下的指令,那么这个切换过程就是用户态转为内核态。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作;
    • 异常:当CPU在执行运行处于用户态的程序时,发生了一些不可知的异常,这个时候就会触发由当前运行进行切换到处理此异常的内核相关程序中,也就是转到了内核态,比如缺页异常;

    这三种是用户态切换到内核态的主要方式,系统调用是主动的,后面两种是被动的。

    Linux的整体架构图如下所示:

     

    同步/异步

    同步/异步关注的是消息通信机制。

    同步:所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。等前一件做完了才能做下一件事。

    异步:异步的概念和同步相对。当一个异步过程调用发出后,调用者若不能立刻得到结果,此时可以直接返回然后执行其他任务,等到获得了结果之后通过状态、通知或者回调等手段通知调用者。

    同步、异步一般发生在不同的线程/进程之间,如Thread1Thread2是同步执行还是异步执行的。

    阻塞和非阻塞

    阻塞和非阻塞关注的是程序在等待调用结果时的状态。

    阻塞: 阻塞调用是指调用返回之前,当前线程会被挂起,只有当调用得到结果后才返回。

    非阻塞:与阻塞相反,非阻塞调用是指在不能立即得到结果之前,该函数不会将当前线程阻塞,而是立即返回。

    五种 IO 模型

    IO一般分为磁盘IO网络IO,这里我们主要关注网络IO。一次完整的网络IO过程如下所示:

     

    从上图可以看出,数据无论从网卡到用户空间还是从用户空间到网卡都需要经过内核。

    阻塞IO模型

    当应用程序调用一个 IO 函数,其底层会委托操作系统的recvfrom()去完成,当数据还没有准备好时,revfrom会一直阻塞,等待数据准备好。当数据准备好后,从内核拷贝到用户空间,recvfrom 返回成功,IO函数调用完成。过程如下所示:

     

    阻塞IO模型的优点是编程简单,但缺点是需要配合大量线程使用。应用进程没接收一个连接,就需要为此连接创建一个线程来处理该连接上的读写任务。

    非阻塞IO模型

    调用进程在等待数据的过程中不会被阻塞,而是会不断地轮询查看数据有没有准备好。当数据准备好后,将数据从内核空间拷贝到用户空间,完成IO函数的调用。等待数据的过程是非阻塞的,但数据拷贝时仍是阻塞的。过程如下所示:

     

    非阻塞io的优点在于可以实现使用一个线程同时处理多个连接的需求,减少线程的大量使用。缺点在于要不断地去轮询检查数据是否准备好,比较耗费CPU

    IO复用模型

    为了解决非阻塞IO不断轮询导致CPU占用升高的问题,出现了IO复用模型。IO复用中,使用其他线程帮助去检查多个线程数据的完成情况,提高效率。

    Linux中提供了selectpollepoll三种方式来实现IO复用。一个线程可以对多个IO端口进行监听,当有读写事件产生时会分发到具体的线程进行处理。过程如下所示:

     

    IO复用只需要阻塞在selectpoll或者epoll,可以同时处理和管理多个连接。缺点是当selectpoll或者epoll 管理的连接数过少时,这种模型将退化成阻塞IO 模型。并且还多了一次系统调用:一次selectpoll或者epoll 一次recvfrom

    4|4信号驱动IO模型

    应用程序可以创建一个信号驱动程序SIGIO,当数据没有处理好时,应用程序继续运行,不会被阻塞。当数据准备好之后,操作系统向应用程序发送信号,之后信号驱动程序就会执行,在信号处理函数中调用 IO函数处理数据。过程如下所示:

     

    信号驱动IO模型的优点在于非阻塞,缺点在于串行处理信号驱动程序,当前一个SIGIO没有被处理的情况下,后一个信号也不能被处理。在信号量大的时候会导致后面的信号不能被及时感知。

    异步IO模型

    相比于同步IO,异步IO不是顺序执行的。应用进程在执行aio_read系统调用之后,无论数据是否准备好,都会直接返回给用户进程,然后应用进程可以去做别的事情。当数据准备好之后,内核直接复制数据给用户进程,然后内核向进程发送通知。过程如下:

     

    信号驱动IO模型中内核通知应用进程数据何时准备好,而在异步IO模型中内核将数据复制完成之后告知应用进程IO操作已完成。

    在异步IO模型中,应用进程调用aio_read以及数据被拷贝到用户空间这两个过程都是非阻塞的。

    5 网络IO模型总结

    IO模型公有五种,前四种模型区别在于第一部分,即系统调用,但是第二部分都是一样的,即将数据从内核空间拷贝到用户空间这个过程,进程阻塞于redvfrom的调用。而最后一种,异步IO模型,在系统调用和数据拷贝过程都是非阻塞的。

     

    展开全文
  • 同步、异步、阻塞、非阻塞,当这些网络IO名词堆到一起时难免使编程初学者感到困惑,这里我们就来为大家总结网络IO模型与select模型的Python实例讲解:
  • 网络IO与磁盘IO

    2021-12-21 10:56:13
    Java中的IO体系:在Java中I/0流操作的类很多,但是核心体系实际上就只有File...1.网络 2.磁盘 //磁盘 FileInputStream fi = null; //创建管道 try { fi = new FileInputStream("d:/111/123.txt"); //存放在磁盘..

    目录

    一.了解IO

    IO流的分类

    IO流的数据来源

    1.网络

    2.磁盘

    3.内存

    4.键盘

    IO流的原理

    二.字节流

    1.read方法加缓存数组

    2.字节流解决乱码问题

    3.缓冲字节流

    5.序列化和反序列化

    三.字符流

    四.网络IO

    1.Socket和ServerSocket

    2.基于Socket手写实现RPC框架

    五.NIO(New IO) 


    一.了解IO

    Java中的IO体系:在Java中I/0流操作的类很多,但是核心体系实际上就只有File、lnputStream、OutputStream、Reader、Writer. 

    IO流的分类

     

    IO流的数据来源

    1.网络

    2.磁盘

            //磁盘
            FileInputStream fi = null; //创建管道
            try {
                fi = new FileInputStream("d:/111/123.txt"); //存放在磁盘中的文件
    
                int i = 0;
                //如果没有值 read()会返回-1
                while ((i = fi.read()) != -1) {
                    System.out.print((char) i);
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }

    3.内存

            //内存
            String str = "Hello Word"; //数据在内存中存放
            ByteArrayInputStream bai = new ByteArrayInputStream(str.getBytes());
            int i = 0;
            //如果没有值 read()会返回-1
            while ((i = bai.read()) != -1) {
                System.out.print((char) i);
            }

    4.键盘

            //键盘
            //Scanner
            InputStream is = System.in;
            int i = 0;
            //如果没有值 read()会返回-1
            while ((i = is.read()) != -1) {
                System.out.print((char) i);
            }
        }

    注意:

     流使用完成后一定要关闭!!

    我们可以调用close方法手动关闭

    也可以把流写在try(创建流){} catch(){}里JVM(java7)可以帮我们自动关闭流,前提是那个流实现了closeable接口

    IO流的原理

    为了安全,用户的进程不能直接操作底层的硬件,只能委派操作系统去执行。所以IO操作一定要借助内核去完成。

    二.字节流

    1.read方法加缓存数组

    目的:减少磁盘IO

    public class ReadBufferDemo {
        public static void main(String[] args) {
            FileInputStream fi = null; //创建管道
            FileOutputStream fo = null;
            try {
                //存放在磁盘中的文件 "Hello World"
                fi = new FileInputStream("d:/111/123.txt");
                fo = new FileOutputStream("d:/111/123_cp.txt");
                int i = 0;
                //创建一个数组 相当于缓存 可以减少 磁盘IO的次数
                byte[] buffer = new byte[3];
                //buffer [][0][]->[H][e][l] ->[1][o][ ]->[w][o][r] ->[1][d][r]
                while ((i = fi.read(buffer)) != -1) {
                    //在这个类例子中,原本需要和磁盘进行11次I0操作,我增加了buffer之后,只需要进行4次IO
                    System.out.println(new String(buffer,0,i));
                }
    
            } catch (IOException e) {
                e.printStackTrace();
            }
                //关闭流
        }
    }

    2.字节流解决乱码问题

    因为中文在UTF-8中占3个字节 所以我们一个一个字节的取一定会乱码,所以我们一次全取出来在输出就可以解决乱码的问题

        try(FileInputStream in = new FileInputStream(file);
                FileOutputStream out = new FileOutputStream(cp_file)) {
                int len = 0;
                //因为中文在UTF-8中占3个字节 所以我们一个一个字节的取一定会乱码
                byte[] buffer = new byte[1024];
                while ((len = in.read(buffer)) != -1) {
                System.out.println(new String(buffer,0,len));               
                }
            }catch (Exception e) {
            }

    3.缓冲字节流

    对比普通字节流和缓冲字节流复制文件test.zip大小约500000kb,所需时间的多少

    public class BufferDemo1 {
        //test.zip 大小500000kb
        static File file = new File("d:/111/test.zip");
        //普通字节流 加缓冲数组
        static void in_cp(File file, File cp_file) {
            try(FileInputStream in = new FileInputStream(file);
                FileOutputStream out = new FileOutputStream(cp_file)) {
                int len = 0;
                byte[] buffer = new byte[1024];//设置缓存区大小为1024
                while ((len = in.read(buffer)) != -1) {
                    //输出
                    out.write(buffer,0,len);
                }
            }catch (Exception e) {
            }
        }
    
        //缓冲字节流
        //普通字节流 加缓冲数组
        static void bin_cp(File file, File cp_file) {
            try(FileInputStream in = new FileInputStream(file);
                FileOutputStream out = new FileOutputStream(cp_file);
                BufferedInputStream bin = new BufferedInputStream(in);
                BufferedOutputStream bout = new BufferedOutputStream(out)) {
                int len = 0;
                byte[] buffer = new byte[1024];
                while ((len = bin.read(buffer)) != -1) {
                    //输出
                    bout.write(buffer,0,len);
                }
            }catch (Exception e) {
            }
        }
    
        public static void main(String[] args) {
            long star = System.currentTimeMillis();
            //普通字节流
            File cp1_file = new File("d:/111/test1.zip");
            in_cp(file, cp1_file);
            System.out.println("普通字节流花费时间"+(System.currentTimeMillis()-star));
    
            long star2 = System.currentTimeMillis();
            //缓冲字节流
            File cp2_file = new File("d:/111/test2.zip");
            bin_cp(file, cp2_file);
            System.out.println("缓冲字节流花费时间"+(System.currentTimeMillis()-star2));
        }
    
    }
    

    结果:

    普通字节流花费时间8096
    缓冲字节流花费时间1378

    因为BufferedInputStream 底层 采用的缓存数组大小为8192

     所以我想 试着把普通字节流的缓存数组 设为1024*8   发现他们的执行效率几乎相同

    结果:

    普通字节流花费时间1457
    缓冲字节流花费时间1484

    缓冲流的flush()方法的作用:

    因为缓冲流的缓冲数组为1024*8,当我们write的时候,只有当缓冲区填满了的时候才会写入,所以为了防止数据没填满缓冲区导致数据没写入,我们可以用到这个flush()方法,顺便提一下close方法里也会调用一次flush相当于刷盘操作。

    5.序列化和反序列化

    ObjectOutputStream实现序列化对象

        static void serialize(Object obj) {
            //ObjectOutputStream实现对象的序列化
            try(FileOutputStream fo = new FileOutputStream("d:/111/user");
                ObjectOutputStream obo = new ObjectOutputStream(fo)) {
                //把对象写入磁盘 相当于序列化
                obo.writeObject(obj);
            } catch (Exception e) {
            }
            System.out.println("序列化成功");
        }

    ObjectInputStream实现反序列化对象

        static void deserialize(File file) {
            try(FileInputStream fi = new FileInputStream(file);
                ObjectInputStream obi = new ObjectInputStream(fi)) {
                //把对象从磁盘读取 相当于反序列化
                User user = (User) obi.readObject();
                System.out.println("反序列化" + user);
            } catch (Exception e) {
            }
        }

    test

        public static void main(String[] args) {
            User user = new User("ws", 18);
            //序列化对象
            serialize(user);
            //反序列化
            deserialize(new File("d:/111/user"));
        }

    结果:

    序列化成功
    反序列化User{name='ws', age=18}

    三.字符流

    字符流体系与字节流相似,字符流中特别的就是字符转换流InputStreamRead,他是字节流转换字符流的桥梁,它还可以指定编码格式。

    四.网络IO

    1.Socket和ServerSocket

    模拟客户端与服务端通信

    ServerSoket

    public class ServerSoketDemo {
        final static int DEFAULT_PORT = 7486;
        public static void main(String[] args) {
    
            ServerSocket serverSocket = null;
            try {
                //创建一个监听
                serverSocket = new ServerSocket(DEFAULT_PORT);
                //等待客户端连接 accept()会阻塞  直到获取到客户端才会往下执行
                Socket socket = serverSocket.accept();
                System.out.println("客户端" + socket.getPort() +"已连接");
                //获取字节输入流getInputStream();
                //转换为缓存字符输入流
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String msg = null;
                msg = br.readLine();
                //接受客户端消息
                System.out.println("客户端发送消息:" + msg);
    
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( socket.getOutputStream()));
                //给客户端发送消息
                bw.write("我收到你的消息啦~\n");
                bw.flush();
    
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    ClienSocket

    public class ClienSocketDemo {
        final static int DEFAULT_PORT = 7486;
    
        public static void main(String[] args) {
            try {
                Socket socket = new Socket("localhost",DEFAULT_PORT);
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
                bw.write("我是客户端:clien-01\n");
                bw.flush();
                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String msg = br.readLine();
                System.out.println(msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    结果:

     

     2.BIO阻塞IO

    所以 传统的IO在网络的数据传输上有两部分的阻塞,一部分是连接阻塞,一部分是IO阻塞。

    可以利用线程池对其进行优化

        //创建线程池
        private final ExecutorService executorService = Executors.newCachedThreadPool();
        //创建监听
        ServerSocket serverSocket = null;
        try {
             serverSocket = new ServerSocket(port);
             System.out.println("开启服务");
             while(true) {
                 final Socket socket = serverSocket.accept(); //连接阻塞
                 System.out.println("客户端" + socket.getPort() +"已连接");
                 //把IO的逻辑交给多线程完成
                 executorService.execute(new ProcessorHandler(service, socket));
             }
     
         } catch (IOException e) {
                e.printStackTrace();
         }   
                //关闭serverSocket
    

    2.基于Socket手写实现RPC框架

    基于Socket手写RPC框架

    五.NIO(New IO) 

    简述:

            NIO 从JDK1.4提出的,本意是New l0,它的出现为了弥补I0的不足,提供了更高效的方式,针对于网络IO,他还可以非阻塞的模式进行网络IO.

    详细了解JAVA NIO

    展开全文
  • Java 网络IO简介

    2015-06-08 23:22:58
    Java 网络IO简介: bio nio aio
  • 网络模型——四种常见网络IO模型

    千次阅读 2021-03-13 19:26:41
    文章目录1.IO读写原理1.1 内核...网络IO模型一共介绍以下四种: 同步阻塞IO、同步非阻塞IO、IO多路复用和异步IO。 1.IO读写原理 文件的读写还是socket读写,在Java应用层开发,都是input或者output处理 用户程序进行IO操
  • 网络IO的理解

    千次阅读 2020-10-19 19:01:26
    Unix/Linux系统下IO主要分为磁盘IO,网络IO,我今天主要说一下对网络IO的理解,网络IO主要是socket套接字的读(read)、写(write),socket在Linux系统被抽象为流(stream)。 网络IO模型 在Unix/Linux系统下,IO分为两...
  • 简单理解网络IO

    千次阅读 2020-11-03 20:39:08
    简单理解网络IO 什么是网络IO? 首先用大白话的方式,方便自己理解 网络IO本质上也是IO的一种,一般的IO就是数据的输入输出,再简单来说,就是从一个地方,到另一个地方 在计算机中,能够存储数据的,一定是存储...
  • 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出的答案都可能不同,比如wiki,就认为asynchronous IO和non...
  • 四种常见网络IO模型

    千次阅读 2020-08-09 18:34:26
    网络IO模型一共有五种,同步阻塞IO、同步非阻塞IO、IO多路复用、信号驱动IO和异步IO。其中信号驱动IO并不常用,我们只要介绍其他四种。 首先我们需要了解一下网络IO的基本原理和一些基本的概念。 IO读写的基本原理 ...
  • 磁盘IO与网络IO

    2020-11-23 11:02:12
    网络IO主要延时由: 服务器响应延时 + 带宽限制 + 网络延时 + 跳转路由延时 + 本地接收延时 决定。(一般为几十到几千毫秒,受环境干扰极大) 所以两者一般来说网络IO延时要大于磁盘IO的延时。 ...
  • 网络IO模型简介

    千次阅读 2021-12-13 21:15:49
    网络IO模型一、基础知识点1、同步、异步、阻塞、非阻塞2、TCP连接2.1 建立连接3次握手2.2 断开连接4次挥手二、网络IO发展过程1、BIO(Blocking IO)1.1 客户端过多2、NIO2.1 NIO的弊端3、多路复用器3.1 SELECT 和 ...
  • 磁盘IO和网络IO

    千次阅读 2019-09-13 15:24:52
    4、IO访问方式 4.1 磁盘IO 具体步骤: 当应用程序调用read接口时,操作系统检查内核缓冲区中是否存在需要的数据,如果存在,就直接从内核缓存中直接返回,否则从磁盘中读取,然后缓存至操作系统的缓存中。 ...
  • 网络IO模型 Linux环境下的network IO 高清 目录 书签
  • 内存与IO,磁盘IO,网络IO

    千次阅读 2020-09-26 21:57:37
    3网络io 任何程序都有 0:标准输入 1:标准输出 2:报错输出 /proc/$$进入当前进程目录 $$表示当前bash的pid $BASHPID 也可以获取进程pid /proc/$$/fd 进入当前进程的文件描述符目录,可以查看打开哪些文件 ...
  • 详细介绍了Java 中常见的四种IO模型BIO、NIO、IO多路复用、AIO,以及select、poll、epoll系统函数。
  • Java面试10-网络IO模型详解

    千次阅读 2019-05-12 23:18:58
    Java面试专栏的第10篇,这篇博客 南国带你主要回顾一下在Java网络IO常见的几种模型 以及大名鼎鼎的Netty框架。 注意这里所讲的网络IO和我在Java面试09——IO知识大盘点 讲述的IO不一样,上一篇我们主要讲述的是文件...
  • IO网络通讯基础概念 首先要从冯诺依曼说起,由他提出的计算机体系结构: 计算器控制(CPU…) ——> 主存(内存…) ——> 输入输出(硬盘、网卡、显示器、键盘…) IO说白了就是输入输出,宏观角度讲可以分为IO...
  • 网络IO和磁盘IO详解

    千次阅读 2018-06-21 19:07:16
    1. 缓存IO 缓存I/O又被称作标准I/O,大多数文件系统的默认I/O操作都是缓存I/O。在Linux的缓存I/O机制中,数据先从磁盘复制到内核空间的缓冲区,然后从内核空间缓冲区复制到应用程序的地址空间。 读操作:操作系统...
  • (监控网络IO) CentOS安装: yum install -y iptraf-ng 运行: sudo iptraf-ng iostat (监控磁盘IO) CentOS安装: yum install sysstat -y 运行: watch -p iostat %user:CPU处在用户模式下的时间...
  • 网络IO之NIO

    万次阅读 2020-11-09 15:58:05
    网络IO之NIO NIO库是在JDK1.4中才引入,弥补了原来的I/O(BIO)的不足,它是一个高速的、面向块的I/O。 NIO有两层含义: 在java层面:nio称为new io,是一套全新的操作io的api。 在OS层面:nio称为no-blocking io,...
  • 在我们日常工作当中,经常会接触到程序读写磁盘、网络通信,这就是宏观上的网络IO。 IO分为磁盘IO和网络IO,比如我们使用Java提供的流读取磁盘时,会涉及到一些流操作,回顾一下,Java流可总结为下图: 也就是2...
  • linux监控CPU、磁盘IO、网络IO、磁盘容量、内存使用 CPU:vmstat ,sar –u,top 磁盘IO:iostat –xd,sar –d,top 网络IO:iftop -n,ifstat,dstat –nt,sar -n DEV 2 3 磁盘容量:df –h 内存使用:free ...
  • 深入浅出RPC---5、网络IO模型

    千次阅读 2022-02-23 14:52:37
    网络IO模型 有哪些网络IO模型 主要包含以下几种: 同步阻塞 IO(BIO) 同步非阻塞 IO(NIO) IO 多路复用 信号驱动IO 异步非阻塞 IO(AIO) 常用的是同步阻塞 IO 和 IO 多路复用模型。 什么是阻塞IO模型 IO多路...
  • Java IO:网络IO模型

    千次阅读 2016-02-18 17:25:13
    网络IO模型有5种,分别为:阻塞式IO,非阻塞式IO,IO复用,信号驱动式IO 和 异步IO。 一. 操作系统如何处理IO  Linux 会把所有的外部设备都看成一个文件来操作,对外部设备的操作可以看成是对文件的操作。我们对...
  • 非阻塞IO通过进程反复调用IO函数(多次系统调用,并马上返回);在数据拷贝的过程中,进程是阻塞的 我们把一个SOCKET接口设置为非阻塞就是告诉内核,当所请求的I/O操作无法完成时,不要将进程睡眠,而是返回一...
  • 【Spark2运算效率】第六节 影响生产集群运算效率的原因之网络IO前言问题概述案例结语跳转 前言 在磁盘IO速率和网络接口IO传输速率匹配的情况下,更快的网络IO能够极大提升Spark程序Shuffle过程中Executor交换数据的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 757,468
精华内容 302,987
关键字:

网络IO