回顾
网络 IO模型
1.阻塞IO模型
多线程 多进程 线程池 进程池 全是阻塞IO
2.非阻塞IO
协程是一种非阻塞IO
1.setblocking(False) 将阻塞修改为非阻塞
2.一旦是非阻塞 在执行accept recv send 就会立马尝试读写数据 一旦数据没准备好就抛异常
3.捕获异常
4.如果没有异常说明数据准备好了 直接处理
5.捕获到异常 那就做别的事情
可以实现单线程并发的效果 会大量占用CPU资源
3.多路复用
将所有连接交给select来管理 管什么? 管哪个连接可以被处理
作为处理任务的一方事情变少了 不需要重复不断的问操作系统拿数据 而是等待select返回需要处理的连接
等待则意味着select是阻塞的
一 创建连接 和管理连接
1.创建服务器socket对象
2.将服务器对象交给select来管理
3.一旦有客户端发起连接 select将不在阻塞
4.select将返回一个可读的socket对象(第一次只有服务器)
5.服务器的可读代表有连接请求 需要执行accept 返回一个客户端连接conn 由于是非阻塞 不能立即去recv
6.把客户端socket对象也交给select来管理 将conn加入两个被检测的列表中
7.下一次检测到可读的socket 可能是服务器 也可能客户端 所以加上判断 服务器就accept 客户端就recv
8.如果检测到有可写(可以send就是系统缓存可用)的socket对象 则说明可以向客户端发送数据了
7 和 8 执行顺序不是固定的
二 处理数据收发
两个需要捕获异常的地方
1.recv 执行第7步 表示可以读 为什么异常 只有一种可能客户端断开连接
还需要加上if not 判断是否有数据 ;linux下 对方下线不会抛出异常 会收到空消息
2.send 执行第8步 表示可以写 为什么异常 只有一种可能客户端断开连接
异步IO 不仅仅指网络IO 也包括本地IO
非阻塞IO 和 多路复用 解决都是网络IO的阻塞问题
本地IO 可以通过子线程 或子进程 来避免阻塞 但是对子线程或子进程而言 依然会阻塞
最终的解决方案就是协程 asyncio 该模快实现异步IO 内部使用协程实现
4.异步IO
UDP客户端
import socket
c = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
addr = ("127.0.0.1",9999)
while True:
msg = input(">>>:")
c.sendto(msg.encode("utf-8"),addr)
print(c.recvfrom(1024)[0].decode("utf-8"))
基于TCP的Socketserver
import socketserver
from threading import current_thread
# fork linux下一个多进程接口 windows没有这接口
# 用于处理请求的类
class MyHandler(socketserver.BaseRequestHandler):
def handle(self):
print(self)
print(self.server) # 获取封装的服务器对象
print(self.client_address)# 客户端地址
print(self.request)# 获取客户端的socket对象
print(current_thread())
while True:
data = self.request.recv(1024)
print(data.decode("utf-8"))
self.request.send(data.upper())
server = socketserver.ThreadingTCPServer(("127.0.0.1",9999),MyHandler)
server.serve_forever()
基于UDP的Socketserver
import socketserver
from threading import current_thread
# fork linux下一个多进程接口 windows没有这接口
# 用于处理请求的类
class MyHandler(socketserver.BaseRequestHandler):
def handle(self):
print(self)
print(self.server) # 获取封装的服务器对象
print(self.client_address)# 客户端地址
print(self.request)# 是一个元祖 包含收到的数据 和服务器端的socket
# data,client = self.request
data = self.request[0]
print(data.decode("utf-8"))
self.request[1].sendto(b"i am server",self.client_address)
server = socketserver.ThreadingUDPServer(("127.0.0.1",9999),MyHandler)
server.serve_forever()
# ThreadingUDPServer 在初始化的时候创建了socket对象
# serve_forever() 将sockt注册到select(多路复用的)
# select中返回一个ready 如果为True则可以处理 _handle_request_noblock 内部创建了一个MyHandler的示例 调用了handler函数
# 使用了socket OOP 多线程
# 正常开发中 如果并发量不大 就是用socketserver
# 否则用协程
"""
使用时的区别:
ThreadingTCPServer
handler 在连接成功时执行
self.request 是客户端的socket对象
ThreadingUDPServer
handler 接收到数据时执行
self.request 数据和服务器端的socket对象
"""
客户端
import socket
c = socket.socket()
c.connect(("127.0.0.1",9999))
while True:
msg = input(">>>:")
c.send(msg.encode("utf-8"))
print(c.recv(1024).decode("utf-8"))
Event使用
"""
事件是什么?
某件事情发生的信号
用来干什么?
在线程间通讯 然而线程本来就能通讯
作用只有一个就是简化代码
线程间通讯的例子
服务器启动需要五秒
客户端启动后去连接服务器
去连接服务器必须保证服务器已经开启成功了
是否启动完成就是要通讯的内容
注意 Event线程通讯 仅仅用于简单的条件判断 说白了代替bool类型 和if判断
set() 将状态修改为True
wati() 等待状态为True才继续执行
"""
# import time
# from threading import Thread
# boot = False
# def server_task():
# global boot
# print("正在启动....")
# time.sleep(5)
# print("启动....成功")
# boot = True
#
# def client_task():
# while True:
# print("连接服务器....")
# time.sleep(1)
# if boot:
# print("连接成功")
# break
# else:
# print("error 连接失败 服务器未启动!!")
#
# t1 = Thread(target=server_task)
# t1.start()
#
# t2 = Thread(target=client_task)
# t2.start()
#
# t1.join()
# t2.join()
# 使用事件实现
import time
from threading import Thread,Event
event =Event()
def server_task():
print("正在启动....")
time.sleep(5)
print("启动....成功")
event.set()
def client_task():
event.wait() #一个阻塞的函数 会阻塞直到对event执行set函数为止
print("连接成功!")
t1 = Thread(target=server_task)
t1.start()
t2 = Thread(target=client_task)
t2.start()