socket服务端实现并发、进程池/线程池、协程、IO模型
一.soccket服务端实现并发
网络编程服务端要满足一下三点要求:
- 1. 固定的ip和port
- 2. 24小时不间断提供服务
- 3. 能够实现并发
#服务端
import socket
from threading import Thread
"""
服务端:
1.固定的ip和port
2.24小时不间断提供服务
3.支持高并发
"""
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5) # 半连接池
def communicate(conn):
while True:
try:
data = conn.recv(1024) # 阻塞
if len(data) == 0:break
print(data)
conn.send(data.upper())
except ConnectionResetError:
break
conn.close()
while True:
conn,addr = server.accept() # 阻塞
print(addr)
t = Thread(target=communicate,args=(conn,))
t.start()
# 客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
info = input('>>>:').encode('utf-8')
if len(info) == 0:continue
client.send(info)
data = client.recv(1024)
print(data)
二.进程池和线程池
- 无论开线程还是开进程其实都消耗资源,开线程消耗的资源比开进程小
- 池: 为了减缓计算机硬件的压力,避免计算机硬件设备奔溃
虽然减轻了计算机硬件的压力,但是一定程度上降低了持续的效率
- 进程池/线程池:
为了限制开设的进程数和线程数,从而保证计算机硬件的安全
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
import os
# 示例化池对象
# 不知道参数的情况,默认是当前计算机cpu个数乘以5,也可以指定线程个数
pool = ProcessPoolExecutor(5) # 创建了一个池子,池子里面有20个线程
def task(n):
print(n, os.getpid())
time.sleep(2)
return n ** 2
def call_back(n):
print('我拿到了结果:%s' % n.result())
"""
提交任务的方式
同步:提交任务之后,原地等待任务的返回结果,再继续执行下一步代码
异步:提交任务之后,不等待任务的返回结果(通过回调函数拿到返回结果并处理),直接执行下一步操作
"""
# 回调函数:异步提交之后一旦任务有返回结果,自动交给另外一个去执行
if __name__ == '__main__':
# pool.submit(task,1)
t_list = []
for i in range(20):
future = pool.submit(task, i).add_done_callback(call_back) # 异步提交任务
t_list.append(future)
# pool.shutdown() # 关闭池子并且等待池子中所有的任务运行完毕
# for p in t_list:
# print('>>>:',p.result())
print('主')
三.协程
- 进程:资源单位(车间)
- 线程:最小执行单位(流水线)
- 协程:单线程下实现并发
1.协程完全是程序员自己想出来的的东西,通过代码层面自己检测io自己实现切换,
让操作系统误认为这个线程没有io
2.切换+保存状态一定可以提升程序效率吗?
按情况考虑:1.当任务是计算密集型的任务时,反而会降低效率
2.当任务是IO密集型任务是,可以提高运行效率
3.实现高并发:将单线程的效率提升到最高,多进程先开多线程,多线程下使用协程
3.1 gevent模块
一个spawn就是一个管理任务的对象
gevent模块不能识别它本身之外的IO行为
所以必须导入它内部封装的模块,可以帮助我们识别所有io行为
from gevent import monkey;monkey.patch_all() # 检测所有的IO行为
from gevent import spawn,joinall # joinall列表里面放多个对象,实现join效果
import time
def play(name):
print('%s play 1' %name)
time.sleep(5)
print('%s play 2' %name)
def eat(name):
print('%s eat 1' %name)
time.sleep(3)
print('%s eat 2' %name)
start=time.time()
g1=spawn(play,'刘清正')
g2=spawn(eat,'刘清正')
# g1.join()
# g2.join()
joinall([g1,g2])
print('主',time.time()-start) # 单线程下实现并发,提升效率
3.2 运用协程实现服务端客户端通信
链接和通信都是io密集型操作,我们要在这两者之间来回切换,就能实现并发效果
服务端检测连接和通信任务,客户端起多线程同时链接服务器
# 服务端
from gevent import monkey;monkey.patch_all()
from socket import *
from gevent import spawn
def communicate(conn):
while True:
try:
data = conn.recv(1024)
if len(data) == 0: break
conn.send(data.upper())
except ConnectionResetError:
break
conn.close()
def server(ip, port, backlog=5):
server = socket(AF_INET, SOCK_STREAM)
server.bind((ip, port))
server.listen(backlog)
while True: # 链接循环
conn, client_addr = server.accept()
print(client_addr)
# 通信
spawn(comunicate,conn)
if __name__ == '__main__':
g1=spawn(server,'127.0.0.1',8080)
g1.join()
# 客户端
from threading import Thread, current_thread
from socket import *
def client():
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
n = 0
while True:
msg = '%s say hello %s' % (current_thread().name, n)
n += 1
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
if __name__ == '__main__':
for i in range(500):
t = Thread(target=client)
t.start()
# 原本服务端需要开启500个线程才能跟500个客户端通信,现在只需要一个线程就可以扛住500客户端
# 进程下面开多个线程,线程下面再开多个协程,最大化提升软件运行效率
四. io模型
-
阻塞IO
-
非阻塞IO(服务端通信针对accept用s.setblocking(False)加异常捕获,cpu占用率过高)
-
IO多路复用
在只检测一个套接字的情况下,他的效率连阻塞IO都比不上。因为select这个中间人增加了环节。
但是在检测多个套接字的情况下,就能省去wait for data过程
-
异步IO