基于协程的TCP程序
from gevent import monkey, spawn monkey.patch_all() import socket server = socket.socket() server.bind(('127.0.0.1', 8888)) server.listen() def talking(client): while True: try: data = client.recv(1024) if not data: client.close() break client.send(data.upper()) except ConnectionResetError: client.close() break def save(): while True: client, address = server.accept() spawn(talking, client) spawn(save).join()
import socket client = socket.socket() client.connect(('127.0.0.1', 8888)) while True: msg = input('>>>:').strip() if not msg: continue client.send(msg.encode('utf-8')) data = client.recv(1024) print(data.decode('utf-8')) # import socket # from concurrent.futures import ThreadPoolExecutor # from threading import current_thread # # pool = ThreadPoolExecutor(1000) # # # def task(): # client = socket.socket() # client.connect(('127.0.0.1', 8888)) # while True: # msg = '%s say hi ' % current_thread().name # if not msg: continue # client.send(msg.encode('utf-8')) # data = client.recv(1024) # print(data.decode('utf-8')) # # # for i in range(500): # pool.submit(task)
协程join的用法
from gevent import monkey, spawn monkey.patch_all() import time def task1(): print('task1 run') time.sleep(3) print('task1 over') def task2(): print('task2 run') time.sleep(3) print('task2 over') spawn(task1) spawn(task2) print('over') time.sleep(10)
IO模型
模型即套路是解决某个固定问题的方式方法
IO模型 即解决IO问题的方式方法
IO指的是输入输出,输入输出设备的速度对比CPU而言是非常慢的,比如recv input 等都属IO操作
IO操作最大的问题就是会阻塞程序执行
IO模型要解决的仅仅是网络IO操作
IO模型有以下几个
1.阻塞IO
socket默认就是阻塞的
问题:同一时间只能服务一个客户端
方法1:多线程
优点:如果并发量不高 效率是较高的 因为每一个客户端都有单独线程来处理
弊端:不可能无限的开启线程 线程也需要占用资源
方法2:多进程
优点:可以多个CPU并行处理
弊端:占用资源非常大,一旦客户端稍微多一点立马就变慢了
线程池:
优点:保证了服务器正常稳定运行,还帮你负责创建和销毁线程,以及任务分配
弊端:一旦并发量超出最大线程数量,就只能等前面的运行完毕
协程:
基于单线程并发
优点:不需要创建一堆线程,不需要在线程间做切换,没有数量限制
弊端:不能利用多核优势,单核处理器性能也是有上限的,如果真的并发特别da那么处理速度会变慢
总结:真正导致效率低的是阻塞问题 但是上述几个方法并没有真正解决阻塞问题 仅仅是避开了阻塞问题
import socket from threading import Thread server = socket.socket() server.bind(('127.0.0.1', 8888)) server.listen() def talking(client): while True: try: data = client.recv(1024) print('recv...') if not data: client.close() break # send是一个本地io操作 速度非常快 client.send(data.upper()) except ConnectionResetError: client.close() break while True: client, address = server.accept() print('accept') t = Thread(target=talking, args=(client,)) t.start()
import socket import os client = socket.socket() client.connect(('127.0.0.1', 8888)) print('connect...') while True: msg = '%s 发来问候 hello ' % os.getpid() if not msg: continue client.send(msg.encode('utf-8')) data = client.recv(1024) print(data.decode('utf-8'))
2.非阻塞IO
即遇到IO操作也不会导致程序阻塞,会继续执行
意味着即使遇到IO操作CUP执行权也不会被剥夺
程序效率就变高了
弊端:占用CUP太高
原因是需要无限的循环 去向操作系统拿数据
import socket c = socket.socket() c.connect(("127.0.0.1", 8888)) 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"))
import socket # import time server = socket.socket() server.bind(('127.0.0.1', 8888)) server.listen() # 设置socket是否阻塞 默认为True server.setblocking(False) # 所有的客户端socket cs = [] # 所有需要返回数据的客户端 send_cs = [] while True: # time.sleep(0.2) try: client, address = server.accept() # 三次握手 print('run accept') cs.append(client) # 存储已经连接成功的客户端 except BlockingIOError: # 没有数据准备 可以作别的事情 # print('收数据') for c in cs[:]: try: data = c.recv(1024) if not data: c.close() cs.remove(c) print(data.decode('utf-8')) # 把数据和连接放进去 send_cs.append((c, data)) # c.send(data.upper()) io # send也是io操作 在一些极端情况下 例如系统缓存满了放不进去 那肯定抛出 # 非阻塞异常 这时候必须把发送数据 单独拿出来处理 因为recv和send都有可能抛出相同异常 except BlockingIOError: continue except ConnectionResetError: c.close() # 从所有客户端列表中删除这个连接 cs.remove(c) # print('发送数据') 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)
删除列表中的某个数据
# 方式1 # 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) # 方式2 li = [1, 2, 3, 4, 5] print(id(li[:])) print(id(li)) # 用切片的方式 产生一个新的列表 新列表中元素与旧列表完全相同 # 遍历新列表 删除旧列表 for i in li[:]: li.remove(i) print(li)
3.IO多路复用 (最重要)
用一个线程来并发处理所有客户端
原本我们是直接问操作系统 要数据
如果是阻塞IO没有数据就进入阻塞状态
非阻塞IO 没有数据就抛出异常 然后继续询问操作系统
在多路复用模块中,要先问select 哪些socket已经准备就绪 然后再处理这些已经就绪的socket
既然是已经就绪 那么执行recv或是send就不会再阻塞
select模块只有一个函数就是select
参数1:r_list 需要被select检测是否是可读的客户端 把所有socket放到改列表中,select会负责从中
找出可以读取数据的socket
参数2:w_list 需要被select检测是否是可写的客户端 把所有socket放到改列表中,select会负责从中
找出可以写入数据的socket
参数3:x_list 存储要检测异常条件。。。忽略即可
返回一个元组 包含三个列表
readables 已经处于可读状态的socket即数据已经到达缓冲区
writeables 已经处于可写状态的socket 即缓冲区没满 可以发送。。。
x_list :忽略
从可读或写入列表中拿出所有的socket依次处理它们即可
也是单线程并发处理所有请求
与非阻塞不同之处在于不需要频繁不断发送系统调用
只要等待select选择出准备就绪socket然后进行处理即可
如果说没有任何一个socket准备就绪 select就会被阻塞住
import socket c = socket.socket() c.connect(("127.0.0.1", 8888)) 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"))
import socket # import time import select server = socket.socket() server.bind(('127.0.0.1', 8888)) server.listen() # 在多路复用中 一旦select交给你一个socket 一定意味着该socket已经准备就绪 # server.setblocking(False) r_list = [server] w_list = [] # 存储需要发送的数据 以及对应的socket 把socket作为key数据作为value data_dic = {} while True: readables, writeables, _ = select.select(r_list, w_list, []) # 接收数据 以及服务器创立连接 for i in readables: if i == server: # 如果是服务器就执行accept client, _ = i.accept() r_list.append(client) 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() # data_dic.pop(i) # w_list.remove(i) finally: data_dic.pop(i) # 删除已经发送成功的数据 w_list.remove(i) # 从检测列表中删除这个连接 如果不删除 将一直处于可写状态