one

简单的说就是服务端监听 socket 每次 accept 一个新的请求后,开一个线程处理 这个 socket 客户连接。如果你对底层实现原理感兴趣,可以继续看下去,从 socket 编程的角度来解释多线程 wsgi server。最后附上一个异步框架工作过程的视频讲解。
这里我们自己撸一个简单的多线程 wsgi server 来看下原理,还是需要深入源码和 socket 编程你才能真正理解。 我们从 python 自带的一个 wsgi server 看下如何实现多线程处理请求。首先你需要熟悉下 wsgi。 看一个最简单的 wsgi app:

  1. #!/usr/bin/env python
  2. # -*- coding:utf-8 -*-
  3. def application(environ, start_response):
  4. status = '200 OK'
  5. headers = [('Content-Type', 'text/html; charset=utf8')]
  6. start_response(status, headers)
  7. return [b"<h1>Hello</h1>"]
  8. if __name__ == '__main__':
  9. from wsgiref.simple_server import make_server
  10. httpd = make_server('127.0.0.1', 8000, application)
  11. httpd.serve_forever()

然后用你的开发工具跟进去 make_server 这个函数,看下它的定义:

  1. # lib/python2.7/wsgiref/simple_server.py
  2. def make_server(
  3. host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler
  4. ):
  5. """Create a new WSGI server listening on `host` and `port` for `app`"""
  6. server = server_class((host, port), handler_class)
  7. server.set_app(app)
  8. return server
  9. class WSGIServer(HTTPServer):
  10. """BaseHTTPServer that implements the Python WSGI protocol"""
  11. application = None
  12. def server_bind(self):
  13. """Override server_bind to store the server name."""
  14. HTTPServer.server_bind(self)
  15. self.setup_environ()
  16. def setup_environ(self):
  17. # Set up base environment
  18. env = self.base_environ = {}
  19. env['SERVER_NAME'] = self.server_name
  20. env['GATEWAY_INTERFACE'] = 'CGI/1.1'
  21. env['SERVER_PORT'] = str(self.server_port)
  22. env['REMOTE_HOST']=''
  23. env['CONTENT_LENGTH']=''
  24. env['SCRIPT_NAME'] = ''
  25. def get_app(self):
  26. return self.application
  27. def set_app(self,application):
  28. self.application = application

看到这个 WSGIServer 定义了吗,继承了一个 HttpServer。我们再继续追一下其定义:

  1. # lib/python2.7/BaseHTTPServer.py
  2. class HTTPServer(SocketServer.TCPServer):
  3. allow_reuse_address = 1 # Seems to make sense in testing environment
  4. def server_bind(self):
  5. """Override server_bind to store the server name."""
  6. SocketServer.TCPServer.server_bind(self)
  7. host, port = self.socket.getsockname()[:2]
  8. self.server_name = socket.getfqdn(host)
  9. self.server_port = port

到这里,我们继续追,看到 TcpServer 定义

  1. # lib/python2.7/SocketServer.py
  2. class TCPServer(BaseServer):
  3. """这里我省略了定义"""

你还可以发现一个 ThreadingTCPServer 类:我们看下它的定义
class ThreadingTCPServer(ThreadingMixIn, TCPServer): <br /> pass
好了,怎么多线程处理请求呢?看下这个 ThreadingMixIn 类是如何实现的:

  1. class ThreadingMixIn:
  2. """Mix-in class to handle each request in a new thread."""
  3. # Decides how threads will act upon termination of the
  4. # main process
  5. daemon_threads = False
  6. def process_request_thread(self, request, client_address):
  7. """Same as in BaseServer but as a thread.
  8. In addition, exception handling is done here.
  9. """
  10. try:
  11. self.finish_request(request, client_address)
  12. self.shutdown_request(request)
  13. except:
  14. self.handle_error(request, client_address)
  15. self.shutdown_request(request)
  16. def process_request(self, request, client_address):
  17. """Start a new thread to process the request."""
  18. t = threading.Thread(target = self.process_request_thread,
  19. args = (request, client_address))
  20. t.daemon = self.daemon_threads
  21. t.start()

看到吗,其实就是对于每个新请求,会启动一个新的线程来处理 socket 请求。假如让我们自己实现一个多线程 socket server 应该怎么实现呢?先来写一个简单的单线程

  1. socker echo server:from socket import * # 偷懒这么写
  2. s = socket(AF_INET, SOCK_STREAM)
  3. s.bind(("", 9000))
  4. s.listen(5)
  5. while True:
  6. c, a = s.accept()
  7. print "Received connection from", a
  8. c.send("Hello %s\\n" % a[0])
  9. c.close()

你可以用telnet之类的工具连上该端口看效果。 这样一次只能处理一个请求,如果想每次来一个请求用一个线程处理呢?我们可以这样做:

  1. import threading
  2. from socket import *
  3. def handle_client(c):
  4. # 处理 client 请求
  5. c.send("Hello\n")
  6. c.close()
  7. return
  8. s = socket(AF_INET, SOCK_STREAM)
  9. s.bind(("", 9000))
  10. s.listen(5)
  11. while True:
  12. c, a = s.accept()
  13. t = threading.Thread(target=handle_client,
  14. args=(c,))

是不是很简单,这其实就是多线程工作的原理,每次 accept 得到一个新的客户端请求以后开一个线程去处理。当然 socket 编程还是偏底层,我们刚才看到了 python 提供了 SocketServer 模块来简化 socket 编程。我们使用 SocketServer 模块来发送数据:

  1. import SocketServer
  2. import time
  3. class TimeHandler(SocketServer.BaseRequestHandler):
  4. def handle(self):
  5. # self.request 是一个 client socket 对象
  6. self.request.sendall(time.ctime() + "\n")
  7. serv = SocketServer.TCPServer(("", 8889), TimeHandler)
  8. serv.serve_forever()

它的执行原理是这样的:

  • server 循环等待请求到来
  • 对每个 socket 连接,server 创建一个新的 handler 类实例
  • handle() 方法调用处理 client socket 对象,比如发送数据
  • handle() 方法返回后,连接关闭,同时 handler 实例对象销毁

但是这个 server 的处理能力很差,一次只能处理一个请求,我们看下这个模块提供了几个类:

  • TCPServer: 同步的 tcp server
  • ForkingTCPServer: 多进程 tcp server
  • ThreadingTCPServer: 多线程 tcp server

怎么实现一个多线程 tcp server 呢?很简单:

  1. # 改成 ThreadingTCPServer 就行了,代码其他部分不动
  2. serv = SocketServer.ThreadingTCPServer(("",8000),TimeHandler)
  3. serv.serve_forever()

这样一来,新的请求就能被新的线程去处理了。我们就通过线程来增强了并发能力,当然线程开销比较大,不如用协程(抽空会写个用协程实现异步的socker server)。 如果你浏览该模块,还能看到两个 Mixin:

  • ForkingMixin
  • ThreadingMixIn

我们只要继承它们就很容易实现一个多进程或者多线程的 server,比如实现一个多线程的 HTTPServer

  1. from BaseHTTPServer import HTTPServer
  2. from SimpleHTTPServer import SimpleHTTPRequestHandler
  3. from SocketServer import ThreadingMixIn
  4. class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
  5. pass
  6. serv = ThreadedHTTPServer(("", 8080), SimpleHTTPRequestHandler)

好了,看了这么多让我们改造下 Python 自带的 wsgi server 为多线程的:

  1. import time
  2. import SocketServer
  3. import socket
  4. from SimpleHTTPServer import SimpleHTTPRequestHandler
  5. from SocketServer import ThreadingMixIn
  6. class HTTPServer(SocketServer.TCPServer):
  7. allow_reuse_address = 1 # Seems to make sense in testing environment
  8. def server_bind(self):
  9. """Override server_bind to store the server name."""
  10. SocketServer.TCPServer.server_bind(self)
  11. host, port = self.socket.getsockname()[:2]
  12. self.server_name = socket.getfqdn(host)
  13. self.server_port = port
  14. class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
  15. pass
  16. class ThreadWSGIServer(ThreadedHTTPServer):
  17. """BaseHTTPServer that implements the Python WSGI protocol"""
  18. application = None
  19. def server_bind(self):
  20. """Override server_bind to store the server name."""
  21. HTTPServer.server_bind(self)
  22. self.setup_environ()
  23. def setup_environ(self):
  24. # Set up base environment
  25. env = self.base_environ = {}
  26. env['SERVER_NAME'] = self.server_name
  27. env['GATEWAY_INTERFACE'] = 'CGI/1.1'
  28. env['SERVER_PORT'] = str(self.server_port)
  29. env['REMOTE_HOST'] = ''
  30. env['CONTENT_LENGTH'] = ''
  31. env['SCRIPT_NAME'] = ''
  32. def get_app(self):
  33. return self.application
  34. def set_app(self, application):
  35. self.application = application
  36. def application(environ, start_response):
  37. time.sleep(10) # 注意这里的 sleep
  38. status = '200 OK'
  39. headers = [('Content-Type', 'text/html; charset=utf8')]
  40. start_response(status, headers)
  41. return [b"<h1>Hello</h1>"]
  42. if __name__ == '__main__':
  43. from wsgiref.simple_server import make_server
  44. httpd = make_server('127.0.0.1', 8000, application, server_class=ThreadWSGIServer)
  45. httpd.serve_forever()

对了,我们怎么证明这个真是多线程的 wsgi server 了呢,很简单。你看我加了个 sleep(10)。 如果你在之前的单线程 wsgi server 跑,你可以开俩个终端去 curl,curl 完一个赶紧切到另一个 curl 。 单线程你会发现几乎是 10 + 10 秒,但是下边这个多线程 wsgi server 你会发现大概只用10秒,两个请求是并发的(当然我这种测试很 low,你可以随便用一个网络测试工具)。虽然我没看过 django wsgi server 的实现,但是它自己实现的开发服务器原理应该是类似的。
如果你能坚持看到这里,是更明白了呢还是更懵了呢?
你可以看下python_web_framework.ipynb” class=” wrap external” target=”_blank” rel=”nofollow noreferrer”>PegasusWang/notebooks 这个是手撸 web 框架的教程,看完你就对 wsgi,如何自己写 框架以及 gunicorn 部署有点概念了。最近在学习异步框架的工作原理,感兴趣可以看看,之后还会出个协程版本的。
Pegasus Wang:Python 异步 web 框架是如何工作的[视频]161 赞同 · 24 评论文章

two

作者:胡阳
链接:https://www.zhihu.com/question/56472691/answer/293292349
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

对于初学Web开发,理解一个web server如何能同事处理多个请求很重要。当然更重要的是,理解你通过浏览器发送的请求web server是怎么处理的,然后怎么返回给浏览器,浏览器才能展示的。
我到现在还记得大概在2010年左右,看了tomcat源码之后,那种豁然开朗的感觉(对,当时我还在写Java)。搞技术的人,总是希望花点时间,能够更透彻的看透整个网络世界的构成,而不是那啥。
要理解web server如何能处理多个请求有两个基本要素

  • 第一,知道怎么通过socket编程,这也是我在视频中强调的一点,理解这点之后再去看看WSGI,你就知道Python世界中大部分的框架怎么运作了。
  • 第二,多线程编程,理解了这个,你才能知道怎么着我起了一个web server,就能处理多个请求。 多进程也是一样的逻辑。

通过这两段代码,应该很好理解:

呃,知乎的编辑器还是那么难用,尤其是贴代码时, :)
@Pegasus Wang
image.png
image.png
想要copy代码的话,去原文看吧,知乎这贴代码很麻烦。

试试吧,与其着急去学习框架不如先弄懂这个。

参考

原文地址:PythonWebServer如何同时处理多个请求 | the5fire的技术博客