day39
day39
traceback.print.exc()
捕获异常
join的注意点
IO模型
模型就是解决某个问题的套路
IO问题
输入输出
需要一个用吗来执行登陆操作,问题是用户名需要用户输入,输入需要耗时,如果输入没有完成,后续的逻辑无法继续,所以默认处理方式就是等待.
怎么等(将当前进程阻塞住,切换至其他进程执行,等到按下回车键,拿到用户名,再唤醒刚才的进程,将状态调整为就绪态),这个处理方案就称之为,阻塞IO模型
网络IO中必经的两个阶段
copy犹豫是本地IO,所以速度非常快,不是我们要解决的
################
存在的问题:
当执行到recv的时候,如果对方没有发送数据,程序阻塞,无法执行其他任务
解决方案:
多线程,多进程:
当客户端并发量非常大的时候,服务器资源就无法开启新的线程或者进程,如果不对数量加以限制,服务器就容易崩了,
线程池或者进程池
1.限制了数量,保证服务器正常运行,但是问题是如果客户端都处于阻塞状态,这些线程,就业阻塞了.
协程:
使用一个线程处理所有客户端,当一个客户端处于阻塞状态时,就可以切换到其他客户端任务
非阻塞IO模型
阻塞IO模型在执行recv和accept的时候,都需要经历一个wait data.
非阻塞IO模型在执行recv和accept的时候不阻塞,会往下执行
代码????
非阻塞IO模型(还没全)
服务器
import socket
server = socket.socket()
server.bind(("127.0.0.1",1688))
server.listen()
server.setblocking(False)
clients = []
while True:
try:
client,addr = server.accept()
clients.append(client)
except BlockingIOError as e:
# print(e)
for c in clients[:]:
try:
data = c.recv(2048)
c.send(data.upper())
except BlockingIOError as e:
print("不需要处理")
except ConnectionResetError as e:
clients.remove(c)
# print("==============",len(clients))
如何使用:
将server的blocking设置为False,非阻塞
存在的问题
这样一来,进程效率非常高,没有任何的阻塞
但是很多情况下,并没有很多数据需要处理,但是我们的进程也需要不停的问操作系统,所以会导致CPU占用过高.并且这个 是 无意义的
多路复用
多个socket对象,一个socket就是一个传输通道
复用:就是同一个线程处理所有socket.
原理,在非阻塞IO模型中,我们需要不断的询问操作系统,是否有数据需要处理,造成资源浪费.
多路复用,使用select,来检测是否与有socket,可以被处理
#########多路复用代码初级
select参数:
参数1:rlist,里面存储需要被检测是否可读的(是否可以执行recv)socket对象
参数2:wlist,里面存储需要被检测是否可写的(是否可以执行send)socket对象
参数3:xlist,就是存储需要关注的异常条件
参数4:timeout超时时间,超过一定时间,还是没有可以被处理的socket,就返回空列表
返回值 三个列表
1,已经有数据到达的socket对象
2.可以发送数据的socket对象.怎么叫可以发,就是缓冲区没满
3不管
import socket
import select
server = socket.socket()
server.bind(("127.0.0.1",1888))
server.listen()
rlist = [server,]
wlist =[]
while True:
readable_list,wirteable_list,_ = select.select(rlist,wlist,[])
print(readable_list,wirteable_list)
for soc in readable_list:
if soc == server:
client,addr = server.accept()
rlist.append(client)
else:
data = soc.recv(2048)
if not data:
soc.close()
rlist.remove(soc)
continue
soc.send(data.upper())
############多路复用代码终极
import socket
import select
server = socket.socket()
server.bind(("127.0.0.1",1688))
server.listen()
# server.setblocking(False)
rlist = [server,] # 将需要检测(是否可读==recv)的socket对象放到该列表中
# accept也是一个读数据操作,默认也会阻塞 也需要让select来检测
# 注意 select最多能检测1024个socket 超出直接报错 这是select自身设计的问题 最终的解决方案epoll
wlist = [] # 将需要检测(是否可写==send)的socket对象放到该列表中
# 只要缓冲区不满都可以写
msgs = [("socket","msg")] # 存储需要发送的数据 等待select 检测后 在进行发送
print("start")
while True:
readable_list,writeable_list,_ = select.select(rlist,wlist,[]) # 会阻塞等到 有一个或多个socket 可以被处理
print("%s个socket可读" % len(readable_list),"%s个socket可写" % len(writeable_list))
"""
readable_list 中存储的是已经可以读取数据的socket对象 可能是服务器 可能是客户端
"""
# 处理可读列表
for soc in readable_list:
if soc == server:
# 服务器的处理
client,addr = server.accept()
#将新连接的socket对象 加入到待检测列表中
rlist.append(client)
else:
try:
# 客户端的处理
data = soc.recv(2048)
if not data:
soc.close()
rlist.remove(soc) # 如果对方下线 关闭socket 并且从待检测列表中删除
continue
# 不能直接发 因为此时缓冲区可能已经满了 导致send阻塞住, 所以要发送数据前一个先这个socket交给select来检查
# soc.send(data.upper())
if soc not in wlist:
wlist.append(soc)
# 将要发送的数据先存起来
msgs.append((soc,data))
except ConnectionResetError:
soc.close()
# 对方下线后 应该从待检测列表中删除 socket
rlist.remove(soc)
wlist.remove(soc)
# 处理可写列表
for soc in writeable_list:
# 由于一个客户端可能有多个数据要发送 所以遍历所有客户端
for i in msgs[:]:
if i[0] == soc:
soc.send(i[1])
# 发送成功 将这个数据从列表中删除
msgs.remove(i)
# 数据已经都发给客户端 这个socket还需不需要检测是否可写,必须要删除
wlist.remove(soc) # 否则 只要缓冲区不满 一直处于可写 导致死循环