了解IO 前需要了解这几个概念
- 用户空间和内核空间
- 进程切换
- 进程阻塞
- 文件描述
- 缓存I/O
这里就不做解释了,大家自行百度
Stevens在文章中一共比较了五种IO Model:
* blocking IO
* nonblocking IO
* IO multiplexing
* signal driven IO
* asynchronous IO
由signal driven IO在实际中并不常用,所以主要介绍其余四种IO Model。
-
阻塞IO
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据----等待。而在用户进程这边,整个进程会被阻塞。当kernel数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态便运行起来。
阻塞 IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被阻塞了。
import socket
sk =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(("127。0.0.1",8000))
sk.listen(5)
while 1:
conn,addr=sk.accept() #阻塞在这里,需要等待请求
while 1:
conn.recv(1024)
conn.send("hello ".encode("utf-8"))
-
非阻塞IO
当用户进程发出read操作时,虽然kernel中的数据还没有准备好,但是它并不会锁住用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。
import socket,time
sk =socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(("127。0.0.1",8000))
sk.listen(5)
sk.setblocking(False) #设置非阻塞
while 1:
try:
print("do0")
conn,addr=sk.accept()#没有请求不需要等待,直接退出到except,这里会反复监听是否有请求过来
print("do1")
conn.recv(1024)
print("do2")
conn.send("hello ".encode("utf-8"))
conn.close()
except Exception as e:
print("非阻塞------",e)
time.sleep(4)
-
IO多路复用
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
虽然select性能不及多线程IO,但是可以投同时监听多个对象。
import socket
import select
sk1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk1.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk1.bind(('127.0.0.1',8000))
sk1.listen(5)
sk1.setblocking(0)
inputs = [sk1,]
while True:
readable_list, writeable_list, error_list = select.select(inputs, [], inputs, 1)
for r in readable_list:
# 当客户端第一次连接服务端时
if sk1 == r:
print('accept')
request, address = r.accept()
request.setblocking(0)
inputs.append(request)
# 当客户端连接上服务端之后,再次发送数据时
else:
received = r.recv(1024)
# 当正常接收客户端发送的数据时
if received:
print('received data:', received)
# 当客户端关闭程序时
else:
inputs.remove(r)
sk1.close()
-
异步IO
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。
今天就讲到这里,下一篇给大家写一个FTP上传下载功能