# django不是一个异步框架 # tornado是异步的web框架 # 处理每秒大量的请求 # 个人理解的IO:就是应用层与内核驱动层的交互,这个过程无论从应用层到内核中,还是驱动层等待硬件层的数据,都是需要时间的,这个过程是IO操作过程 # 五种IO Model # blocking IO 阻塞IO # nonblocking 非阻塞IO # IO multiplexing IO多路复用 # signal driven IO 信号驱动IO # asynchronous IO 异步IO # 在python中没有提供异步IO的机制,没有操作系统将数据直接给应用层的获取到数据的接口. 但是由很多python的异步框架 # 这种异步IO机制其实是很好的
# IO发生时涉及的对象和步骤,对于一个network IO,我们以read举例,他会涉及到两个系统对象,一个是调用这个IO的process(or thread),另一个就是系统内核。当一个read操作时,该操作会经历两个阶段 # 1.等待数据准备 # 2.将数据从内核拷贝到应用层
# 同步 提交一个任务之后要等待这个任务执行完毕才能继续执行其他的 # 异步 只管提交任务,不用等待该任务执行完毕就可以继续做其他事情 # 阻塞 运行状态 -> 阻塞状态 -> 就绪状态 -> 运行状态、一进程或线程阻塞则会进入阻塞状态休眠 # 非阻塞
1.非阻塞IO
# 非阻塞IO # import socket # sk = socket.socket() # sk.setblocking(False) # 设置非阻塞 # sk.bind(('127.0.0.1', 8080)) # sk.listen() # # try: # conn, addr = sk.accept() # 因为将套接字设置为了非阻塞,所以这里会报错,因为accept不允许为非阻塞性的,所以下面捕捉异常,有异常则直接pass # print('有客户端连接上来 ') # except BlockingIOError: # pass
2.阻塞IO
3.IO多路复用
# 在windows、linux上,有一个select专门提供IO多路复用的。 # poll机制 # linux上有 # epoll机制 # linux上有 # poll机制和epoll机制使用方法和select一样 # poll可以监听的对象比select可以监听的多。如果select能监听500,则poll能监听1000个类似这样 # poll和select都是操作系统去轮询机制的监听被监听的项,看是否有读操作等,随着监听列表增多,会导致效率变差 # epoll机制 # 给每一个被监听的对象都绑定了一个回调函数,当被监听的对象有监听事件后,会触发此监听对象绑定的回调函数这种机制比select和poll的轮询效率要高,高并发非常有用 # import selectors 这个模块会帮助你选择当前操作系统上最优的IO多路复用
3.1 IO多路复用中的select
服务端
import select # 内置的select模块,用于IO多路复用 import socket # select.select(rlist, wlist, xlist, timeout=None) # 参数是3个列表,一个监听超时时间 # rlist参数,表示监听读,直到监听到读或超时返回 # wlist参数,表示监听写,直到监听到写或超时返回 # xlist参数,表示监听条件 # timeout参数,监听超时时间 # 返回值有三个 sk = socket.socket() sk.bind(('127.0.0.1', 8080)) sk.setblocking(False) # 设置为非阻塞 sk.listen() read_lst = [sk] # 创建一个列表,想要监听谁,则将哪个对象放进来,这里开始先监听socket对象,当有人向这个socket发起连接的时候,select则会监听到返回一个socket while 1: # 调用select后操作系统会帮你监听三个监听列表,这里监听的是监听读列表 r_lst, w_lst, x_lst = select.select(read_lst, [], []) # 当监听到后,返回一个元组,元组中有三个元素,分别是rlist,wlist,xlist。这里rlist中最开始监听sk,因此当有客户端连接上来后,会监听有要被读的事件,这里会返回得到一个r_lst,r_lst中有一个sk对象 #print(r_lst) for i in r_lst: if i is sk: # 判断监听到的对象是否是sk对象 conn, addr = i.accept() # 此时直接sk.accpet()就会得到客户端连接和地址 read_lst.append(conn) # 将客户端连接符加入到监听列表中 else: # 如果监听到的不是sk对象, 这里的第二可能是监听到了客户端连接符有要被读的事件 msg = i.recv(1024) if msg == b'': # 当客户端连接关闭时,会接收到空的数据, i.close() # 因为客户端连接主动关闭,这里也要关闭下这个客户端连接 read_lst.remove(i) # 同时在监听列表中去除这个客户端连接符,不再去监听它 continue print(msg)
3.2 linux上更好的IO多路复用epoll、selectors选择当前系统最优的IO多路复用机制
服务端
# linux上的selectors IO多路复用机制,IO多路复用机制,默认选择系统最优,linux上肯定选择epoll import selectors from socket import * def read(conn, mask): ''' 将来要绑定的回调函数 :param conn: :param mask: :return: ''' try: data = conn.recv(1024) if not data: # 如果监听客户端的数据是空数据,则表示客户端连接关闭了 print('closeing', conn) sel.unregister(conn) # 在监听列表中去除这个客户端连接的监听 conn.close() # 同时服务端也关闭这个客户端连接描述符 return conn.send(data.upper() + b'SB') except Exception: # 该客户端连接描述符监听到异常事件,则直接关闭客户端连接 print('closing', conn) sel.unregister(conn) # 在监听列表中去除这个客户端连接的监听 conn.close() # 同时服务端也关闭这个客户端连接描述符 def accept(server_fileobj, mask): ''' :param server_fileobj: 接收到的socket :param mask: :return: ''' conn, addr = server_fileobj.accpet() # 得到客户端连接描述符 sel.register(conn, selectors.EVENT_READ, read) # 将conn客户端连接符注册到监听列表中,监听的是其读时间,绑定的回调函数是read if __name__ == '__main': sk = socket() sk.setblocking(SOL_SOCKET, SO_REUSEPORT, 1) sk.bind(('127.0.0.1', 8080)) sk.listen(5) sk.setblocking(False) sel = selectors.DefaultSelector() # 获取到当前操作系统最优的IO多路复用机制 sel.register(sk, selectors.EVENT_READ, accept) # 将socket对象注册到监听列表中,监听其读事件,当有读事件时绑定的回调函数accpet会被执行 # server_fileobj = socket(AF_INET, SOCK_STREAM) # server_fileobj.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1) # server_fileobj.bind(('127.0.0.1', 8080)) # server_fileobj.listen(5) # server_fileobj.setblocking(False) # sel.register(server_fileobj, selectors.EVENT_READ, accept) while True: events = sel.select() # 检测到所有的fileobj(监听列表中的所有对象,都是文件描述符),是否有完成wait data阶段。当监听到有事件时返回 for sel_obj, mask in events: callback = sel_obj.data # 第一次是callback = accpet 通过sel_obj.data就能拿到刚刚这个被监听对象的回调函数 callback(sel_obj.fileobj, mask) # 第一次是ccpet(server_fileobj, 1) 直接调用回调函数
客户端
import socket from threading import Thread def func(): sk = socket.socket() sk.connect(('127.0.0.1', 8080)) sk.send(b'hello') sk.close() if __name__ == '__main__': for i in range(20): Thread(target=func).start()
4.信号驱动IO
5.异步IO
6.五种IO模型的比较,个人觉得肯定还是异步IO好