好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

python之模拟io模式

环境:python3.8

参考:

? ? https://segmentfault测试数据/a/1190000003063859( Linux ?io模式等,本文的原理图片来自这个 )

????https://blog.csdn.net/zhuangzi123456/article/details/84400108(select的使用)

????https://blog.51cto测试数据/linzb/1911468(以前的笔记)

本文所有模型的客户端程序如下:

import??socket
c=socket.socket()
c.connect(("127.0.0.1",456))
while?True:
????c.sendall(input(":").encode('utf-8'))
????print(c.recv(1024).decode('utf-8'))

民以食为天,让我们从吃饭说起。

假设你去食堂吃饭(取用户空间外的数据),你要做的事是什么?

排队取餐(发起系统调用)

??2.打饭阿姨等厨师的饭菜端上来(内核等数据)

? 3.油腻的抽风阿姨把饭打给你(内核拷贝数据)

这就是所谓的阻塞i/o模型啦

体现在代码就跟简单的socket程序一样

from?socket?import?*
backlog=10
buffsize=1024
server=socket(AF_INET,SOCK_STREAM)
server.bind(("127.0.0.1",5666))
server.listen(backlog)
clientsocket,?clientaddr?=?server.accept()
while?True:
????msg=clientsocket.recv(buffsize)
????print('client?msg:',?msg.decode('utf-8'))
????clientsocket.sendall(input('your?msg:').encode('utf-8'))

recv方法发起了一个系统调用,等待内核把数据拷贝到用户内存区,然后用户进程才能读取数据。

为什么要这么拷贝数据呢?

因为socket程序运行在用户态,而接受的数据被客户端发送到网卡,网卡是硬件,只有内核才有资格去操作硬件

这种模型会阻塞在两个地方:

用户在等待用户空间有数据(你在等阿姨把饭给你)

系统在等待取得数据(阿姨在等厨师做好饭)

这好比饭还有很久才熟,你却只能在那等着,不能出去玩,于是你想了个好办法:

你还是出去玩,但是隔一段时间回来问下:我的饭熟了吗?

这个就是所谓的不阻塞io

这里我利用socket模块的setblocking来模拟这个过程,当服务器收到客户端的链接的时会打印“hello”,其他时候隔3秒打印“waiting”,代码如下:

import?socket,select,time
s=socket.socket()
s.bind(("127.0.0.1",456))
s.listen(5)
s.setblocking(0)
while?1:
????try:
????????print?('waiting')
????????s.accept()
????????print('hello')
????except?Exception:
????????time.sleep(3)

显然,这种模型要自己大量的来回询问,所以你又想了个偷懒的办法:

色诱打饭阿姨,给阿姨留个号码,让她饭好了告诉你声,自己跑出去玩

这就是所谓的io复用:

给阿姨留号码又分两种:

????一种是匿名电话,每次阿姨都要询问这是哪个英俊的小伙子的xx餐好了[,收匿名电话的阿姨也分两类,有原则的(一次1024个,用select实现),无原则的(不限个数,用poll实现)]

????一种是留姓名的,阿姨省去轮询步骤,饭好了直接通知(用epoll实现),可以想象,这种效率是最高的

select实现如下:参考:https://blog.csdn.net/zhuangzi123456/article/details/84400108

import?socket,select,time
s=socket.socket()
s.bind(("127.0.0.1",456))
s.listen(5)
s.setblocking(0)
l=[s,]


while?True:
????r,w,err=select.select(l,[],[])
????for?i?in?r:
????????if?s==i:
????????????conn,addr=s.accept()
????????????conn.setblocking(0)
????????????l.append(conn)
????????else:
????????????print(i.recv(1024).decode('utf-8'))
????????????i.sendall('hello?sir'.encode('utf-8'))

debug:

????select监听的三个列表不能初始内容都为空,否则后续会报错(具体原因待查明)

epoll实现如下:(抄自https://my.oschina.net/SnifferApache/blog/776123)

import?socket,?select
s=socket.socket()
host="127.0.0.1"
port=8889
s.bind((host,port))

fdmap={s.fileno():s}
s.listen(5)
p=select.poll()
p.register(s)

while	True:
	events=p.poll()
	for?fd,event?in?events:
		if?fd==s.fileno():
			c,addr?=?s.accept()
			print?('got?connection?from?',addr)
			p.register(c)
			fdmap[c.fileno()]?=?c
		elif?event?&?select.POLLIN:
			data=fdmap[fd].recv(1024)
			if?not?data:
				print?(fdmap[fd].getpeername(),'disconnected')
				p.unregister(fd)
				del?fdmap[fd]
			else:	print?(data)

epoll实现如下:(抄自http://scotdoyle测试数据/python-epoll-howto.html)

import?socket,?select

EOL1?=?b'\n\n'
EOL2?=?b'\n\r\n'
response?=?b'HTTP/1.0?200?OK\r\nDate:?Mon,?1?Jan?1996?01:01:01?GMT\r\n'
response?+=?b'Content-Type:?text/plain\r\nContent-Length:?13\r\n\r\n'
response?+=?b'Hello,?world!'

serversocket?=?socket.socket(socket.AF_INET,?socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET,?socket.SO_REUSEADDR,?1)
serversocket.bind(('0.0.0.0',?8080))
serversocket.listen(1)
serversocket.setblocking(0)

epoll?=?select.epoll()
epoll.register(serversocket.fileno(),?select.EPOLLIN)

try:
???connections?=?{};?requests?=?{};?responses?=?{}
???while?True:
??????events?=?epoll.poll(1)
??????for?fileno,?event?in?events:
?????????if?fileno?==?serversocket.fileno():
????????????connection,?address?=?serversocket.accept()
????????????connection.setblocking(0)
????????????epoll.register(connection.fileno(),?select.EPOLLIN)
????????????connections[connection.fileno()]?=?connection
????????????requests[connection.fileno()]?=?b''
????????????responses[connection.fileno()]?=?response
?????????elif?event?&?select.EPOLLIN:
????????????requests[fileno]?+=?connections[fileno].recv(1024)
????????????if?EOL1?in?requests[fileno]?or?EOL2?in?requests[fileno]:
???????????????epoll.modify(fileno,?select.EPOLLOUT)
???????????????print('-'*40?+?'\n'?+?requests[fileno].decode()[:-2])
?????????elif?event?&?select.EPOLLOUT:
????????????byteswritten?=?connections[fileno].send(responses[fileno])
????????????responses[fileno]?=?responses[fileno][byteswritten:]
????????????if?len(responses[fileno])?==?0:
???????????????epoll.modify(fileno,?0)
???????????????connections[fileno].shutdown(socket.SHUT_RDWR)
?????????elif?event?&?select.EPOLLHUP:
????????????epoll.unregister(fileno)
????????????connections[fileno].close()
????????????del?connections[fileno]
finally:
???epoll.unregister(serversocket.fileno())
???epoll.close()
???serversocket.close()

现在好了,你已经能算好点过来等饭了,但是打饭还是需要时间呀,于是你:

献身阿姨,让阿姨在你来的时候就已经打好饭等你,你只需要取走,这就是所谓的异步io模型

显然这种模型是最为高效的,这里我能力有限不提供代码,最后推荐看:https://segmentfault测试数据/a/1190000003063859

查看更多关于python之模拟io模式的详细内容...

  阅读:41次