IO

事件驱动

1.要理解事件驱动和程序,就需要与非事件驱动的程序进行比较。实际上,现代的程序大多是事件驱动的,比如多线程的程序,肯定是事件驱动的。早期则存在许多非事件驱动的程序,这样的程序,在需要等待某个条件触发时,
  会不断地检查这个条件,直到条件满足,这是很浪费cpu时间的。而事件驱动的程序,则有机会释放cpu从而进入睡眠态(注意是有机会,当然程序也可自行决定不释放cpu),当事件触发时被操作系统唤醒,这样就能更加有效地使用cpu.

2.再说什么是事件驱动的程序。一个典型的事件驱动的程序,就是一个死循环,并以一个线程的形式存在,这个死循环包括两个部分,第一个部分是按照一定的条件接收并选择一个要处理的事件,第二个部分就是事件的处理过程。程序的执
   行过程就是选择事件和处理事件,而当没有任何事件触发时,程序会因查询事件队列失败而进入睡眠状态,从而释放cpu。

3.事件驱动的程序,必定会直接或者间接拥有一个事件队列,用于存储未能及时处理的事件。

4.事件驱动的程序的行为,完全受外部输入的事件控制,所以,事件驱动的系统中,存在大量这种程序,并以事件作为主要的通信方式。

5.事件驱动的程序,还有一个最大的好处,就是可以按照一定的顺序处理队列中的事件,而这个顺序则是由事件的触发顺序决定的,这一特性往往被用于保证某些过程的原子化。

6.目前windows,linux,nucleus,vxworks都是事件驱动的,只有一些单片机可能是非事件驱动的。
View Code

 

 IO

 IO: 同步、异步、阻塞、非阻塞

 

 阻塞IO

 阻塞IO:只发生一次系统调用, 阻塞的时候不能做其它事情,一直等待

默认情况下所有的socket都是blocking

 

 

非阻塞IO

非阻塞IO: 发生多次系统调用, 数据处理会滞后。
linux下,可以通过设置socket使其变为non-blocking。(socket_obj.setblocking(False))
在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。
 
需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
缺点:
  1.循环调用recv()将大幅度推高CPU占用率
  2.任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。
 
 

 IO多路程复用

又称为事件驱动IO(event driven IO)

select,epoll,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。

 
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
 
注意:
    1. 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接
    2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
 
select监听fd变化的过程分析:
#用户进程创建socket对象,拷贝监听的fd到内核空间,每一个fd会对应一张系统文件表,内核空间的fd响应到数据后,就会发送信号给用户进程数据已到;
#用户进程再发送系统调用,比如(accept)将内核空间的数据copy到用户空间,同时作为接受数据端内核空间的数据清除,这样重新监听时fd再有新的数据又可以响应到了(发送端因为基于TCP协议所以需要收到应答后才会清除)。

import socket
import select


tcp_server = socket.socket() 
tcp_server.bind(('127.0.0.1', 8080))
tcp_server.listen(10)

rlist=[tcp_server,]
wlist=[]
wdata={}

while True:
    # tcp_server.accept()
    rl, wl, e = select.select(rlist,wlist,[], 3)  # 监听tcp_server
    # select.select(rlist,wlist,xlist[,timeout])
    # rlist -- wait until ready for reading
    # wlist -- wait until ready for writing
    # xlist -- wait for an ``exceptional condition
    # timeout -- timeout
    print(rl)
    for sock in rl:
        # conn, addr = sock.accept()
        # print(conn)
        print('hello')

    print('>>>')
select引子
import select
import socket
import time

# select 把socket放入 select中,然后每当有一个连接过来,把连接conn放入select模型里面去
port = 8080
ip = "127.0.0.1"
ss = socket.socket()
ss.bind((ip, port))
ss.listen(10)
read_list = [ss]
write_list = []
msg_list = dict()
while 1:
    # print('listen again')
    rlist, wlist, xlist = select.select(read_list, write_list, [], 5)
    for i in rlist:
        if i is ss:
            # 如果ss准备就绪,那么说明ss就可以接受连接了,当ss接受到连接
            # 那么把连接返回readlist
            conn, addr = i.accept()
            read_list.append(conn)
        # 如果不是socket对象,那么就是conn连接对象了,如果是conn连接对象,那么就代表有
        # 读入数据的变化,对应recv方法
        else:
            try:
                data = i.recv(1024)
                # 如果接受不到数据了 则说明连接已经关闭了
                if not data:
                    print('connecting close')
                    read_list.remove(i)
                    break
                # 我们去发送数据,但是我们要把conn准备好了再去发送
                # 所以首先把数据存在一个dict中msg_list,然后再等他准备好的时候
                # 再去发送
                msg_list[i] = [data]
                if i not in write_list:
                    write_list.append(i)
            except Exception:
                read_list.remove(i)

    for j in wlist:
        # 把对应各自的消息取出来
        msg = msg_list[j].pop()
        try:
            j.send(msg)
            # 回复完成后,一定要将outputs中该socket对象移除
            write_list.remove(j)
        except Exception:
            # 如果报错就所以连接或者已经断开了,那么我们就把他移出出去
            write_list.remove(j)
简单的使用select来进行客户端多连接
import selectors
import socket

sel = selectors.DefaultSelector()

def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    print('accepted', conn, 'from', addr)
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)

def read(conn, mask):
    data = conn.recv(1000)  # Should be ready
    if data:
        print('echoing', repr(data), 'to', conn)
        conn.send(data)  # Hope it won't block
    else:
        print('closing', conn)
        sel.unregister(conn)
        conn.close()

sock = socket.socket()
sock.bind(('localhost', 1234))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)

while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)
selectors示例

 

 

 异步IO

 

 

 

 

 

 

 

 
posted @ 2019-02-27 22:01  李小样  阅读(1286)  评论(0编辑  收藏  举报