Python知识点-IO模型

1.阻塞IO模型

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

普通的socket通信就是阻塞IO,blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。

2.非阻塞IO

如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error,就代表NO data 。从用户进程角度讲 ,它发起一个call后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是用户就可以在本次到下次再发起read询问的时间间隔内做其他事情。

所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有

#服务端
from socket import *
import time
s=socket(AF_INET,SOCK_STREAM)
s.bind(('127.0.0.1',8080))
s.listen(5)
s.setblocking(False) #设置socket的接口为非阻塞
conn_l=[]
del_l=[]
while True:
    try:
        conn,addr=s.accept()  #因为是非阻塞的没有连接会报错
        conn_l.append(conn)
    except BlockingIOError:
        for conn in conn_l:  #没有连接的时候就是循环已经请求过来的conn
            try:
                data=conn.recv(1024)  #因为非阻塞,如果也没有conn也会报错,如有有就继续一直循环连接池里是否有人发信息
                if not data: #有人断开连接
                    del_l.append(conn) #因为发送的数据为空了,说明这个连接已经断了,下次要删除这个连接否则会报错
                    continue
                conn.send(data.upper())
            except BlockingIOError:
                pass
            except ConnectionResetError:
                del_l.append(conn)

        for conn in del_l: #循环需要删除conn的列表
            conn_l.remove(conn) #从连接池里去除
            conn.close() #close
        del_l=[] #清空这个需要删除conn的列表,否则下去for 还会循环到

#客户端
from socket import *
c=socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8080))

while True:
    msg=input('>>: ')
    if not msg:continue
    c.send(msg.encode('utf-8'))
    data=c.recv(1024)
    print(data.decode('utf-8'))

3.IO多路复用 

判断非阻塞调用是否就绪如果 OS 能做,是不是应用程序就可以不用自己去等待和判断了,就可以利用这个空闲去做其他事情以提高效率。

select poll epoll的好处就在于单个process就可以同时处理多个网络连接的IO

所以OS将I/O状态的变化都封装成了事件,如可读事件、可写事件。并且提供了专门的系统模块让应用程序可以接收事件通知。这个模块就是select。让应用程序可以通过select注册文件描述符和回调函数。当文件描述符的状态发生变化时,select 就调用事先注册的回调函数。

select因其算法效率比较低,后来改进成了poll,再后来又有进一步改进,BSD内核改进成了kqueue模块,而Linux内核改进成了epoll模块。这四个模块的作用都相同,暴露给程序员使用的API也几乎一致,区别在于kqueue 和 epoll 在处理大量文件描述符时效率更高。

鉴于 Linux 服务器的普遍性,以及为了追求更高效率,所以我们常常听闻被探讨的模块都是 epoll 。

它的基本原理就是会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:

  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的优势在于可以处理多个连接,不适用于单个连接

#服务端
from socket import *
import select

s=socket(AF_INET,SOCK_STREAM)
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('127.0.0.1',8081))
s.listen(5)
s.setblocking(False) #设置socket的接口为非阻塞
read_l=[s,] #把服务端放入 一个列表 一会传给select
while True:
    #select()方法接收并监控3个通信列表, 第一个是所有的输入的data,就是指外部发过来的数据,第2个是监控和接收所有要发出去的data(outgoing data),第3个监控错误信息
    r_l,w_l,x_l=select.select(read_l,[],[]) #等待有人连接
    for ready_obj in r_l:
        if ready_obj == s:
            conn,addr=ready_obj.accept() #此时的ready_obj等于s
            read_l.append(conn)
        else:
            try:
                data=ready_obj.recv(1024) #此时的ready_obj等于conn
                if not data:
                    ready_obj.close()
                    read_l.remove(ready_obj)
                    continue
                ready_obj.send(data.upper())
            except ConnectionResetError:  #当接受的数据没有完成时,客服端断开了触发error
                ready_obj.close()
                read_l.remove(ready_obj)

#客户端
from socket import *
c=socket(AF_INET,SOCK_STREAM)
c.connect(('127.0.0.1',8081))

while True:
    msg=input('>>: ')
    if not msg:continue
    c.send(msg.encode('utf-8'))
    data=c.recv(1024)
    print(data.decode('utf-8'))

epoll适用于linux,好处在于能接受的文件描述符要更多,epoll采用的是事件通知机制,而不再是以轮询的方式挨个询问每个文件描述符的状态,节省cpu时间。

mac os 使用 kqueue 同epoll

selectors,它的功能与linux的epoll,还是select模块,poll等类似;实现高效的I/O multiplexing,  常用于非阻塞的socket的编程中,根据平台选出最佳的IO多路机制,比如在win的系统上他默认的是select模式而在linux上它默认的epoll。

模块定义了一个 BaseSelector的抽象基类, 以及它的子类,包括:SelectSelector, PollSelector, EpollSelector, DevpollSelector, KqueueSelector.    

另外还有一个DefaultSelector类,它其实是以上其中一个子类的别名而已,它自动选择为当前环境中最有效的Selector,所以平时用 DefaultSelector类就可以了,其它用不着。

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(1024)  # 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
        conn.close()


sock = socket.socket()
sock.bind(('localhost', 10000))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)
while True:
    events = sel.select()  #m默认阻塞,有活动连接就返回活动连接列表
    for key, mask in events:
        print(key) #SelectorKey,包含了fileobj注册的对象也就是socket对象,fd文件描述符,events 1 或者0 可读还是可写,data就是注册对象的的回调函数 accept
        print(key.data) #accept 函数
        print(mask)  # 1 2 代表的读 写
        print(key.fileobj) #及时绑定的sock对象
        callback = key.data   #accept
        callback(key.fileobj, mask)

如果不清楚以后用在什么操作系统下,尽量选择selectors,会自动选择是匹配最合适的epoll 还是select 

猜你喜欢

转载自www.cnblogs.com/ghx1/p/10146945.html
今日推荐