49、【Ubuntu】【Gitlab】拉出内网 Web 服务:http.server 单/多线程分析(一)

【声明】本博客所有内容均为个人业余时间创作,所述技术案例均来自公开开源项目(如Github,Apache基金会),不涉及任何企业机密或未公开技术,如有侵权请联系删除

背景

上篇 blog
【Ubuntu】【Gitlab】拉出内网 Web 服务:Nginx 事件驱动分析(二)

分析了 Nginx 的高性能设计,多进程模型,配合事件驱动 + 非阻塞 I/O + epoll(Linux),下面继续

Python http.server 单/多线程分析

分析完了 Nginx 的高性能模型,下面再对比下之前的 Python http.server 模型

首先,在分析多线程模型之前,有个比较有意思的点,首先终端输入

bash 复制代码
python3 --version

可以看到本地版本号是 v3.12.3

然后查看 http/server.py 实现如下

python 复制代码
# ... 前面省略
def test(HandlerClass=BaseHTTPRequestHandler,
         ServerClass=ThreadingHTTPServer,
         protocol="HTTP/1.0", port=8000, bind=None):
    """Test the HTTP request handler class.

    This runs an HTTP server on port 8000 (or the port argument).

    """
    ServerClass.address_family, addr = _get_best_family(bind, port)
    HandlerClass.protocol_version = protocol
    with ServerClass(addr, HandlerClass) as httpd:
        host, port = httpd.socket.getsockname()[:2]
        url_host = f'[{host}]' if ':' in host else host
        print(
            f"Serving HTTP on {host} port {port} "
            f"(http://{url_host}:{port}/) ..."
        )
        try:
            httpd.serve_forever()
        except KeyboardInterrupt:
            print("\nKeyboard interrupt received, exiting.")
            sys.exit(0)

if __name__ == '__main__':
    import argparse
    import contextlib

    parser = argparse.ArgumentParser()
    parser.add_argument('--cgi', action='store_true',
                        help='run as CGI server')
    parser.add_argument('-b', '--bind', metavar='ADDRESS',
                        help='bind to this address '
                             '(default: all interfaces)')
    parser.add_argument('-d', '--directory', default=os.getcwd(),
                        help='serve this directory '
                             '(default: current directory)')
    parser.add_argument('-p', '--protocol', metavar='VERSION',
                        default='HTTP/1.0',
                        help='conform to this HTTP version '
                             '(default: %(default)s)')
    parser.add_argument('port', default=8000, type=int, nargs='?',
                        help='bind to this port '
                             '(default: %(default)s)')
    args = parser.parse_args()
    if args.cgi:
        handler_class = CGIHTTPRequestHandler
    else:
        handler_class = SimpleHTTPRequestHandler

    # ensure dual-stack is not disabled; ref #38907
    class DualStackServer(ThreadingHTTPServer):

        def server_bind(self):
            # suppress exception when protocol is IPv4
            with contextlib.suppress(Exception):
                self.socket.setsockopt(
                    socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
            return super().server_bind()

        def finish_request(self, request, client_address):
            self.RequestHandlerClass(request, client_address, self,
                                     directory=args.directory)

    test(
        HandlerClass=handler_class,
        ServerClass=DualStackServer,
        port=args.port,
        bind=args.bind,
        protocol=args.protocol,
    )

可以看到这里默认启用的是多线程模型

这里要先解释先 http.server单线程和多线程模式 ,首先说单线程模式,单线程的处理类如下

可以看到,HTTPServersocketserver.TCPServer 的子类,HTTPServer 继承的是 TCPServer 单线程同步处理的默认行为,其执行流程如下:

  • Python http.server 启动一个单线程/进程
  • 接收一个客户端连接(accept 方法阻塞接收)
  • 调用 handle 方法处理这个请求(比如读文件,返回 HTTP 响应)
  • 这个请求需要完全处理完,连接关闭后,才能处理下一个请求

这里单线程执行的逻辑点在于,第二个请求必须等第一个请求结束才能被处理,无法并发 ,下面来测试验证一下这个单线程阻塞行为

首先,在 index.html 目录下新建一个 slow_server.py 启动文件,用来模拟低速处理服务器,slow_server.py 的内容如下

python 复制代码
#!/usr/bin/env python3

from http.server import HTTPServer, SimpleHTTPRequestHandler
import time

class SlowHandler(SimpleHTTPRequestHandler):
    def do_GET(self):
        print(f"Handling {self.path} ... (will sleep 10s)")
        time.sleep(10)  # 模拟耗时操作
        """Serve a GET request."""
        f = self.send_head()
        if f:
            try:
                self.copyfile(f, self.wfile)
            finally:
                f.close()

server = HTTPServer(('localhost', 2027), SlowHandler)
server.serve_forever()

这里解释下里面几个点

  • 首先是 SlowHandler 是继承了 http.server 模块里面的静态处理类 SimpleHTTPRequestHandlerSlowHandler 就是准备模拟的低速服务器
  • 然后这里 SlowHandler 重写了下 SimpleHTTPRequestHandler 里面的 do_Get 方法,注意,这里重写的 do_Get 方法几乎就是拷贝的 SimpleHTTPRequestHandler 里面的 do_Get 方法,唯一不同的,就是加了两行,一行是 print 打印,另一行是 time.sleep 延时操作,相当于收到请求后,延时 10s 再处理
  • 然后最后是用 HTTPServer 启动该服务,该 Web 服务可以通过 2027 端口进行访问,注意,这里必须是 HTTPServer,因为 HTTPServer 是单线程,现在就是要测试验证单线程的阻塞行为

OK,用 chmod 777slow_server.py 赋予执行权限,然后在该目录(index.html 同级目录)目录下执行 ./slow_server.py 启动这个模拟低速 Web 服务

然后终端输入

bash 复制代码
time curl http://localhost:2027/a

可以测试这个 Web 服务的连接时间

终端输入 man time,查看到该命令的描述如下

可以看到,time 命令是一个测量程序运行时资源消耗的命令(不仅仅是测量时间,虽然名字叫 time ,比如花了多少 CPU 时间,实际耗时,内存使用等,但这个属于 GNU time,需要用 /usr/bin/time 命令(和上面的不是同一个命令,虽然都叫 time ,关于 GNU time 就不展开分析了

直接在终端输入 time 命令的话(上面用的),只能测量时间 如下


OK,本篇先到这里,如有疑问,欢迎评论区留言讨论,祝各位功力大涨,技术更上一层楼!!!更多内容见下篇 blog

相关推荐
代码搬运媛8 小时前
Jest 测试框架详解与实现指南
前端
counterxing9 小时前
我把 Codex 里的 Skills 做成了一个 MCP,还支持分享
前端·agent·ai编程
wangqiaowq9 小时前
windows下nginx的安装
linux·服务器·前端
之歆9 小时前
DAY_12JavaScript DOM 完全指南(二):实战与性能篇
开发语言·前端·javascript·ecmascript
发现一只大呆瓜9 小时前
Vite凭什么这么快?3分钟带你彻底搞懂 Vite 热更新的幕后黑手
前端·面试·vite
Maimai1080810 小时前
React如何用 @microsoft/fetch-event-source 落地 SSE:比原生 EventSource 更灵活的实时推送方案
前端·javascript·react.js·microsoft·前端框架·reactjs·webassembly
kyriewen11 小时前
产品经理把PRD写成“天书”,我用AI半小时重写了一遍,他当场愣住
前端·ai编程·cursor
不仙52012 小时前
VMware Workstation 26.0.0 在 Ubuntu 24.04 (内核 6.17.0) 上的安装与内核模块编译问题
linux·ubuntu·elasticsearch
humcomm12 小时前
元框架的工作原理详解
前端·前端框架
canonical_entropy12 小时前
Attractor Before Harness: AI 大规模开发的方法论
前端·aigc·ai编程