I/O模型

在网络环境下I/O 分为两步:

  1. 等待, 等待数据准备好,也就是到达内核的某个缓冲区
  2. 复制, 将数据从内核缓冲区,复制到应用进程缓冲区。

阻塞型I/O, 第一步和第二部都是阻塞的
非阻塞型I/O, 第一步是非阻塞的,但第二步是阻塞的、

想要提高I/O效率,需要将“等”的时间降低。

不同的I/O模型就是对上述两个阶段不同的处理,以上两个阶段之间的衔接也不同。

五种I/O模型

这四个属于同步I/O:阻塞I/O, 非阻塞I/O,信号驱动I/O, I/O复用
这一个属于异步I/O:异步I/O

所谓同步/异步,关注的是消息通知机制,调用后能否继续向后执行。
消息通知机制:
同步:等待对方返回消息
异步:被调用者通过状态通知或回调机制通知调用者 被调用者的运行状态

阻塞/非阻塞,关注调用者在等待结果返回之前所处的状态。
阻塞:blockIng 调用结果返回之前,调用者被挂起(只针对同步)
非阻塞:noblocking 调用结果返回之前,调用者不会被挂起。

组合:
同步-阻塞:
同步-非阻塞:
异步-非阻塞:

  1. 阻塞I/O


    image.png

    应用进程系统调用recvfrom, 但是直到数据包到达内核缓冲区并复制到应用进程缓冲区才返回。
    应用进程在这整段时间内都是阻塞的,一直到recvfrom返回成功指示后,应用进程才开始处理数据。 注意: 此处只调用了一次recvfrom系统调用。

from socket import *

s = socket(AF_INET, SOCK_STREAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 8080))
s.listen(5)
while 1:
    conn, addr = s.accept()
    while 1:
        try:
            data = conn.recv(1024)
            if not data:break
            conn.send(data.upper())
        except Exception:
            break
    conn.close()
s.close()

代码解析
主要两个地方处于阻塞状态:

  1. s.accept():该状态下server套接字等待操作系统把conn连接发给它,但是 操作系统一直在等待客户端链接,这个阶段处于阻塞状态。
  2. data = conn.recv(1024):该状态下conn套接字等待操作系统把数据拷贝给它,操作系统自己也需要等待客户端发送数据过来,这也使整个程序处于阻塞状态。
  1. 非阻塞I/O
    应用进程会首先把套接字设置成非阻塞的。


    image.png

    应用进程会一直对非阻塞描述符循环调用recvfrom,查看内核数据是否准备好,当有数据报准备好时,就进行拷贝操作,当没有数据报准备好时,也不阻塞程序,内核直接返回未准备就绪的信号,等待用户应用进程的下次轮询。

轮询对cpu来说时较大的浪费,一般只有在特定条件下使用。

from socket import *

s = socket(AF_INET, SOCK_STREAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.setblocking(False)
s.bind(('127.0.0.1', 8080))
s.listen(5)
conn_l = []
del_conn = []
while 1:
    try:
        conn, addr = s.accept()
        conn_l.append(conn)
    except Exception:
        for conn in conn_l:
            try:
                data = conn.recv(1024)
                conn.send(data.upper())
            except BlockingIOError:
                pass
            except ConnectionResetError:
        #将客户端断开的链接剔除
        for conn in del_conn:
            conn.close()
            del_conn.remove(conn)
        del_conn = [] 

代码解读
1.设置s.setblocking(False),套接字为非阻塞状态,这样server套接字不会等操作系统有数据才继续执行。
2.没有链接数据会抛一个异常,让程序继续向下执行,走的是通讯循环的逻辑。如果有数据则会将conn加到列表里面进行链接保存。
此种方式占用CPU过多,不推荐使用

  1. 复用型I/O
    前两种模型, 应用进程都是阻塞在recvfrom系统调用上。而I/O复用则是将阻塞状态转移到上一层,如select或poll等,阻塞在这两个系统调用的其中一个之上。


    image.png

以select为例分析。应用进程阻塞于select调用,等待数据报主备好,也就是数据到达内核的某个缓冲区,在调用recvfrom把数据进一步复制待应用进程缓冲区。

乍一看I/O复用没什么优势,只是多了一次系统调用,但是它可以同时监听多个文件描述符,这在高并发情况下有巨大优势。

from select import select
from socket import *

s = socket(AF_INET, SOCK_STREAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 8080))
s.listen(5)
select_l = [s, ]
while 1:
    selected_l, _, _ = select(select_l, [], [])
    print(selected_l)
    for selected_item in selected_l:
        if selected_item == s:
            conn, addr = selected_item.accept()
            select_l.append(conn)
        else:
            data = selected_item.recv(1024)
            selected_item.send(data.upper())

注意问题
select模块会自动过滤没有数据的套接字,有的话会将该套接字放入子集里面。
根据加入的套接字进行判断,是server套接字就取出链接和地址,是conn套接字就接收发送数据。
select模块工作是基于列表去筛选哪个套接字有数据,效率较低,而且有套接字监控数量的限制,而poll只是对套接字监控数量做了扩展,没有实现高效筛选套接字的机制。

知识点:

阻塞型I/O在阻塞中,除非被调用者返回结果,否则无法唤醒,但是通常使用Ctrl+C可以终止一些进程,就是使用可复用型I/O

一个进程无法直接监控多路I/O,但不能让其阻塞在一个I/O上。通过调用内核中的I/O复用器,帮助监控多个I/O,此时不是阻塞在调用者,而是阻塞在I/O复用器,只要多路I/O中有一个返回结果,就可以唤醒进程。

实现I/O多路复用的机制
select、poll、epoll
select(): 最多监控1024个I/O
poll(): 无限制
其中epoll克服了select和poll筛选效率低的问题,套接字数据有了就主动通知调用者进行处理,通过回调函数机制实现效率的大幅提升。但是只在linux平台下可以使用。这样selectors模块应运而生!它可以根据用户使用不同的平台来自动选择该使用select还是epoll.

4.信号驱动I/O
当描述符就绪后,内核也可以通过SIGIO信号通知应用进程。


image.png

首先需要开启套接字的信号驱动式I/O功能,并通过sigaction系统调用安装一个信号处理函数,该系统调用将立即返回,应用进程得以继续工作其他事。待数据准备好,已在内核缓存区时,内核就会为该进程产生一个SIGIO信号,随后就可以在信号处理函数中进行recvfrom系统调用,将数据从内核缓冲区复制到应用进程缓冲区,并返回成功指示。

这种模型最大的优势在于应用进程在等待数据报期间不被阻塞,主程序可以继续执行,只需在接受到来自信号处理函数的通知后开始recvfrom系统调用即可。

  1. 异步I/O
    满足POSIX规范的函数工作机制一般是:告诉内核启动某个操作,并让内核在某个操作完成后通知应用进程。


    image.png

这里调用aio_read函数,给内核传递描述符,缓冲区指针,缓冲区大小和文件偏移,并告诉内核当整个操作完成时如何通知应用进程。同时,该应用进程立即返回,在等待I/O完成期间,应用进程也不阻塞。

与信号驱动I/O主要区别在于:信号驱动I/O是 由内核通知应用进程何时启动I/O操作,而异步I/O模型则通知应用进程I/O操作何时完成。

总结:
可以看出,前四种模型的主要区别在第一阶段,第二阶段也就是数据从内核缓冲区复制到应用程序缓冲区这一阶段是相同的,应用程序在这期间都是阻塞的。 而异步I/O模型在这期间也不被阻塞,所有前4中模型也都是同步I/O模型。

可以看出,阻塞程度:阻塞IO>非阻塞IO>多路转接IO>信号驱动IO>异步IO,效率是由低到高的。

五种IO模型的对比如下表。

image.png

简要总结:

同步/异步:
    关注消息通知机制,调用后能否继续向后执行

    消息通知:
        同步:等待对方返回消息
        异步:被调用者通过状态,通知或回调机制通知调用者被调用者的运行状态。

阻塞/非阻塞
    关注调用者在等待结果返回之前所处的状态

    阻塞:blocking 调用结果返回之前,调用者被挂起(只针对同步)
    非阻塞:noblocking 调用结果返回之前,调用者不会被挂起,轮询 

    组合:同步-阻塞  同步-非阻塞  异步-非阻塞

一次文件I/O请求,都会由两阶段组成。
    第一步:等待数据,即数据从磁盘到内核内存
    第二部:复制数据,即数据从内核内存到进程内存

    阻塞型I/O : 第一步和第二步都是阻塞的
    非阻塞型I/O:第一步是非阻塞的,但第二步是阻塞的

复用型I/O:
    select():最多监控1024个I/O 
    poll():无限制
    阻塞型I/O在阻塞中,除非被调用者返回结果,否则无法唤醒,但是通常使用Ctrl+C可以终止一些进程,就是使用可复用型I/O
    一个进程无法直接监控多路I/O,但不能让其阻塞在一个I/O上。通过调用内核中的I/O复用器,帮助监控多个I/O,此时不是阻塞在调用者,而是阻塞在I/O复用器,只要多路I/O中有一个返回结果,就可以唤醒进程。


1. 同步,就是我调用一个功能,该功能没有结束前,我死等结果。
2. 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)
3. 阻塞,      就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。
4. 非阻塞,  就是调用我(函数),我(函数)立即返回,通过select通知调用者

同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞!

阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回!

阻塞和非阻塞是指当进程访问的数据如果尚未就绪,进程是否需要等待,简单说这相当于函数内部的实现区别,也就是未就绪时是直接返回还是等待就绪;

而同步和异步是指访问数据的机制,同步一般指主动请求并等待I/O操作完毕的方式,当数据就绪后在读写的时候必须阻塞(区别就绪与读写二个阶段,同步的读写必须阻塞),异步则指主动请求数据后便可以继续处理其它任务,随后等待I/O,操作完毕的通知,这可以使进程在数据读写时也不阻塞。(等待"通知")


函数说明:
recv和recvfrom

recv和recvfrom是可以替换使用的, 只是recvfrom多个两个参数,用来接收对端的地址信息,这个对于udp这种无连接的,可以很方便的进行回复,而如果在udp中使用recv,那么就不知该回复给谁了,若不需要回复,则是可以使用的。另外对于TCP是已经知道对端地址的,就没必要每次接受还多收一个地址,没有意义,地址可以在accept中取得。

对于recvfrom可同时应用于面向连接和无连接的套接字

recv一般只用在面向连接的套接字,几乎等同于recvfrom,只要将recvfrom的第五个参数设置为NULL.
recvfrom就比recv多了一个导航功能。


以linux下 tcp socket编程为例:

阻塞就是 recv/read的时候 socket接收缓冲区要是有数据就读, 没数据我就一直睡觉赖着不走,直到有数据来了读完我才走。send/write的时候,要是发送缓冲区满了,没有空间继续发送了我也一直睡觉赖着不走,直到发送缓冲区腾出足够的空间让我把数据全部塞到发送缓冲区里我才走。(当然如果你通过setsockopt设置了读写超时,超时时间到了还是会返回-1和EAGAIN,不再睡觉等待)


非阻塞就是recv/read的时候,要是接收缓冲区有数据我就读完,没有数据我直接带着返回的-1和EGAIN走人,绝不睡觉等待耽误时间。write/send的时候, 要是发送缓冲区有足够的空间,就立刻把数据塞到发送缓冲区去,然后走人,如果发送缓存区满了,空间不足,那直接带着返回的-1和EAGAIN走人。

###至于IO多路复用,首先要理解的是,操作系统为你提供了一个功能,当你的某个socket接收缓存区有数据可读,或者发送缓冲区有空间可写的时候,它可以给你一个通知。这样当配合非阻塞的socket使用时,只有当系统通知我哪个描述符可读了,我才去执行read操作,可以保证每次read都能读到有效数据而不做纯返回-1和EAGAIN的无用功。写操作类似。操作系统的这个功能通过select/poll/epoll之类的系统调用函数来使用,这些函数都可以同时监视多个描述符的读写就绪状况,这样,多个描述符的I/O操作都能在一个线程内完成,这就叫I/O多路复用,这里的“复用”指的是复用同一个线程。

至于事件驱动,其实是I/O多路复用的一个另外的称呼。

至于异步同步,我们常见的linux下的网络编程模型大部分都是同步io,以读操作为例,本质上都是需要用户调用read/recv去从内核缓冲区把数据读完再处理业务逻辑。异步io则是内核已经把数据读好了,用户直接处理逻辑。异步IO在linux下一般是用aio库

为什么epoll快?

比较一下Apache常用的select,和Nginx常用的epoll

select:
1、最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧 …
2、效率问题, select 每次调用都会线性扫描全部的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了。
3、内核 / 用户空间 内存拷贝问题,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了内存拷贝方法,在FD非常多的时候,非常的耗费时间。
总结为:1、连接数受限 2、查找配对速度慢 3、数据由内核拷贝到用户态消耗时间

epoll:
1、Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 查看。
2、效率提升, Epoll 最大的优点就在于它只管你“活跃”的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。
3、内存共享, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。

另一个版本的详细介绍

一、初步了解什么是I/O模型。

1.回顾,用户态与内核态。

操作系统位于应用程序和硬件之间,本质上是一个软件,它由内核以及系统调用组成。

内核:用于运行于内核态,主要作用是管理硬件资源。

系统调用:运行与用户态,为应用程序提供系统调用的接口。

操作系统的核心,就是内核,内核具有访问底层硬件设备的权限,为了保证用户无法直接对内核进行操作,并且保证内核的安全,所以就划分了用户空间和内核空间。

2.回顾进程切换。

如果说要实现进程之间的切换,那么进程需要有能力挂起,有能力恢复,这样才叫切换。

进程与进程之间的切换是由操作系统来完成的!

假如说有多个进程,其中有个进程正在cpu上运行,现在需要切换到另一个线程,之间都发生了什么?

第一步:保存cpu上下文,程序计数器,以及寄存器。

第二步:更新PCB信息,把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列,选择另一个进程执行,并更新其PCB。

第三步:更新内存管理的数据结构。

第四步:恢复处理器的上下文

这几步理解不了无所谓....其实主要就是想说明,进程和进程之间进行切换真的很耗资源!!!

3.回顾进程阻塞。

正在执行的进程,这个进程在等待数据,或者说,请求资源失败,或者,等待的某种事物没有发生。

系统就会将这个进程的状态改为阻塞(block)状态。

阻塞,也可以说是进程自己的一种主动行为。

只有进程处于运行状态的时候,才会获得cpu资源,如果当一个进程变为了阻塞状态,是不会对cpu资源造成占用的。(阻塞完全不占用cpu资源。)

4.回顾文件描述符。

个人觉得文件描述符这种东西,只有在unix,linux(各种类unix系统)上才会存在。

是一个用来引用文件的一个很抽象的概念。

文件描述符的表达形式,是一个非负数的整数。

我一直把文件描述符理解为一个索引,这个索引指向了每个进程打开的每个文件的一个记录表。

当一个应用程序或者一个进程,打开或者创建一个文件的时候,内核就会返回一个文件描述符。

linux的文件描述符一旦被耗尽,会导致一些应用程序无法正常运行,无法创建socket文件等....

5.缓存I/O。(数据在内核态和用户态之间来回复制)

拿socket通信来举例吧,两台机器之间通过socket来进行通信,当计算机A给计算机B发送一个数据的时候,是直接发送给计算机B的吗?其实并不是,计算机A在发送数据时,首先会从用户态转换为内核态,然后把数据交给自己的网卡,再对外进行发送。

接收端在接收数据的时候,由内核态转交给用户态。

数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓冲区拷贝到应用程序的地址空间。用户空间没法直接访问内核空间的。(可以理解为数据从内核态被复制到了用户态)

通过上面的例子,说明了缓存I/O是由缺点的,数据在传输的过程中,数据需要在应用程序地址空间(用户态),和内核空间(内核态)中进行来回的数据拷贝操作!对CPU和内存的开销都是非常大的!

6.I/O模型简单介绍。

I/O模型到底是什么?不同的I/O模型到底有什么区别?

就拿socket网路通信来举例吧,socket通信属于网络I/O,执行accept(等待连接)和recv(等待数据接收)时,会涉及到两个系统对象,一个是调用这个I/O操作的线程或者进程,另外就是系统内核。

等待数据的准备。

然后将数据从内核空间拷贝到进程中

下面总结了四种比较常用的I/O模型:

(1).blocking IO(阻塞IO)(unix/linux默认的一中I/O模型)

在linux和类unix系统下,所有的socket对象都是blocking I/O模型,下面是一个阻塞I/O模型的运行流程。

假设现在有两台主机,主机A和主机B要通过socket去通信,主机A现在执行了一个accept()要去等待主机B来连接,这个时候程序中的accepet(这个过程是在向操作系统索要数据!),就会发起一个系统调用“recvfrom”给内核,在主机A执行accept的时候,主机B有去连接主机A吗?主机B什么时候会去连接主机A?这些是未知的,这个时候,就出现了一个等待数据的现象,这个等待现象是操作系统的内核造成的,内核会去等待数据,这个等待的过程就是阻塞的!!主机A的代码就会一直卡在accept的位置,无法继续执行后面的内容(一直等在这)。

这个时候,主机B执行了connect,连接到了主机A,此时内核拿到了数据!(需要注意!此时拿到数据的是内核!)程序无法直接拿到放在内核空间的数据,所以接下来内核会把数据拷贝一份给用户地址空间。此时此刻!!我们的程序才会真正拿到这个数据!

这就是阻塞IO的一个运行流程。

如果说的再简单点儿:

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

所以,blocking IO的特点就是在IO执行的两个阶段都被block了。

(整个过程中只发起了一次系统调用)

(阻塞IO是同步的,)

(这种I/O模型效率偏低,但是数据可以得到及时的处理。)

(2).nonbloking IO (非阻塞IO)

其他的系统不太清楚,但是在linux系统下,可以通过设置socket(setblocking方法),可以改变socket的阻塞模型为非阻塞模型,非阻塞模型的运行流程如下:

还拿前面说的主机A和主机B通信的例子来说,主机A和主机B要通过socket去通信,主机A现在执行了一个accept()要去等待主机B来连接,这时会发起一个recvfrom的系统调用,此时如果kernel中还没有收到主机B的连接,或者说数据还没有准备好,这时不会去阻塞这个进程,而是返回一个error(IO阻塞异常)(主机A这端的程序不会一直等待,而是会直接返回结果)。此时用户的程序去做一个判断,判断这次recvfrom系统调用的结果是不是error,如果是error(I/O阻塞异常)则说明了数据还没有准备好,但是这个程序不会被阻塞,还可以去做点其他事情,也可以再次accept或者recv(发起recvfrom系统调用),一旦内核中的数据准备好了,并且再次收到了程序的系统调用,那么,马上就会把数据从内核的内存空间直接拷贝到用户的内存空间。

所以,使用非阻塞IO模型的情况下,用户的进程需要不停的询问内核,是否有把数据准备好。

在网络I/O中,使用非阻塞I/O模型,发起recvfrom这个系统调用后,进程没有被阻塞,内核直接返回结果给进程,如果数据没准备好会返回一个错误,进程在返回之后,可以干点别的事情,然后再发起recvform系统调用,一直重复上面的过程。

循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。

最后还有一点要注意!!!!非阻塞I/O模型,并不是全程无阻塞的,内核要给用户控件拷贝数据的时候,这个时候时间虽然非常短!!但这个过程一定是阻塞的!!!!

下面是个非阻塞模型的网络I/O示例:

服务端:

import time

import socket

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

sk.setsockopt

sk.bind(('127.0.0.1',6667))

sk.listen(5)

sk.setblocking(False) #设置非阻塞I/O模型

while True:

try:

    print ('waiting client connection .......')

    connection,address = sk.accept()   # 进程主动轮询

    print("+++",address)

    client_messge = connection.recv(1024)

    print(str(client_messge,'utf8'))

    connection.close()

except Exception as e:

    print (e)

    time.sleep(4)

客户端:

import time

import socket

sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

while True:

sk.connect(('127.0.0.1',6667))

print("hello")

sk.sendall(bytes("hello","utf8"))

time.sleep(2)

break

其实这种非阻塞I/O模型看起来效率很高,但是也有缺点:

首先,这种非阻塞的I/O模型会发送很多的系统调用。

其次,数据无法及时的被处理,先来看看上面那个例子,每次轮询内核数据的时候,如果发现内核没有准备好数据,需要sleep 4秒,在sleep的这4秒钟,刚sleep两秒,内核中的数据准备好了,但是这时sleep才刚执行了2秒,还有2秒没有sleep完,需要再等待两秒,才会从新去轮询检查一次内核的数据(也就是说,想拿到数据,还要再过两秒),所以说,数据没有办法及时得到处理。

(3).IO multiplexing(I/O多路复用)

I/O多路复用的好处就在于单个进程,可以同时处理多个网络连接的I/O,其实就是又select/epoll这两个函数去不断的轮询所有的socket,一旦有socket的状态发生变化,就会去通知用户进程(程序)。

I/O多路复用的基本运行流程图如下:

首先由select函数发起一个系统调用,这个系统调用也叫select,一但调用了这个select,整个进程就会进入阻塞状态,此时,内核回去检测,所有select负责的socket,当select负责的这些socket中,其中只要有一个socket的状态发生了改变,那么select就会立刻返回。用户就可以收到来自这个socket发来的数据。

其实select这个图和阻塞I/O很像,貌似没什么区别,但是,使用select是由优势的,select可以同时处理多个连接。

如果处理的并发连接数不是很高的情况下,使用这种IO多路复用,可能还没有多线程+IO阻塞的性能好,甚至可能会加大延迟。

注意!!IO多路复用select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。

在I/O多路复用的模式中,每一个socket,一般都设置为非阻塞,但是整个用户的进程其实是一直被阻塞的!和默认的I/O阻塞不同,这个阻塞是因为进程被select函Lock导致的,和I/O造成的阻塞其实不是一回事。

一旦select函数返回的结果中有内容可以拿了,那么进程就可以调用accpet或者recv将位于内核空间的数据copy到用户空间中。

只有在处理特别多的连接数时,才需要考虑使用select函数,如果只处理单个连接,无法体现出select的优势!

下面是select函数基本用法的一个示例:

服务端:

!/usr/local/bin/python2.7

-- coding:utf-8 --

import socket

import select

socket_obj = socket.socket()

socket_obj.bind(("127.0.0.1",8889))

socket_obj.listen(5)

while True:

r,w,e = select.select([socket_obj,],[],[],5) #用来检测socket_obj这个socket对象是否产生了变化,如果有变化,返回给变量r,(5代表5秒后,即使socket没有任何变化,代码也会向下执行!)

一旦这个socket产生了变化之后,变量r就有内容了。

for i in r:

    print "conn ok!"

print ('wait.....')

客户端:

!/usr/local/bin/python2.7

-- coding:utf-8 --

import socket

socket_client = socket.socket()

socket_client.connect(("127.0.0.1",8889))

while True:

inpt = raw_input(">>>").strip()

socket_client.send(inpt.encode("utf-8"))

data = socket_client.recv(1024)

print data.decode("utf8")

(也许有人很好奇,select检测socket对象的机制是什么样的,其实select使用的是一种名为“水平触发”的检测机制。关于边缘触发和水平触发,后面会有详细介绍。)

(4).asynchronous IO (异步I/O)

也是一个用的不太多的I/O模型,下面是运行流程图。

用户程序首先发起一个aio_read系统调用,发起系统调用后,用户程序就会继续做其他的事情,当内核接收到了aio_read这个系统调用后,会像非阻塞IO一样,立刻返回一个结果,此时内核会等待数据准备完成, 然后将数据拷贝到内存空间,拷贝结束后,内核会给用户进程发送一个信号,告诉用户的进程,数据拷贝结束。

注意!!!

说道这里,有人可能会把异步I/O模型和非阻塞I/O模型的概念混淆,其实这两个I/O模型的区别还是很大的。

在使用非阻塞I/O模型的时候,进程的大部分时间都不会被Block,但是进程要主动去检查内核中的数据是否准备好,如果数据准备好了,还需要进程去发起一个recvfrom来将数据从内核空间拷贝到用户空间。

异步I/O模型则完全不同,感觉更像是用户进程把I/O操作直接交给了内核,等内核做完I/O操作,再去通知用户进程,在这个过程中,用户不需要去检查内核是否有将数据准备好,并且也不需要主动去将内核空间的数据拷贝到用户空间。

来源:https://blog.51cto.com/suhaozhi/1927374

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,635评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,628评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,971评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,986评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,006评论 6 394
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,784评论 1 307
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,475评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,364评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,860评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,008评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,152评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,829评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,490评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,035评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,428评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,127评论 2 356

推荐阅读更多精彩内容