【问题标题】:Persistence of urllib.request connections to a HTTP serverurllib.request 连接到 HTTP 服务器的持久性
【发布时间】:2012-03-19 15:21:28
【问题描述】:

我想在我们的一台 Web 服务器上进行一些性能测试,以了解该服务器如何处理大量持久连接。不幸的是,我对 HTTP 和 Web 测试并不十分熟悉。到目前为止,这是我为此获得的 Python 代码:

import http.client
import argparse
import threading


def make_http_connection():
    conn = http.client.HTTPConnection(options.server, timeout=30)
    conn.connect()


if __name__ == '__main__':
    parser = argparse.ArgumentParser()

    parser.add_argument("num", type=int, help="Number of connections to make (integer)")
    parser.add_argument("server", type=str, help="Server and port to connect to. Do not prepend \'http://\' for this")

    options = parser.parse_args()

    for n in range(options.num):
        connThread = threading.Thread(target = make_http_connection, args = ())
        connThread.daemon = True
        connThread.start()

    while True:
        try:
            pass
        except KeyboardInterrupt:
            break

我的主要问题是:如何让这些连接保持活跃?我设置了一个很长的超时时间,但这是一种非常粗略的方法,我什至不确定它会影响连接。只是每隔一段时间请求一两个字节就可以了吗?

(另外,在不相关的说明中,有没有比我的代码末尾丑陋的while True: 块更好的等待键盘中断的过程?)

【问题讨论】:

  • “持久连接”是什么意思?服务器的正常活动是客户端连接,服务器发送响应并关闭连接。
  • @jjm 特别是使用“Connection: Keep-Alive”标头 (link) 保持活动状态的连接。

标签: python http python-3.x


【解决方案1】:

urllib.request 不支持持久连接。代码中有'Connection: close' 硬编码。但是http.client 部分支持持久连接(包括旧的http/1.0 keep-alive)。所以问题标题可能会产生误导。


我想在我们的其中一台 Web 服务器上进行一些性能测试,以了解该服务器如何处理大量持久连接。不幸的是,我对 HTTP 和 Web 测试并不十分熟悉。

您可以使用现有的 http 测试工具,例如 slowloris、httperf,而不是自己编写。


如何保持这些连接有效?

要关闭 http/1.1 连接,客户端应明确指定 Connection: close 标头,否则服务器认为连接是持久的(尽管它可能随时关闭它,http.client won't know about it 直到它尝试读取/写入连接)。

conn.connect() 几乎立即返回并且您的线程结束。要强制每个线程保持与服务器的 http 连接,您可以:

import time

def make_http_connection(*args, **kwargs):
    while True: # make new http connections
        h = http.client.HTTPConnection(*args, **kwargs)
        while True: # make multiple requests using a single connection
            try:
                h.request('GET', '/') # send request; make conn. on the first run
                response = h.getresponse()
                while True: # read response slooowly
                    b = response.read(1) # read 1 byte
                    if not b:
                       break
                    time.sleep(60) # wait a minute before reading next byte
                    #note: the whole minute might pass before we notice that 
                    #  the server has closed the connection already
            except Exception:
                break # make new connection on any error

注意:如果服务器返回'Connection: close',那么每个连接只有一个请求。


(另外,在不相关的说明中,有没有比丑陋的 while True: block 在我的代码末尾更好的等待键盘中断的过程?)

要等到所有线程完成或KeyboardInterrupt 发生,您可以:

while threads:
    try:
        for t in threads[:]: # enumerate threads
            t.join(.1) # timeout 0.1 seconds
            if not t.is_alive():
               threads.remove(t)
    except KeyboardInterrupt:
        break

或者是这样的:

while threading.active_count() > 1:
    try:
        main_thread = threading.current_thread()
        for t in threading.enumerate(): # enumerate all alive threads
            if t is not main_thread:
               t.join(.1)
    except KeyboardInterrupt:
        break

由于各种原因,后者可能无法正常工作,例如,如果存在虚拟线程,例如在 C 扩展中启动但未使用 threading 模块的线程。

concurrent.futures.ThreadPoolExecutor 提供了比threading 模块更高的抽象级别,它可以隐藏一些复杂性。

您可以在单个线程中同时打开多个连接,而不是每个连接模型中的线程,例如,直接使用 requests.async 或 gevent。

【讨论】:

  • 这个答案中包含了很多帮助。谢谢,J.F.!
  • @Kudzu:我更新了问题,提到了提供更高级别接口的ThreadPoolExecutor、requests.async。
【解决方案2】:

如果 a lot 真的 a lot 那么您可能希望使用异步 io 而不是线程。

requests + gevent = grequests

GRequests 允许您将 Requests 与 Gevent 一起使用,从而轻松地发出异步 HTTP 请求。

import grequests

urls = [
    'http://www.heroku.com',
    'http://tablib.org',
    'http://httpbin.org',
    'http://python-requests.org',
    'http://kennethreitz.com'
]

requests = (grequests.get(u) for u in urls)
responses = grequests.map(requests)

请求 support 持久 HTTP 连接。

【讨论】:

【解决方案3】:

我在这里有点超出我的知识库,但我会假设你的线程在函数 make_http_connection() 完成时结束。也就是说,如果你想要它们,你会想要包括:

while condition:
    pass

函数结束时。我想你希望他们都同时变得活跃?然后让函数修改一个全局变量,并使用条件来对照 options.num 测试这个值,这样进程就会等到所有进程都在运行后才开始终止。

题外话,猜猜你在这里的目标是什么,你不能只问线程计算你有多少活动线程并继续运行直到没有剩余吗?

threading.active_count()

这里讨论的是阅读键盘,如果你需要的话:

Polling the keyboard

【讨论】:

    【解决方案4】:

    您确实应该使用像 Funkload 这样的基准测试工具来做到这一点。如果您没有使用 HTTP 的经验,那么尝试像这样从头开始进行性能测试肯定会导致糟糕的结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-30
      • 1970-01-01
      • 1970-01-01
      • 2012-06-14
      • 1970-01-01
      相关资源
      最近更新 更多