• IO模型


    数据传输过程中经历的两个阶段:

    send:是将数据从应用程序内存copy到操作系统缓存,这个过程称之为copydata
    服务器要接受数据:recv 是从操作系统缓冲区copy数据到应用程序内存
    如果数据已经到达缓存区则直接copy 如果数据还没有到达会进入阻塞状态一直等待有数据发过来 等待的过程称之为 waitdata

    accept 三次握手 也会经历copy wait 阶段
    recvfrom wait完后copy
    sendto copy
    sendall copy

    IO模型
    模型即套路 是解决某个固定问题的方式方法
    IO模型即解决IO问题的方式方法
    IO指的输入输出,输入输出设备的速度 对比CPU而言是非常慢的,比如recv input等都是IO操作
    IO操作的最大问题就是会阻塞程序执行

    IO模型要解决的也仅仅是网络IO操作

    IO模型有以下几个
    1.阻塞IO
    socket默认就是阻塞的
    问题:同一时间只能服务一个客户端
    方法1:多线程
    优点:如果并发量不高 ,效率是较高的 因为每一个客户端都有单独的线程来处理
    弊端:不可能无限的开启线程 线程也需要占用资源

    方式2:多进程
    优点:可以多个CPU并行处理
    弊端:占用资源非常大 一旦客户端稍微多一点 立马就变慢了

    线程池:
    优点:保证了服务器正常稳定运行,还帮你负责创建和销毁线程以及任务分配
    弊端:一旦并发量超出最大线程数量,就只能等前面的运行完毕

    进程池:

    真正导致效率低的是阻塞问题 但是上述几个方法 并没有真正解决阻塞问题 仅仅是避开了阻塞问题

    协程:
    基于单线程并发
    优点:不需要创建一堆线程,也不需要在线程间做切换
    弊端:不能利用多核优势 单核处理器 性能也是由上限的 如果真的并发特别大 name处理速度回变慢

    import socket,os
    
    c=socket.socket()
    c.connect(('127.0.0.1',1688))
    
    
    while True:
        msg='%s 发来问候!'%os.getpid()
        if not msg:continue
        c.send(msg.encode())
        data=c.recv(1024)
        print(data.decode())
    client
    import socket
    from threading import Thread
    
    s=socket.socket()
    s.bind(('127.0.0.1',1688))
    s.listen()
    print('等待连接')
    
    
    def talking(c):
       while True:
           try:
               data=c.recv(1024)
               print(data.decode())
               if not data:
                   c.close()
                   break
               c.send(data.upper())
           except ConnectionResetError:
               c.close()
               break
    
    
    
    
    while True:
        c,addr=s.accept()
        print('连接成功')
        t=Thread(target=talking,args=(c,))
        t.start()
    server

    2.非阻塞IO
    即遇到IO操作也不导致程序阻塞,会继续执行 意味着即使遇到IO操作 CPU执行权也不会被剥夺 程序效率就变高了
    以下程序 占用CPU太高
    原因是 需要无线的循环 去向操作系统拿数据

    setblocking(False) 设置socket是否阻塞 默认为True
    import socket
    
    
    
    c=socket.socket()
    c.connect(('127.0.0.1',1688))
    print('connecting..')
    
    while True:
        msg=input('>>>:')
        if not msg:continue
        c.send(msg.encode())
        data=c.recv(1024)
        print(data.decode())
    client
    import socket,time
    s=socket.socket()
    s.bind(('127.0.0.1',1688))
    s.listen()
    #设置socket 是否阻塞 默认为True
    s.setblocking(False)
    #所有的客户端socket
    cs=[]
    #所有需要返回数据的客户端
    send_cs=[]
    
    
    while True:
        try:
            c,addr=s.accept()
            #三次握手
            print('连接成功')
            cs.append(c)#存储已经连接成功的客户端
        except BlockingIOError:
            # 没有数据准备 可以作别的事情
            # print("收数据")
            for i in cs[:]:
                try:
                    data=i.recv(1024)
                    if not data:
                        i.close()
                        cs.remove(i)
                    print(data.decode())
                    # 把数据和连接放进去
                    send_cs.append((i,data))
                    # c.send(data.upper()) # io
                    # send也是io操作  在一些极端情况下 例如系统缓存满了 放不进去 那肯定抛出
                    # 非阻塞异常  这时候必须把发送数据 单独拿出来处理 因为recv和send都有可能抛出相同异常
                    # 就无法判断如何处理
                except BlockingIOError:
                    continue
                except ConnectionResetError:
                    i.close()
                    # 从所有客户端列表中删除这个连接
                    cs.remove(i)
    
            for item in send_cs[:]:
                c,data=item
                try:
                    c.send(data.upper())
                    # 如果发送成功就把数据从列表中删除
                    send_cs.remove(item)
                except BlockingIOError: # 如果缓冲区慢了 那就下次再发
                    continue
                except ConnectionResetError:
                    c.close()# 关闭连接
                    send_cs.remove(item)# 删除数据
                    # 从所有客户端中删除这个已经断开的连接
                    cs.remove(c)
    server

    3.IO多路复用 *******
    也是单线程并发处理所有请求
    与非阻塞不同之处在于 不需要频繁不断发送系统调用
    只要等待select 选择出准备就绪socket然后进行处理即可
    如果说 没有任何一个socket 准备就绪 select就会阻塞住 这意味着效率降低了吗?
    并不是 服务器的工作就是处理一堆socket 既然没有socket需要处理 就不需要做任何操作了
    import socket
    
    c = socket.socket()
    c.connect(("127.0.0.1", 1688))
    print("connect....")
    
    while True:
        msg  = input(">>>:").strip()
    
        if not msg:continue
        c.send(msg.encode("utf-8"))
    
        data = c.recv(1024)
        print(data.decode("utf-8"))
    client
    """
        IO多路复用
        用一个线程来并发处理所有的客户端
    
        原本我们是直接问操作系统 要数据,
            如果是阻塞IO  没有数据就进入阻塞状态
            非阻塞IO   没有数据就抛出异常 然后继续询问操作系统
    
        在多路复用模型中,要先问select  哪些socket已经准备就绪   然后在处理这些已经就绪的socket
        既然是已经就绪 那么执行recv或是send 就不会在阻塞
    
        select模块只有一个函数就是select
        参数1:r_list 需要被select检测是否是可读的客户端  把所有socket放到该列表中,select会负责从中找出可以读取数据的socket
        参数2:w_lirt 需要被select检测是否是可写的客户端  把所有socket放到该列表中,select会负责从中找出可以写入数据的socket
        参数3:x_list 存储要检测异常条件 ....忽略即可
    
        返回一个元组 包含三个列表
            readables 已经处于可读状态的socket  即数据已经到达缓冲区
            writeables 已经处于可写状态的socket  即缓冲区没满 可以发送...
            x_list:忽略
    
        从可读或写列表中拿出所有的socket 依次处理它们即可
    
    """
    import socket
    import select
    
    s=socket.socket()
    s.bind(('127.0.0.1',1688))
    s.listen()
    # 在多路复用中 一旦select交给你一个socket 一定意味着 该socket已经准备就绪 可读或是可写
    # s.setblocking(False)
    r_list=[s]
    w_list=[]
    # 存储需要发送的数据 已及对应的socket  把socket作为key 数据作为value
    data_dic={}
    while True:
        readables,writeables,_=select.select(r_list,w_list,[])
        # 接收数据 以及服务器建立连接
        for i in readables:
            if i==s:# 如果是服务器  就执行accept
                c,_=i.accept()
                r_list.append(c)
            else:# 是一个客户端端 那就recv收数据
                try:
                    data=i.recv(1024)
                    if not data:#linux 对方强行下线或是 windows正常下线
                        i.close()
                        r_list.remove(i)
                        continue
                    print(data)
                    # 发送数据 不清楚 目前是不是可以发 所以交给select来检测
                    w_list.append(i)
                    data_dic[i]=data# 把要发送的数据先存在 等select告诉你这个连接可以发送时再发送
                except ConnectionResetError:# windows强行下线
                    i.close()
                    r_list.remove(i)# 从检测列表中删除
        # 发送数据
        for i in writeables:
            try:
                i.send(data_dic[i].upper())# 返回数据
                # data_dic.pop(i)
                # w_list.remove(i)
            except ConnectionResetError:
                i.close()
            finally:
                data_dic.pop(i)# 删除已经发送成功的数
                w_list.remove(i)# 从检测列表中删除这个连接  如果不删除 将一直处于可写状态
    server

    4.异步IO 爬虫阶段讲
    5.信号驱动 了解



    迭代过程中 不允许修改元素

    # li = [1,2,3,4,5]
    #
    # rm_list = []
    #
    # for i in li:
    #     rm_list.append(i)
    #
    # for i in rm_list:
    #     li.remove(i)
    #
    # print(li)
    
    
    li=[1,2,3,4,5]
    # print(id(li[:]))
    # print(id(li))
    # 用切片的方式 产生一个新的列表 新列表中元素与就列表完全相同
    # 遍历新列表 删除就列表
    for i in li[:]:
        li.remove(i)
    print(li)
  • 相关阅读:
    解决docker pull很慢的方法
    Linux 基础 Day1
    linux运维人员必须熟悉的运维工具汇总
    chrome 浏览器插件推荐
    只能运维主要职责
    Linux查看所有用户用命令
    ubuntu16.04 离线包安装docker
    2013-10
    ELK原理与介绍
    shell中各种括号的作用()、(())、[]、[[]]、{}
  • 原文地址:https://www.cnblogs.com/gengbinjia/p/10518715.html
Copyright © 2020-2023  润新知