为了更好地了解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()