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)

 

posted @ 2019-03-12 18:47  777ijBGly-  阅读(823)  评论(0编辑  收藏  举报