我需要一个可以处理至少数万个并发套接字连接的Python TCP服务器。我试图在多处理器和多线程模式下测试Python SocketServer软件包的功能,但两者均远未达到理想的性能。
首先,我将描述客户端,因为这两种情况都很常见。
client.py
import socket
import sys
import threading
import time
SOCKET_AMOUNT = 10000
HOST, PORT = "localhost", 9999
data = " ".join(sys.argv[1:])
def client(ip, port, message):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
while 1:
sock.sendall(message)
time.sleep(1)
sock.close()
for i in range(SOCKET_AMOUNT):
msg = "test message"
client_thread = threading.Thread(target=client, args=(HOST, PORT, msg))
client_thread.start()
多处理器服务器:
foked_server.py
import os
import SocketServer
class ForkedTCPRequestHandler(SocketServer.BaseRequestHandler):
def handle(self):
cur_process = os.getpid()
print "launching a new socket handler, pid = {}".format(cur_process)
while 1:
self.request.recv(4096)
class ForkedTCPServer(SocketServer.ForkingMixIn, SocketServer.TCPServer):
pass
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
server = ForkedTCPServer((HOST, PORT), ForkedTCPRequestHandler)
print "Starting Forked Server"
server.serve_forever()
多线程服务器:
threaded_server.py
import threading
import SocketServer
class ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
def handle(self):
cur_thread = threading.current_thread()
print "launching a new socket handler, thread = {}".format(cur_thread)
while 1:
self.request.recv(4096)
class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
server = ThreadedTCPServer((HOST, PORT), ForkedTCPRequestHandler)
print "Starting Threaded Server"
server.serve_forever()
在第一种情况下,使用 forked_server.py 创建仅40个进程,其中大约20个进程在一段时间内开始中断,并出现以下错误:
在客户端。
线程版本更耐用,可容纳4000多个连接,但最终开始显示
测试是在我的本地计算机Kubuntu 14.04 x64内核v3.13.0-32上进行的。这些是我为提高系统总体性能所采取的步骤:
sysctl -w fs.file-max=10000000
sysctl -w net.core.netdev_max_backlog = 2500
sysctl -w net.core.somaxconn = 250000
因此,问题是:
最佳答案
socketserver
将无法处理接近10k连接的任何地方。当前的硬件和操作系统上都不会有线程化或分支的服务器。成千上万的线程意味着您花费比实际工作更多的时间进行上下文切换和调度。现代linux在调度线程和进程方面已经变得非常擅长,而Windows在线程方面非常出色(但是对于进程却很糟糕),但是它的功能是有限的。socketserver
甚至都没有尝试实现高性能。
当然,CPython的GIL使情况变得更糟。如果您不使用3.2+,请执行以下操作:任何进行微不足道的CPU绑定(bind)工作的线程都会阻塞所有其他线程,并阻塞您的I/O。使用新的GIL,如果您避免使用不平凡的CPU,就不会增加太多的问题,但是它仍然使上下文切换比原始pthread或Windows线程更昂贵。
所以你想要什么?
您需要一个单线程“ react 器”,该“ react 器”为循环中的事件提供服务并启动处理程序。 (在Windows和Solaris上,代替使用“proactor”(具有全部服务于同一事件队列的线程池)是有优势的,但是由于您使用的是Linux,因此不必担心。)现代OS具有非常好的功能。可以构建多路复用的API(在BSD/Mac上为kqueue
,在Linux上为epoll
,在Solaris上为/dev/poll
,在Windows上为IOCP),即使在几年前的硬件上也可以轻松处理10K连接。socketserver
并不是一个糟糕的 react 堆,只是它没有提供任何好的方法来分发异步工作,仅分发线程或进程。从理论上讲,您无需在GreenletMixIn
上进行过多工作即可构建greenlet
(使用CoroutineMixIn
扩展模块)或socketserver
(假设您已经或知道如何编写蹦床和调度程序),而这可能不会太繁重,重量。但是我不确定那时候您从socketserver
中获得了多少好处。
并行性可以提供帮助,但只能将所有慢速作业从主工作线程中分派(dispatch)出去。首先建立您的10K连接,做最少的工作。然后,如果您要添加的实际工作是受I/O约束的(例如,读取文件或向其他服务发出请求),请添加要分派(dispatch)给的线程池;如果您需要添加大量CPU约束的工作,请改为添加一个进程池(或在某些情况下甚至每个进程中的一个)。
如果您可以使用Python 3.4,则stdlib在 asyncio
中有一个答案(并且PyPI上有一个用于3.3的反向端口,但是从本质上来说,不可能反向移植到早期版本)。
如果不是,那么,如果您不关心Windows,则可以在3.4+的 selectors
之上构建自己的东西;如果您仅关心linux,* BSD和Mac,并且愿意编写两个版本,则可以在2.6+的 select
之上构建东西。您的代码,但这将需要大量工作。或者,您可以用C编写核心事件循环(或仅使用libev
或libuv
或libevent
这样的现有事件)并将其包装在扩展模块中。
但实际上,您可能希望转向第三方库。从gevent
(尝试使您的代码看起来像抢先线程的代码,但实际上在单线程事件循环上以greenlet运行)到Twisted
(基于显式回调和 future ,类似于许多现代JavaScript框架)。
StackOverflow并不是获取特定库建议的好地方,但是我可以向您提供一般建议:仔细研究它们,选择一个对您的应用程序听起来最适合的API,测试它是否足够好,然后再回到另一个如果您喜欢的一个无法剪切(或者如果您发现喜欢该API是错误的),则返回一个。这些库中的某些库(尤其是gevent
和tornado
的爱好者会告诉您,他们最喜欢的库是“最快的”,但是谁在乎呢?重要的是它们是否足够快并且可以用来编写您的应用程序。
我不由自主地搜索gevent
,eventlet
,concurrence
,cogen
,twisted
,tornado
,monocle
,diesel
和circuits
。那可能不是一个很好的列表,但是如果您一起搜索所有这些术语,我敢打赌,您会找到一个最新的比较,或者是一个合适的论坛来询问。