当我们需要创建的子进程数量不多时,可以直接利用 multiprocessing
中的 Process
来动态生成多个进程。但如果是上百个甚至上千个目标,手动去创建进程的工作量就很大了,此时可以利用到 multiprocessing
模块提供的 Pool
方法,也就是使用进程池技术。
那我们初始化 Pool
时,可以指定一个最大进程数,当有新的请求提交到 Pool
中时,如果池还没有满,那么就会创建一个新的进程来执行该请求,但是如果池中的进程数已经达到刚刚我们指定的最大值,那么请求就会等待,直到池中有进程结束,才会使用之前的进程来执行新的任务。
但是呢,简单的进程池模型为每个连接创建一个进程,当有1000个空闲的长连接,就需要1000个进程才能处理。我们说进程池解决的是服务器如何高并发的处理这些接收的请求,I/O多路复用技术则解决的是从客户端如何到服务器高效的通信发送接收请求。所以采用 epoll + 进程池 模型,I/O进程处理I/O收发,当I/O进程收到一个完整的请求,把请求交给进程池处理,进程池处理完成之后,交由I/O进程发送回客户端。这个方案大大优于前者。
那么什么是epoll呢?当服务器端需要管理多个客户端连接,而socket的 recv
只能监视单个socket,但是epoll能高效的监视多个socket。epoll先维护一个等待队列,然后阻塞进程,epoll 就去监视那些socket,当有socket收到数据之后,会把该socket添加到就绪列表中,然后遍历这个列表,就可以知道是哪些socket收到数据了,就可以进行操作了。
epoll 使用红黑树结构。
- 使用 epoll_create() 创建一棵空的红黑树,内核创建epoll对象;
- 把需要监视的socket加入到红黑树中,使用 epoll_ctl();
- 当有socket收到数据后,把该socket加入到就绪队列中, epoll_wait() 返回就绪队列中的就绪事件。
进程池Pool server端代码流程(Python):
server_socket = socket(AF_INET, SOCK_STREAM)
server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
local_addr = ('', 7788)
server_socket.bind(local_addr)
server_socket.listen(128)
po = Pool(3)
while True:
client_socket, client_addr = server_socket.accept()
po.apply_async(child_handler, (client_socket, client_addr))
po.close()
po.join()
server_socket.close()
epoll server端代码流程(Python):
import sys
import select
from socket import *
# 初始化套接字
tcp_server_socket = socket(AF_INET, SOCK_STREAM)
# ip地址和端口复用
tcp_server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# bind ip地址为server端ip地址
tcp_server_socket.bind(('', 2000))
# 激活listen端口
tcp_server_socket.listen(10)
ep = select.epoll()
# epoll监控标准输入stdin
ep.register(sys.stdin.fileno(), select.EPOLLIN)
# epoll监控tcp_server_socket
ep.register(tcp_server_socket.fileno(), select.EPOLLIN)
client_dict={}
while True:
events = ep.poll(-1) # -1表示永久等待
for event in events:
if event[0] == tcp_server_socket.fileno(): # 有新的连接
client_socket, client_addr = tcp_server_socket.accept()
ep.register(client_socket.fileno(), select.EPOLLIN) # epoll监听
client_dict[client_socket.fileno()]=client_socket
else: # 读数据
recv_data=client_dict[event[0]].recv(1000)
if recv_data:
# 处理收到的数据
...
pass
else: # 没有读到数据,说明对端断开了
# 解除和客户端对应的socket的注册
ep.unregister(event[0])
client_dict[event[0]].close()
del client_dict[event[0]]
break