Python IO多路复用
IO多路复用
检测多个socket是否已经发生变化,(是否已经连接成功/是否已经获得数据)(可读/可写)
实现:
-
IO多路复用 select模块
操作系统检查socket是否发生变化,有三种模式:
- select:最多
1024
个socket;循环去检测 - poll:不限制监听socket个数;循环去检测(水平触发)
- epoll:不限制监听socket个数;回调方式(边缘触发)
python模块:
- select.select
- select.epoll
- select:最多
-
非阻塞
代码如下:
import socket
import select
client1 = socket.socket()
client1.setblocking(False) # 将原本链接阻塞,修改为非阻塞
# 百度创建链接,阻塞
try:
client1.connect(('www.baidu.com',80))
except BlockingIOError:
pass
client2 = socket.socket()
client2.setblocking(False) # 将原本链接阻塞,修改为不阻塞
# 百度创建链接,阻塞
try:
client2.connect(('www.sogou.com',80))
except BlockingIOError:
pass
client3 = socket.socket()
client3.setblocking(False) # 将原本链接阻塞,修改为不阻塞
# 百度创建链接,阻塞
try:
client3.connect(('www.oldboyedu.com',80))
except BlockingIOError:
pass
socket_list = [client1,client2,client3]
conn_list = [client1,client2,client3]
while True:
rlist,wlist,elist = select.select(socket_list,conn_list,[],0.005)
for sk in wlist:
if sk == client1:
sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
elif sk == client2:
sk.sendall(b'GET /web?query=alex HTTP/1.0\r\nhost:www.sogou.com\r\n\r\n')
else:
sk.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.oldboyedu.com\r\n\r\n')
conn_list.remove(sk)
for sk in rlist:
chunk_list = []
while True:
try:
chunk = sk.recv(8096)
if not chunk:
break
chunk_list.append(chunk)
except BlockingIOError:
break
boby = b''.join(chunk_list)
print('------->',boby)
sk.close()
socket_list.remove(sk)
if not socket_list:
break
著名异步框架:Twisted
基于事件循环实现的异步非阻塞框架
- 非阻塞:不等待
- 异步:执行完某个事物的时候,自动调用我给他的函数
python中开源 基于事件循环实现的异步非阻塞框架 Twisted
自定义基于事件循环的异步非阻塞代码示例:
import socket
import select
class Req():
def __init__(self,sk,func):
self.sock = sk
self.func = func
def fileno(self):
return self.sock.fileno()
class Nb():
def __init__(self):
self.conn_list = []
self.socket_list = []
def add(self,url,func):
client = socket.socket()
client.setblocking(False)
try:
client.connect((url,80))
except BlockingIOError:
pass
obj = Req(client,func)
self.socket_list.append(obj)
self.conn_list.append(obj)
def run(self):
while True:
rlist,wlist,elist = select.select(self.socket_list,self.conn_list,[],0.005)
for sk in wlist:
sk.sock.sendall(b'GET /s?wd=alex HTTP/1.0\r\nhost:www.baidu.com\r\n\r\n')
self.conn_list.remove(sk)
for sk in rlist:
chunk_list = []
while True:
try:
chunk = sk.sock.recv(8096)
if not chunk:
break
chunk_list.append(chunk)
except BlockingIOError:
break
boby = b''.join(chunk_list)
sk.func(boby)
sk.sock.close()
self.socket_list.remove(sk)
if not self.socket_list:
break
def baidu_response(data):
print('百度数据:',data)
def sogou_response(data):
print('sogou: ',data)
obj1 = Nb()
obj1.add('www.baidu.com',baidu_response)
obj1.add('www.sogou.com',sogou_response)
obj1.run()
提高并发方案
-
多进程
-
多线程
-
单线程异步非阻塞模块(Twisted) 著名框架scrapy框架(单线程并发)
返回的数据太多,解决方法
- 生产者与消费者模式
为什么异步非阻塞(面试题)
-
非阻塞: 不等待。
比如:创建socket对某个地址进行content,或者 获取接收数据recv时,默认都是阻塞的(连接成功或者接收数据),才会执行后续操作。
如果设置了
setblocking(False)
,以上两个过程就不再等待了。但是会报BlockingIOError
的错误,只要捕获它即可。 -
异步,执行完成之后自动执行回调函数或自动执行某些操作:
比如:做爬虫中向某个地址baidu.com发送请求,当请求执行完成后自动执行回调函数(通知)
为什么同步阻塞
-
阻塞:等
-
同步:按照顺序逐步执行
爬虫:
request.get(url)
,使用for循环默认就会一个一个url去爬取,这就是一个例子