数据传输过程中经历的两个阶段:
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)