之前学到gevent 遇到IO操作,自动切换
对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:
- 1 等待数据准备 (Waiting for the data to be ready)
- 2 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况。
IO模型
- 阻塞IO
- 非阻塞IO
- IO多路复用(监听多个连接)
- 异步IO
- 驱动信号
1 阻塞IO blocking
默认的socket是阻塞的,任务是串行执行的
一个典型的读操作流程:
wait data和copy data的过程中都是在等待的,也就是阻塞的。内核态--用户态
操作系统操作的
数据存放在内核空间(数据缓冲区),操作系统把内核空间中的数据传到用户空间数据准备阶段
accept发起系统调用,用户态转换成内核态
等待数据wait for data、copy data from kernal to user 都是阻塞,就是全程阻塞,在没有到达用户态的时候,都是阻塞2 非阻塞IO nonblocking
可以通过设置socket的编程unblocking,sock.setblocking(True)
sock.setblocking(False) 这个没有连接,立刻报错
会立即返回是否已经连接了
wait data 不是阻塞的
从内核态拷贝数据 copy data到用户态的时候是阻塞的- 优点:不用等待数据 ,在发起系统调用的过程中是自己的时间
- 缺点:一直发起系统调用,拿到的数据不是实时的
3 IO多路复用 select
IO 多路复用的本质就是非阻塞IO,它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程
面试中的比较多
把等待数据的过程分成了两部分:
- select 替代看wait for data return readable代表收到数据
- recv已经获得了数据
select 可以连接多个套接字对象,
r,w,e = select.select([socket,],[],[])
socketserver是threading和select共同实现的,利用了多线程和IO多路复用
select是在监听,监听的是有变化的套机子
第一次监听的是sock对象,第二次连接的是conn
socket是一个文件描述符,fd是一个整数
select是基于IO操作,节省时间
基于IO多路复用的socket 多并发聊天
客户端select机制
import socketimport selectimport timesock = socket.socket()sock.bind(("127.0.0.1",8810))sock.listen(5)sock.setblocking(False)inputs = [sock,] # 监听的列表while 1: r,w,e = select.select(inputs,[],[]) # 监听有变化的套接字,inputs = [socket,conn1,conn2...] for obj in r: if obj==sock: # 第一次用[socket],第二次[conn] conn,addr = obj.accept() print("conn",conn) inputs.append(conn) # else: data = obj.recv(1024) print(data.decode("utf-8")) send_data = input(">>>") obj.send(send_data.encode("utf-8"))
客户端
import socketsock = socket.socket()sock.connect(("127.0.0.1", 8810))while 1: data = input(">>>") sock.send(data.encode("utf-8")) recv_data = sock.recv(1024) print(recv_data.decode("utf-8"))sock.close()
测试结果:
有两个通道,socket是建立连接的,conn是进行通信的管道。sock对象是不变的。
关于文件描述符(套接字对象):
- 是非零整数,不会变(fd=xxx,xxx是一个整数,filedescription)
- 收发数据的时候,对于接收端而言,数据先到内核空间,然后copy到用户空间,同时内核空间的数据清空(内核空间和数据空间都是内存中分配的)
- 发送端的时候,TCP的三次握手没有应答,数据不清空
IO多路复用总结
IO多路复用的特性:(监听多个连接)
- 全程阻塞 wait for data copy data
- 监听多个文件描述符
4 异步IO
全程无阻塞
实现起来复杂的最后总结:
阻塞IO,进程一直等待
非阻塞IO,copy data 的过程是阻塞的 IO多路复用,全程阻塞有阻塞的就是同步IO
(阻塞IO 、非阻塞IO(copy data),IO多路复用)没有阻塞的是异步IO
[^1]
[^2]