介绍
网络应用需要处理解决的主要可以归为两大类问题:
- 网络I/O
- 数据计算
网络I/O的本质是socket的读取,socket在linux系统被抽象为流,I/O可以理解为对流的操作。这个操作又分为两个阶段:
- 等待流数据准备(wating for the data to be ready)
- 从内核向进程复制数据(copying the data from the kernel to the process)
对于流而言:
- 第一步通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区
- 第二步把数据从内核缓冲区复制到应用进程缓冲区
概念
同步&&异步
可根据一个函数调用之后,是否直接返回结果来判断:
- 如果函数调用之后挂起了,直到获得了结果它才返回,这是同步的方式
- 如果函数调用之后马上返回,等数据到达再通知函数,这是异步的方式
- 状态
- 通知
- 回调函数
阻塞&&非阻塞
- 函数如果让线程挂起不再往下执行,该函数是阻塞的
组合分析
举例:
去银行办理业务(存钱、取钱以及其他),假如我们有两种方式可以选择到达柜台办理业务
- 大家排成一条长队,银行依此按顺序给我们办理业务(同步)
- 我们打印一个单号,等银行通知改到我这个号码办理业务的时候,我们再去柜台(异步)
同步阻塞
- 效率最低
- 专心排队,排队期间不干其他任何事
异步阻塞
- 不用排队,等待通知号码(要自己去等),但是不能离开银行这个地方,要等待被通知。阻塞就阻塞在需要等待被通知,被通知之前不能离开
同步非阻塞
- 效率比较低
- 排队期间,还处理着别的事情,一边要看排没排到自己,一边还要忙别的
异步非阻塞
- 效率更高
- 不用排队,等待通知号码(不用自己等,到自己的号码了会有人来通知我们),期间可以做别的事情
解析
通常而言,同步阻塞,异步非阻塞。
什么情况是异步阻塞呢?
即函数调用之后并没有返回结果而注册了回调函数,非阻塞的情况下,函数也马上返回,可是如果此时函数不返回,那么此时就是阻塞的状态,等数据到达通知函数,依然是异步的过程。
模型分类
- 同步模型(synchronous I/O)
- 阻塞I/O(bloking I/O)
- 非阻塞I/O(non-blocking I/O)
- 多路复用I/O(multiplexing I/O)
- 信号驱动式I/O(signal-driven I/O)
- 异步I/O(asynchronous I/O)
阻塞I/O(bloking I/O)
在网络I/O的时候,进程发起
recvform
系统调用,然后进程就被阻塞了,什么也不干,直到数据准备好,并且将数据从内核复制到用户进程,最后进程再处理数据,在等待数据到处理数据的两个阶段,整个进程都被阻塞。不能处理别的网络I/O。
非阻塞I/O(non-bloking I/O)
在网络I/O时候,非阻塞I/O也会进行
recvform
系统调用,检查数据是否准备好,与阻塞I/O不一样,"非阻塞将大的整片时间的阻塞分成N多的小的阻塞, 所以进程不断地有机会 '被' CPU光顾"。
也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。
这个过程通常被称之为
轮询
。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
多路复用I/O(multiplexing I/O)
可以看出,由于非阻塞的调用,轮询占据了很大一部分过程,轮询会消耗大量的CPU时间。
多路复用有两个特别的系统调用
select
或poll
。select调用是内核级别的。
select轮询相对非阻塞的轮询的区别
select可以等待多个socket,当其中任何一个socket的数据准好了,就能返回进行可读,然后进程再进行
recvform
系统调用,将数据由内核拷贝到用户进程,当然这个过程是阻塞的。
多路复用有两种阻塞,select或poll调用之后,会阻塞进程,与第一种阻塞不同在于,此时的select不是等到socket数据全部到达再处理, 而是有了一部分数据就会调用用户进程来处理。如何知道有一部分数据到达了呢?监视的事情交给了内核,内核负责数据到达的处理。
多路复用的特点是通过一种机制一个进程能同时等待IO文件描述符,内核监视这些文件描述符(套接字描述符),其中的任意一个进入读就绪状态,select, poll,epoll函数就可以返回。
对于监视的方式,又可以分为
select
,poll
,epoll
三种方式。
异步I/O(asynchronous I/O)
相对于同步I/O,异步I/O不是顺序执行。用户进程进行
aio_read
系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。I/O两个阶段,进程都是非阻塞的。
本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ Linux 高性能服务器编程:高性能服务器架构一12/05
- ♥ Linux 高性能服务器编程:网络基础编程一11/27
- ♥ 51CTO:C++网络通信引擎架构与实现一09/09
- ♥ 创建socket环境:hello socket10/16
- ♥ Socket:创建TCP客户端10/17
- ♥ Linux 高性能服务器编程:Libevent12/19