网络IO模型

为了更好地了解IO模型,我们需要事先回顾下:同步、异步、阻塞、非阻塞

1.网络传输中的两个阶段 分别是 waitdata 和 copydata
send---copydata
recv---waitdata + copydata
记住这两点很重要,因为这些IO模型的区别就是在两个阶段上各有不同的情况。

2.阻塞IO
无论是线程 进程 还是线程 进程池 统统都是阻塞IO
应用程序 发送 系统调用---操作系统等待数据(wait)---数据准备好 return data

所以,blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了。

3.非阻塞IO
协程是一种非阻塞IO
server.setblocking(False)将阻塞修改为非阻塞

最直接体现 recv send accept 都不会阻塞 会立即执行
但是不能保证立马就有数据 没有数据抛出异常
我们需要手动捕获异常 捕获异常后可以处理别的任务
可以实现单线程并发的效果 但会大量占用CPU资源
while True:
pass

所以,在非阻塞式IO中,用户进程其实是需要不断的主动询问kernel数据准备好了没有。

4.多路复用
管理连接的一种方式
为什么使用它? 相对于非阻塞IO降低无用的系统调用
怎么管?
核心函数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步 表示可以写 为什么异常 只有一种可能客户端断开连接)

强调:
1. 如果处理的连接数不是很高的话,使用select/epoll的web server不一定比
使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。
select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。
2. 在多路复用模型中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,
整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。

结论: select的优势在于可以处理多个连接,不适用于单个连接

5.异步IO 网络IO+本地IO 都适用
IO包括 网络IO 本地IO
上面的三种IO模型描述的都是网络IO,不是本地IO的问题
解决的方案就是:
将同步的IO操作改成异步的IO操作 在IO期间 可以执行其他的任务
最终的解决方案就是协程 使用asyncio模块 该模快实现异步IO 内部使用协程实现

它的流程:
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,
当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。
然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,
当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。

socketserver
是什么? 对服务器端的socket的封装
封装了多线程 多进程 IO模型,支撑高并发 高并发 的socket套接字
为什么用? 简化代码
使用方法:
socketserver (forkingUDP forkingTCP windows无法使用)
核心类 ThreadingUDPServer ThreadingTCPServer
ThreadingTCPServer 实例化时 传入服务器地址 和 自定义的一个数据处理类
自定义类需要继承BaseRequestHandler类中需包含handle函数
对象调用serve_forever

TCP服务端
import socketserver

class MyHandler(socketserver.BaseRequestHandler):
def handler(self):
while True:
try:
data=self.request.recv(1024)
if not data:break
print(data.decode('utf-8'))
self.request.send(data.upper())
except ConnectionResetError:
break
self.request.close()

if __name__ == '__main__':
server=socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyHandler)
server.serve_forever()

UDP服务端
import socketserver

class MyHandler(socketserver.BaseRequestHandler):
def handle(self):
data,server=self.request
print(data.decode('utf-8'))
server.sendto(data.upper(),self.client_address)

if __name__ == '__main__':
server=socketserver.ThreadingUDPServer(('127.0.0.1',8080),MyHandler)
server.serve_forever()
原文地址:https://www.cnblogs.com/du-jun/p/9963303.html