【问题标题】:Multithreaded web server in pythonpython中的多线程Web服务器
【发布时间】:2020-01-29 14:28:00
【问题描述】:

我正在尝试在 python 中创建多线程 Web 服务器,但它一次只响应一个请求,我不知道为什么。你能帮帮我吗?

#!/usr/bin/env python2
# -*- coding: utf-8 -*-

from SocketServer import ThreadingMixIn
from  BaseHTTPServer import HTTPServer
from SimpleHTTPServer import SimpleHTTPRequestHandler
from time import sleep

class ThreadingServer(ThreadingMixIn, HTTPServer):
    pass

class RequestHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/plain')
        sleep(5)
        response = 'Slept for 5 seconds..'
        self.send_header('Content-length', len(response))
        self.end_headers()
        self.wfile.write(response)

ThreadingServer(('', 8000), RequestHandler).serve_forever()

【问题讨论】:

  • 使用非阻塞套接字,您可以为数千个客户端提供服务。无需为每个请求创建线程。
  • @shiplu.mokadd.im 你能发帖吗..你的帮助将不胜感激
  • @Pilot 这里需要两件事。 select() 和非阻塞。 Python 有一个 socket 库。 IBM 在使用 select() 进行套接字编程时得到了一些 good articles
  • @shiplu.mokadd 感谢师父的宝贵意见
  • 这是另一个类似 SimpleHTTPServer 的多线程 HTTP 服务器的好例子:MultithreadedSimpleHTTPServer on GitHub

标签: python multithreading http webserver


【解决方案1】:

python3.7中的多线程https服务器

from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import ThreadingMixIn
import threading
import ssl

hostName = "localhost"
serverPort = 8080


class MyServer(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(bytes("<html><head><title>https://pythonbasics.org</title></head>", "utf-8"))
        self.wfile.write(bytes("<p>Request: %s</p>" % self.path, "utf-8"))
        self.wfile.write(bytes("<p>Thread: %s</p>" % threading.currentThread().getName(), "utf-8"))
        self.wfile.write(bytes("<p>Thread Count: %s</p>" % threading.active_count(), "utf-8"))
        self.wfile.write(bytes("<body>", "utf-8"))
        self.wfile.write(bytes("<p>This is an example web server.</p>", "utf-8"))
        self.wfile.write(bytes("</body></html>", "utf-8"))


class ThreadingSimpleServer(ThreadingMixIn,HTTPServer):
    pass

if __name__ == "__main__":
    webServer = ThreadingSimpleServer((hostName, serverPort), MyServer)
    webServer.socket = ssl.wrap_socket(webServer.socket, keyfile='./privkey.pem',certfile='./certificate.pem', server_side=True)
    print("Server started http://%s:%s" % (hostName, serverPort))

    try:
        webServer.serve_forever()
    except KeyboardInterrupt:
        pass

    webServer.server_close()
    print("Server stopped.")

你可以在浏览器中测试它:https://localhost:8080 运行结果是: enter image description here
enter image description here 提醒您可以生成自己的密钥文件和证书使用

$openssl req -newkey rsa:2048  -keyout privkey.pem -x509 -days 36500 -out certificate.pem

了解有关使用 openssl 创建自签名证书的详细信息:https://www.devdungeon.com/content/creating-self-signed-ssl-certificates-openssl

【讨论】:

  • 这个例子很好用,谢谢分享。这实际上是在使用线程池吗?如果是这样我如何控制这个池的大小?
【解决方案2】:

在python3中,可以使用下面的代码(https或http):

from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn
import threading

USE_HTTPS = True

class Handler(BaseHTTPRequestHandler):

    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'Hello world\t' + threading.currentThread().getName().encode() + b'\t' + str(threading.active_count()).encode() + b'\n')


class ThreadingSimpleServer(ThreadingMixIn, HTTPServer):
    pass

def run():
    server = ThreadingSimpleServer(('0.0.0.0', 4444), Handler)
    if USE_HTTPS:
        import ssl
        server.socket = ssl.wrap_socket(server.socket, keyfile='./key.pem', certfile='./cert.pem', server_side=True)
    server.serve_forever()


if __name__ == '__main__':
    run()

你会发现这段代码会创建一个新线程来处理每个请求。

以下命令生成自签名证书:

openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365

如果您使用的是 Flask,this blog 非常棒。

【讨论】:

  • 这应该是一条评论。在 python3 中是socketserver(小写)
  • 简洁的代码与 python3 配合得很好,谢谢。
【解决方案3】:

我开发了一个名为 ComplexHTTPServer 的 PIP 实用程序,它是 SimpleHTTPServer 的多线程版本。

要安装它,您需要做的就是:

pip install ComplexHTTPServer

使用起来很简单:

python -m ComplexHTTPServer [PORT]

(默认端口为8000。)

【讨论】:

  • 我赞成您的答案,因为在不需要流式传输的情况下,它最终与任何其他答案一样有效。
  • 你的回答更简洁!
【解决方案4】:

令人惊讶的是,这些打破流媒体的解决方案获得了如此多的选票。如果将来可能需要流式传输,那么ThreadingMixIn 和 gunicorn 就不好用了,因为它们只是收集响应并在最后将其作为一个单元写入(如果您的流是无限的,这实际上什么都不做)。

您将BaseHTTPServer 与线程结合的基本方法很好。但是默认的BaseHTTPServer 设置会在每个侦听器上重新绑定一个新套接字,如果所有侦听器都在同一个端口上,这在 Linux 中将不起作用。在调用serve_forever() 之前更改这些设置。 (就像您必须在线程上设置 self.daemon = True 以阻止 ctrl-C 被禁用一样。)

以下示例在同一端口上启动 100 个处理程序线程,每个处理程序通过 BaseHTTPServer 启动。

import time, threading, socket, SocketServer, BaseHTTPServer

class Handler(BaseHTTPServer.BaseHTTPRequestHandler):

    def do_GET(self):
        if self.path != '/':
            self.send_error(404, "Object not found")
            return
        self.send_response(200)
        self.send_header('Content-type', 'text/html; charset=utf-8')
        self.end_headers()

        # serve up an infinite stream
        i = 0
        while True:
            self.wfile.write("%i " % i)
            time.sleep(0.1)
            i += 1

# Create ONE socket.
addr = ('', 8000)
sock = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(addr)
sock.listen(5)

# Launch 100 listener threads.
class Thread(threading.Thread):
    def __init__(self, i):
        threading.Thread.__init__(self)
        self.i = i
        self.daemon = True
        self.start()
    def run(self):
        httpd = BaseHTTPServer.HTTPServer(addr, Handler, False)

        # Prevent the HTTP server from re-binding every handler.
        # https://stackoverflow.com/questions/46210672/
        httpd.socket = sock
        httpd.server_bind = self.server_close = lambda self: None

        httpd.serve_forever()
[Thread(i) for i in range(100)]
time.sleep(9e9)

【讨论】:

  • 不想将 Websockets 用于这样的事情吗?
  • 基于此代码的服务器对我来说运行良好,因为准备响应需要 2 分钟。能够立即返回“正在工作......”有所帮助。唉,Chrome 开箱即用,Internet Explorer v11 会在 1-2 分钟后返回整个页面。还不知道服务器是否需要其他东西,或者 IE 对流媒体毫无希望。
  • @Adrian 想一想,您可以尝试分块传输编码。也许如果你有一个包含你想要立即显示的部分的内容长度的块头,浏览器可能会更快地“接受”它?但我自己没有尝试过。当然,如果这不起作用,您总是可以提供一个
  • 您的服务器是否适合“数十个并发请求,每个请求可能需要 20 秒,其中包含远程数据库调用和计算”的用例?换句话说,每个工作任务(例如等待复杂的远程数据库操作完成)足够的“停机时间”,以保证比系统中的 CPU 数量更多的活动线程。
  • @StephenBoesch 是的,我在比这更苛刻的条件下使用它。如果您不需要那么多线程,请务必使用更少的线程。
【解决方案5】:

查看来自 Doug Hellmann 博客的 this 帖子。

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from SocketServer import ThreadingMixIn
import threading

class Handler(BaseHTTPRequestHandler):

    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        message =  threading.currentThread().getName()
        self.wfile.write(message)
        self.wfile.write('\n')
        return

class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
    """Handle requests in a separate thread."""

if __name__ == '__main__':
    server = ThreadedHTTPServer(('localhost', 8080), Handler)
    print 'Starting server, use <Ctrl-C> to stop'
    server.serve_forever()

【讨论】:

  • 请注意,ThreadingMixIn 在超类列表中必须位于 HTTPServer 之前,否则将不起作用
  • 更详细的例子在Python3 docs
  • 这不会流式传输。使用BaseHTTPServer 的更好方法在这里:stackoverflow.com/questions/46210672
  • 我无法发现此答案中的代码与问题之间的任何本质区别。有吗?
  • Python 3.7:不起作用,ModuleNotFoundError:没有名为“BaseHTTPServer”的模块
猜你喜欢
  • 2017-03-05
  • 1970-01-01
  • 2013-11-15
  • 2016-11-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多