经常使用的IO模型有4种:linux
不经常使用的有:windows
阻塞IO就是全程阻塞,其中,全程指的是等待数据和 数据从内核态拷贝到用户态。网络
全程阻塞就是以上两个步骤都阻塞。如图:并发
系统调用两个阶段:app
非阻塞IO是部分阻塞,
等待数据时不会阻塞,而是在固定时间内循环发起系统调用,请求不到作本身的事情,等待下次请求,
而数据从内核态拷贝到用户态仍是阻塞的。如图:异步
系统调用两个阶段:socket
优势:
等待数据无阻塞函数
缺点:
1.系统调用发送太多
2.数据不是即时接收的操作系统
ps:socket设置socket对象.setblocking(False)
设置阻塞状态为非阻塞code
IO多路复用:全程阻塞,监听多个连接
系统调用两个阶段:
实现IO多路复用的经常使用方式有:
基本原理:
经过select/poll/epoll函数不断轮询所负责的全部socket套接字,当某个socket套接字有数据到达,就通知用户进程。
特色:
就是单个process能够同时处理多个网络链接的IO,
ps:不一样的操做系统提供的函数不一样:
windows系统: select
linux系统: select、poll、epoll
系统调用经过select模块完成wait for data的工做
示例:
select监听多个socket对象(sock是socket对象),实现并发
r, w, e = select.select([sock,], [], []) # 等待连接 for obj in r: conn, addr = obj.accept()
示例升级:
inputs = [sock,] r, w, e = select.select(inputs, [], []) # inputs监听有变化的套接字 inputs=[sock,conn1,conn2,...] for obj in r: # 第一次[sock,] 第二次[conn1,] if obj == sock: # 若是返回的r = sock,说明有链接请求 conn, addr = obj.accept() inputs.append(conn) # inputs=[sock, conn1, conn2] else: # 不然,能够接收数据了 data = obj.recv(1024)
ps:关于文件描述符的tips(socket套接字)
1.每个套接字对象的本质就是一个非零整数,不会变(fb=4)
<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 51963)>
2.收发数据的时候,对于接收端而言,数据先到内核空间,而后copy到用户空间,同时内核空间的数据被清空
3.根据TCP协议,当发送端接收到接收端的确认信息后,清空内核空间的数据,不然不清空
select:
ps:select的特色也是其缺点,会致使效率降低:
poll:
epoll:
ps:回调函数
某一个函数或者某一个动做,成功完成以后,会触发的函数
selectors是select的升级版
selectors基于select模块实现IO多路复用,调用语句selectors.DefaultSelector()
建立selecters对象,特色是根据平台自动选择最佳IO多路复用机制,调用顺序:epoll > poll > select
import selectors import socket sel = selectors.DefaultSelector() # 根据平台自动选择最佳IO多路复用机制 def accept(sock, mask): conn, addr = sock.accept() sel.register(conn, selectors.EVENT_READ, read) # 将conn和read()注册到一块儿,当conn有变化时执行read() def read(conn, mask): try: data = conn.recv(1000) print(data.decode('utf8')) inputs = input('>>:').strip() conn.send(inputs.encode('utf8')) except Exception: sel.unregister(conn) conn.close() sock = socket.socket() sock.bind(('127.0.0.1', 8080)) sock.listen(100) sock.setblocking(False) # 设置为非阻塞IO sel.register(sock, selectors.EVENT_READ, accept) # 将sock和accept()注册到一块儿,当sock有变化时执行accept() while True: events = sel.select() # 监听 [(key1,mask1),(key2),(mask2)] for key, mask in events: func = key.data # 1 key.data就是accept # 2 key.data就是read obj = key.fileobj # 1 key.fileobj就是sock # 2 key.fileobj就是conn func(obj, mask) # 1 accept(sock,mask) # 2read(conn,mask)
只要系统调用中存在阻塞就是同步IO,
因此,阻塞IO、非阻塞IO、IO多路复用都是同步IO
全程无阻塞,实现复杂
系统调用两个阶段: