python – 将许多并发请求从urllib.request发送到HTTPServer时的神秘异常

我正在尝试做一个this Matasano crypto challenge,它涉及对服务器进行定时攻击,并进行人为减慢的字符串比较功能。它表示使用“您选择的Web框架”,但我不想安装一个Web框架,所以我决定使用HTTPServer class内置的http.server模块。

我想出了一些有用的东西,但是这是非常慢的,所以我试图加速使用(不好的文件)建立在multiprocessing.dummy的线程池。它是更快,但我注意到一些奇怪的:如果我做8或同时减少请求,工作正常。如果我有更多的,它可以工作一段时间,给我看似随机的错误。错误似乎是不一致的,并不总是相同的,但是它们通常都有Connection被拒绝,无效的参数,OSError:[Errno 22]无效参数urllib.error.URLError:< urlopen错误[Errno 22]无效的参数> BrokenPipeError :[Errno 32] Broken pipe,或urllib.error.URLError:< urlopen error [Errno 61]拒绝连接>在他们中。

服务器可以处理的连接数量有一些限制吗?我不认为线程本身的数量是问题,因为我写了一个简单的函数来做慢速字符串比较而不运行Web服务器,并用500个并发线程调用它,并且工作正常。我不认为只是从许多线程提出请求是问题,因为我已经使用了超过100个线程的抓取工具(所有这些都同时发送到同一个网站),并且它们工作正常。看来也许HTTPServer不是可靠地托管大量流量的生产网站,但令我感到惊讶的是,这很容易使它崩溃。

我尝试逐渐从我的代码中删除与该问题无关的代码,正如我通常在诊断这样的神秘错误时所做的那样,但是在这种情况下并不是很有帮助。看起来我正在删除看似无关的代码,服务器可以处理的连接数量逐渐增加,但并没有明显的崩溃原因。

有人知道如何增加我可以一次性提出的请求数量,还是至少为什么会发生这种情况?

我的代码很复杂,但我想出了这个简单的程序来证明问题:

#!/usr/bin/env python3

import os
import random

from http.server import BaseHTTPRequestHandler, HTTPServer
from multiprocessing.dummy import Pool as ThreadPool
from socketserver import ForkingMixIn, ThreadingMixIn
from threading import Thread
from time import sleep
from urllib.error import HTTPError
from urllib.request import urlopen


class FancyHTTPServer(ThreadingMixIn, HTTPServer):
    pass


class MyRequestHandler(BaseHTTPRequestHandler):
    def do_GET(self):
        sleep(random.uniform(0, 2))
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b"foo")

    def log_request(self, code=None, size=None):
        pass

def request_is_ok(number):
    try:
        urlopen("http://localhost:31415/test" + str(number))
    except HTTPError:
        return False
    else:
        return True


server = FancyHTTPServer(("localhost", 31415), MyRequestHandler)
try:
    Thread(target=server.serve_forever).start()
    with ThreadPool(200) as pool:
        for i in range(10):
            numbers = [random.randint(0, 99999) for j in range(20000)]
            for j, result in enumerate(pool.imap(request_is_ok, numbers)):
                if j % 20 == 0:
                    print(i, j)
finally:
    server.shutdown()
    server.server_close()
    print("done testing server")

由于某些原因,上面的程序工作正常,除非它有超过100个线程,但是我的真正的代码只能处理8个线程。如果我用9运行它,我通常会得到连接错误,10,我总是得到连接错误。我尝试使用concurrent.futures.ThreadPoolExecutorconcurrent.futures.ProcessPoolExecutormultiprocessing.pool而不是multiprocessing.dummy.pool,没有一个似乎有帮助。我尝试使用一个简单的HTTPServer对象(没有ThreadingMixIn),这只是让事情运行得很慢,没有解决问题。我尝试使用ForkingMixIn,也没有修复它。

我应该怎么做这个?我在运行OS X 10.11.3的2013年底的MacBook Pro上运行Python 3.5.1。

编辑:我尝试了更多的东西,包括在进程中运行服务器而不是线程,作为一个简单的HTTPServer,使用ForkingMixIn和ThreadingMixIn。没有一个帮助。

编辑:这个问题比我想象的更陌生。我尝试使用服务器制作一个脚本,另一个脚本与许多线程进行请求,并在终端中的不同选项卡中运行。与服务器的进程运行正常,但一个请求崩溃。异常是ConnectionResetError的混合:[Errno 54]连接由对等体重置,urllib.error.URLError:< urlopen错误[Errno 54]连接由对等体重置>,OSError:[Errno 41]协议错误类型为套接字,urllib .error.URLError:< urlopen错误[Errno 41]套接字的协议错误类型>,urllib.error.URLError:< urlopen错误[Errno 22]无效参数&gt ;. 我尝试使用像上面那样的虚拟服务器,如果我将并发请求的数量限制在5个或更少,它工作正常,但是有6个请求,客户端进程崩溃。服务器有一些错误,但是它一直在继续。无论我是使用线程还是进程来执行请求,客户端都会崩溃。然后,我尝试将减速功能放在服务器中,它能够处理60个并发请求,但是它与70崩溃。这似乎可能与服务器问题的证据相矛盾。 编辑:我尝试使用请求而不是urllib.request描述的大部分事情,并遇到类似的问题。 编辑:我现在正在运行OS X 10.11.4并遇到同样的问题。

您正在使用默认的listen()积压值,这可能是许多错误的原因。这不是已建立连接的并发客户端的数量,而是在连接建立之前等待侦听队列的客户端数量。将您的服务器类更改为:

class FancyHTTPServer(ThreadingMixIn, HTTPServer):
    def server_activate(self):
        self.socket.listen(128)

128是合理的限制。如果要进一步增加,您可能需要检查socket.SOMAXCONN或您的OS somaxconn。如果您在重负载下仍然有随机错误,您应该检查您的ulimit设置,并在需要时增加。

我用你的例子做了这个,我有超过1000个线程运行正常,所以我认为应该解决你的问题。

更新

如果它改善了,但仍然与200个同时的客户端崩溃,那么我很确定你的主要问题是积压的大小。请注意,您的问题不是并发客户端的数量,而是并发连接请求的数量。简要的说明这是什么意思,没有太深入TCP内部。

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(BACKLOG)
while running:
    conn, addr = s.accept()
    do_something(conn, addr)

在此示例中,套接字现在接受给定端口的连接,并且s.accept()调用将阻塞,直到客户端连接。您可以让许多客户端尝试同时连接,根据您的应用程序,您可能无法调用s.accept()并以客户端尝试连接的速度分派客户端连接。待处理的客户端排队,并且该队列的最大大小由BACKLOG值确定。如果队列已满,则客户端将失败,并显示“连接拒绝”错误。

线程没有帮助,因为ThreadingMixIn类是在单独的线程中执行do_something(conn,addr)调用,因此服务器可以返回到mainloop和s.accept()调用。

您可以尝试进一步增加积压,但是有一点不会有帮助,因为如果队列增长太大,则在服务器执行s.accept()调用之前,某些客户端将超时。

因此,如上所述,您的问题是并发连接尝试的次数,而不是同时发生的客户端数。也许128对于您的真实应用程序来说足够了,但是您的测试中出现错误,因为您尝试同时连接所有200个线程并充斥队列。

不要担心ulimit,除非你得到太多的打开文件错误,但如果你想增加超过128的积压,请对socket.SOMAXCONN进行一些研究。这是一个很好的开始:https://utcc.utoronto.ca/~cks/space/blog/python/AvoidSOMAXCONN

http://stackoverflow.com/questions/36075676/mysterious-exceptions-when-making-many-concurrent-requests-from-urllib-request-t

本站文章除注明转载外,均为本站原创或编译
转载请明显位置注明出处:python – 将许多并发请求从urllib.request发送到HTTPServer时的神秘异常