阻塞IO即为之前正常使用的IO 逻辑简单
非阻塞IO 可以把阻塞IO 设置为非阻塞IO,例如sockfd.setblocking(false)。如果设置成了非阻塞,无客户端连接时就会报BlockingIOError错误,通过try来捕获。通过循环来接受客户端连接
还可以设置超时检测,settimeout---sockfd.settimeout(5)超时报错
while True:
print("Waiting from connect...")
try:
connfd,addr = sockfd.accept()
except (BlockingIOError,timeout) as e:
sleep(2)
f.write("%s : %s\n"%(ctime(),e))
f.flush()
else:
print("Connect from",addr)
data = connfd.recv(1024).decode()
print(data)
IO多路复用:select poll epoll
select适用于window,Linux ,unix,poll:inux unix epoll:unix
select最多监控1024个IO
poll比select监控的IO数量多
epoll比select poll的效率高,监控的IO多,触发方式多
select:把要监测的IO放入rlist,wlist,xlist中,rlist适用于被动接受,wlist适用于主动写入,xlist出现错误出发(一般不用)。select()函数返回值也是3个列表,循环3个列表处理IO就绪事件
from select import select
from socket import *
s=socket()
s.bind(('0.0.0.0',8000))
s.listen(5)
rlist=[s]
wlist=[]
xlist=[]
while True:
rs,ws,xs=select(rlist,wlist,xlist)
for r in rs:
if r==s:
conf, addr = r.accept()
print('监听到',addr,'的链接')
rlist.append(conf)
else:
data=r.recv(1024)
print(data)
r.send(b'ok')
if not data:
r.close()
rlist.remove(r)
for w in ws:
pass
poll:from select import poll
用到的函数有:p=poll()(建立poll对象),p.register(IO对象,POLLIN|POLLERR)(注册IO事件),p.unregister(IO对象或文件描述符) (取消注册)“event & POLLIN” 按位与检查是否检测POLLIN事件(读IO事件)。events=p.poll()(阻塞等待监控IO事件发生)events为列表里面套元组[(fileno,event),()] 要自己做fileno和IO对象的映射字典,动态维护字典
from select import *
from socket import *
s=socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8887))
s.listen(3)
p=poll()
fdmap={s.fileno():s}
p.register(s,POLLIN)
while True:
events=p.poll()
for fileno,event in events:
if fileno==s.fileno():
conf,addr=s.accept()
p.register(conf,POLLIN|POLLERR)
fdmap[conf.fileno()]=conf
elif event & POLLIN:
data=fdmap[fileno].recv(1024).decode()
print(data)
if not data:
#客户端退出,unregister,字典删除,套接字关闭
p.unregister(fileno)
fdmap[fileno].close()
del fdmap[fileno]
continue
fdmap[fileno].send(b'ok')
epoll使用方法和poll一样,要加E
协程
asyncio模块
- 普通函数def之前加async关键字 async def get_request()...
- 函数调用生成协程对象c=get_request()
- 创建任务对象 task=asyncio.ensure_future(c),多任务则需创建tasks列表,把task append到tasks里。loop.run_until_complete(asyncio.wait(tasks))
- 创建事件循环对象loop=asyncio.get_event_loop()
- 将任务对象装载在事件循环对象中启动事件循环对象loop.run_until_complete(task)
- 如果函数有返回值,则给任务对象绑定回调函数task.add_done_callback(task_callback)
- 回调函数必须有一个参数,data=参数.result(),data则为函数的返回值
- await关键字:挂起发⽣阻塞操作的任务对象。 在任务对象表示的操作中,凡是阻塞操作的前⾯ 都必须加上await关键字进⾏修饰
如果用协程去获取网络请求就需要用到:aiohttp模块,aiohttp支持异步模块
在每⼀个with前加上async关键字
在阻塞操作前加上await关键字
async def get_request(url):
#requests是不⽀持异步的模块
# response = await requests.get(url=url)
# page_text = response.text
#创建请求对象(sess)
async with aiohttp.ClientSession() as sess:
#基于请求对象发起请求
#此处的get是发起get请求,常⽤参数:url,headers,params,proxy
#post⽅法发起post请求,常⽤参数:url,headers,data,proxy
#发现处理代理的参数和requests不⼀样(注意),此处处理代理使⽤proxy='http://ip:port'
多任务异步爬⾍的完整代码实现:
async with await sess.get(url=url) as response:
page_text = await response.text()
#text():获取字符串形式的响应数据
#read():获取⼆进制形式的响应数据
return page_text
第三方库模块:gevent模块
用到的函数:f=gevent.spawn(func,argv)(生成协程函数)gevent.joinall(list,[timeout])
- 导入monkey模块 from gevent import monkey
- 运行相应脚本 monkey.patch_socket()(很多脚本,需要自己选择自己需要的,或者patch_all())
- 脚本运行要在相应的模块导入之前
"""
gevent server 基于协成的tcp并发
思路 : 1. 每个客户函数端设置为协成
2. 将socket模块下的阻塞变为可以触发协程跳转
"""
import gevent
from gevent import monkey
monkey.patch_all() # 执行脚本,修改socket
from socket import *
def handle(c):
while True:
data = c.recv(1024).decode()
if not data:
break
print(data)
c.send(b'OK')
c.close()
# 创建tcp套接字
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind(('0.0.0.0',8888))
s.listen(5)
# 循环接收来自客户端连接
while True:
c,addr = s.accept()
print("Connect from",addr)
# handle(c) # 处理具体客户端请求
gevent.spawn(handle,c) # 协程方案