python实现并发服务器实现方式(多线程/多进程/select/epoll)

 

并发服务器开发

并发服务器开发,使得一个服务器可以近乎同一时刻为多个客户端提供服务。实现并发的方式有多种,下面以多进程,多线程,IO多路复用等方式实现并发。这里使用网络编程中的TCP服务器和客户端通信为例子。

多进程并发阻塞

利用进程把客户端和服务器进行管理,当有新的客户端连接到服务器时,就创建一个新的进程来管理,通过操作系统的调度,从而实现了并发的操作

  1. from multiprocessing import Process
  2. from socket import *
  3. def recv_data(new_socket, client_info):
  4. print("客户端{}已经连接".format(client_info))
  5. # 接受数据
  6. raw_data = new_socket.recv(1024)
  7. while raw_data:
  8. print(f"收到来自{client_info}的数据:{raw_data}")
  9. raw_data = new_socket.recv(1024)
  10. new_socket.close()
  11. def main():
  12. # 实例化socket对象
  13. socket_server = socket(AF_INET, SOCK_STREAM)
  14. # 设置端口复用
  15. socket_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
  16. # 绑定IP地址和端口
  17. socket_server.bind(("", 7788))
  18. # 改主动为被动,监听客户端
  19. socket_server.listen(5)
  20. while True:
  21. # 等待连接
  22. new_socket, client_info = socket_server.accept()
  23. p = Process(target=recv_data, args=(new_socket, client_info))
  24. p.start()
  25. # 多进程会复制父进程的内存空间,所以父进程中new_socket也必须关闭
  26. new_socket.close()
  27. if __name__ == '__main__':
  28. main()

多线程并发阻塞

多线程和多进程类似,只是线程间共享内存空间,要注意变量的管理

  1. from threading import Thread
  2. from socket import *
  3. def recv_data(new_socket, client_info):
  4. print("客户端{}已经连接".format(client_info))
  5. # 接受数据
  6. raw_data = new_socket.recv(1024)
  7. while raw_data:
  8. print(f"收到来自{client_info}的数据:{raw_data}")
  9. raw_data = new_socket.recv(1024)
  10. new_socket.close()
  11. def main():
  12. # 实例化socket对象
  13. socket_server = socket(AF_INET, SOCK_STREAM)
  14. # 设置端口复用
  15. socket_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
  16. # 绑定IP地址和端口
  17. socket_server.bind(("", 7788))
  18. # 改主动为被动,监听客户端
  19. socket_server.listen(5)
  20. while True:
  21. # 等待连接
  22. new_socket, client_info = socket_server.accept()
  23. p = Thread(target=recv_data, args=(new_socket, client_info))
  24. p.start()
  25. # 多线程共享一片内存区域,所以这里不用关闭
  26. # new_socket.close()
  27. if __name__ == '__main__':
  28. main()

多路复用IO---select模型

在操作系统层面上,系统提供了一个select接口,它会轮询给定的文件描述符状态,如果其中有描述符的状态改变,select()就会返回有变化的文件描述符。

  1. from socket import *
  2. import select
  3. # 实例化对象
  4. socket_server = socket(AF_INET, SOCK_STREAM)
  5. # 绑定IP和端口
  6. socket_server.bind(("", 7788))
  7. # 将主动模式改为被动模式
  8. socket_server.listen(5)
  9. # 创建套接字列表
  10. socket_lists = [socket_server]
  11. # 等待客户端连接
  12. while True:
  13. # 只监听读的状态,程序阻塞在这,不消耗CPU,如果列表里面的值读状态变化后,就解阻塞
  14. read_lists, _, _ = select.select(socket_lists, [], [])
  15. # 循环有变化的套接字
  16. for sock in read_lists:
  17. # 判断是否是主套接字
  18. if sock == socket_server:
  19. # 获取新连接
  20. new_socket, client_info = socket_server.accept()
  21. print(f"客户端:{client_info}已连接")
  22. # 添加到监听列表中
  23. socket_lists.append(new_socket)
  24. else:
  25. # 不是主客户端,即接收消息
  26. raw_data = sock.recv(1024)
  27. if raw_data:
  28. print(f"接收数据:{raw_data.decode('gb2312')}")
  29. else:
  30. # 如果没有数据,则客户端断开连接
  31. sock.close()
  32. # 从监听列表中删除该套接字
  33. socket_lists.remove(sock)

优点:良好的跨平台支持

缺点:1.监测的文件描述符数量有最大限制,Linux系统一般为1024,可以修改宏定义或者内核进行修改,但是会造成效率低下;2.对文件描述符采用轮询机制,每个文件描述符都会询问一遍,这样很消耗CPU时间

多路复用IO---epoll模型

为了解决select轮询机制造成的效率低下问题,则引入了epoll接口。相较于select的两大优势。1.没有文件描述符最大数量的限制(最大数量则看内存大小);2.采用时间通知机制,当文件描述符状态有变时,主动通知内核进行调度。其中print注释是为了打印对象,查看对象是什么。

  1. from socket import *
  2. import select
  3. # 创建socket对象
  4. sock_server = socket(AF_INET, SOCK_STREAM)
  5. # 绑定IP和端口
  6. sock_server.bind(("", 7788))
  7. # 将主动模式设置为被动模式,监听连接
  8. sock_server.listen(5)
  9. # 创建epoll监测对象
  10. epoll = select.epoll()
  11. # print("未注册epoll对象:{}".format(epoll))
  12. # 注册主套接字,监控读状态
  13. epoll.register(sock_server.fileno(), select.EPOLLIN)
  14. # print("注册了主套接字后:{}".format(epoll))
  15. # 创建字典,保存套接字对象
  16. sock_dicts = {}
  17. # 创建字典,保存客户端信息
  18. client_dicts = {}
  19. while True:
  20. # print("所有套接字:{}".format(sock_dicts))
  21. # print("所有客户端信息:{}".format(client_dicts))
  22. # 程序阻塞在这,返回文件描述符有变化的对象
  23. poll_list = epoll.poll()
  24. # print("有变化的套接字:{}".format(poll_list))
  25. for sock_fileno, events in poll_list:
  26. # print("文件描述符:{},事件:{}".format(sock_fileno, events))
  27. # 判断是否是主套接字
  28. if sock_fileno == sock_server.fileno():
  29. # 创建新套接字
  30. new_sock, client_info = sock_server.accept()
  31. print(f"客户端:{client_info}已连接")
  32. # 注册到epoll监测中
  33. epoll.register(new_sock.fileno(), select.EPOLLIN)
  34. # 添加到套接字字典当中
  35. sock_dicts[new_sock.fileno()] = new_sock
  36. client_dicts[new_sock.fileno()] = client_info
  37. else:
  38. # 接收消息
  39. raw_data = sock_dicts[sock_fileno].recv(1024)
  40. if raw_data:
  41. print(f"来自{client_dicts[sock_fileno]}的数据:{raw_data.decode('gb2312')}")
  42. else:
  43. # 关闭连接
  44. sock_dicts[sock_fileno].close()
  45. # 注销epoll监测对象
  46. epoll.unregister(sock_fileno)
  47. # 数据为空,则客户端断开连接,删除相关数据
  48. del sock_dicts[sock_fileno]
  49. del client_dicts[sock_fileno]

  50.  

IO多路复用和线程池在提高并发性上应用场景的区别

多路复用适用于需要保持大量闲置(区别于计算密集型)长连接的业务场景,例如聊天室。这样的好处是能够避免不断的创建新线程,导致系统资源浪费。需要注意,多路复用本质上是复用单线程的,回调函数的执行必然是有可能长时间阻塞的,所以如果涉及到耗时的计算密集型任务,则会大大降低系统处理其它连接的响应速度。

线程池则适合短连接并发的情况,比如普通的web业务系统,Tomcat的Servlet容器默认选择就是线程池(虽然3.0后支持异步,但一般情况下不常使用)。由于处理短连接的线程很快会退出,因此能够充分发挥线程池复用线程的好处。

当然,多路复用和线程池可以结合起来使用,效果也许更好,但代码复杂度也会相应提高,需要更好的设计。建议根据业务场景选择相应的技术,避免过早优化。

 

一点补充:很多人不知道协程该归于哪个技术范畴。协程除了在用户态通过栈切换实现控制流的切换以外,还通常将多路复用和线程池结合起来。比如go语言内置的协程就是在多线程的基础上实现了一套调度策略,调度策略的实现建立在操作系统内核提供的IO多路复用技术之上,同时go语言参考计算机硬件情况自动将协程绑定在若干个系统线程之上,从而实现资源的高效率利用。

python实现并发服务器实现方式(多线程/多进程/select/epoll)的更多相关文章

  1. Python网络编程(4)——异步编程select & epoll

    在SocketServer模块的学习中,我们了解了多线程和多进程简单Server的实现,使用多线程.多进程技术的服务端为每一个新的client连接创建一个新的进/线程,当client数量较多时,这种技 ...

  2. Python的并发并行[3] -> 进程[1] -> 多进程的基本使用

    多进程的基本使用 1 subprocess 常用函数示例 首先定义一个子进程调用的程序,用于打印一个输出语句,并获取命令行参数 import sys print('Called_Function.py ...

  3. linux学习之多高并发服务器篇(一)

    高并发服务器 高并发服务器 并发服务器开发 1.多进程并发服务器 使用多进程并发服务器时要考虑以下几点: 父最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符) 系统内创建进程 ...

  4. Linux 并发服务器雏形总结

    如下介绍一个并发回射客户端/服务器的雏形,所谓回射:就是客户端输入一条数据,服务器端读取并显示,然后服务器端再把刚读取的信息发送回客户端进行显示.示意图如下: 所谓并发服务器:就是一个服务器可以同时为 ...

  5. web服务-2、四种方法实现并发服务器-多线程,多进程,协程,(单进程-单线程-非堵塞)

    知识点:1.使用多线程,多进程,协程完成web并发服务器 2.单进程-单线程-非堵塞也可以实现并发服务器 1.多进程和协程的代码在下面注释掉的部分,我把三种写在一起了 import socket im ...

  6. 【Python之路】特别篇--多线程与多进程

    并发 与 并行 的区别: 解释一:并发是在同一实体上的多个事件,并行是在不同实体上的多个事件: 解释二:并发是指两个或多个事件在同一时间间隔发生,而并行是指两个或者多个事件在同一时刻发生. 并发:就是 ...

  7. Python中并发、多线程等

    1.基本概念 并发和并行的区别: 1)并行,parallel 同时做某些事,可以互不干扰的同一时刻做几件事.(解决并发的一种方法) 高速公路多个车道,车辆都在跑.同一时刻. 2)并发 concurre ...

  8. Python复习笔记(十)Http协议--Web服务器-并发服务器

    1. HTTP协议(超文本传输协议) 浏览器===>服务器发送的请求格式如下:(浏览器告诉服务器,浏览器的信息) GET / HTTP/1.1 Host: www.baidu.com Conne ...

  9. Python并发编程系列之多线程

    1 引言 上一篇博文详细总结了Python进程的用法,这一篇博文来所以说Python中线程的用法.实际上,程序的运行都是以线程为基本单位的,每一个进程中都至少有一个线程(主线程),线程又可以创建子线程 ...

随机推荐

  1. JS (二)

    ]1 函数 1 函数就是一段待执行的代码段 2 函数可以实现功能的封装,可以实现代码的复用 3 函数使用: 1 函数声明 2 函数调用 4 语法: 1 函数声明 1 使用function关键字进行函数 ...

  2. 【leetcode】566. Reshape the Matrix

    原题 In MATLAB, there is a very useful function called 'reshape', which can reshape a matrix into a ne ...

  3. 微软Surface低端版本将问世

    平板电脑现如今已走进千家万户,其触屏的操作相比笔记本电脑更加方便,屏幕也比手机大很多,是家用玩机的首选.虽然微软也在这一领域有所发力,推出了Surface这一产品,但其高昂的售价使得其在市场上的表现并 ...

  4. NORDIC BLE升级

    NRF52832 SDK15.3.0 概述: 所谓DFU(Device Firmware Update),就是设备固件升级的意思,而OTA是DFU的一种类型,准确说,OTA的全称应该是OTA DFU, ...

  5. 关于Linux、python的PDF书籍整理(附带亲测的 IT 电子书网站)

    [18.1.3][在博客园发的文章不是很多呢,接下来的博客会转移到独立的个人博客网站上去了,具体的学习笔记和内容都会在独立网站上发布,后期还会有博主的个人资源库和教程还有独立网盘存储(可以关注一波哈) ...

  6. idou老师教你学Istio 07: 如何用istio实现请求超时管理

    在前面的文章中,大家都已经熟悉了Istio的故障注入和流量迁移.这两个方面的功能都是Istio流量治理的一部分.今天将继续带大家了解Istio的另一项功能,关于请求超时的管理. 首先我们可以通过一个简 ...

  7. D. Lakes in Berland (DFS或者BFS +连通块

    https://blog.csdn.net/guhaiteng/article/details/52730373 参考题解 http://codeforces.com/contest/723/prob ...

  8. python 学习笔记_2 模拟socket编程 服务端、客户端通信(参考核心编程2代码实现)

    服务器端代码实现: #!/usr/bin/env python#coding=gbk'''接收客户端字符串,在字段串前面打上当前时间,然后返回server端采用 python2 linux下调试运行客 ...

  9. 1. jenkins常见错误及解决方法

    1. Jenkins一直卡在启动页面 需要你进入jenkins的工作目录, 打开 hudson.model.UpdateCenter.xml 把 http://updates.jenkins-ci.o ...

  10. 一步一步pwn路由器之rop技术实战

    前言 本文由 本人 首发于 先知安全技术社区: https://xianzhi.aliyun.com/forum/user/5274 这次程序也是 DVRF 里面的,他的路径是 pwnable/She ...