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

相关推荐
涔溪42 分钟前
Vue3 中ref和reactive的核心区别是什么?
前端·vue.js·typescript
天意__1 小时前
Flutter开发,scroll_to_index适配flutter_list_view
前端·flutter
吉星9527ABC1 小时前
表示离散量的echarts图型示例
前端·arcgis·echarts·离散量web展示
光影少年1 小时前
web3学习路线
前端·学习·前端框架·web3
克喵的水银蛇1 小时前
Flutter 状态管理:Provider 入门到实战(替代 setState)
前端·javascript·flutter
鹏多多1 小时前
flutter-使用url_launcher打开链接/应用/短信/邮件和评分跳转等
android·前端·flutter
刻刻帝的海角1 小时前
响应式数据可视化 Dashboard
开发语言·前端·javascript
小飞侠在吗1 小时前
vue3 中的 ref 和 reactive
前端·javascript·vue.js
0思必得01 小时前
[Web自动化] 开发者工具控制台(Console)面板
前端·javascript·python·自动化·web自动化·开发者工具