tornado高效开发必备之源码详解
前言:本博文重在tornado源码剖析,相信读者读完此文能够更加深入的了解tornado的运行机制,从而更加高效的使用tornado框架。
本文参考武sir博客地址:http://www.cnblogs.com/wupeiqi/tag/Tornado/
初识tornado
首先我们从经典的hello world 案例入手:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | import tornado.ioloop import tornado.web class MainHandler(tornado.web.RequestHandler): def get( self ): self .write( "Hello, world" ) application = tornado.web.Application([ (r "/index" , MainHandler), ]) if __name__ = = "__main__" : application.listen( 8888 ) tornado.ioloop.IOLoop.instance().start() |
运行该脚本,依次执行:
- 创建一个Application对象,并把一个正则表达式'/'和类名MainHandler传入构造函数:tornado.web.Application(...)
- 执行Application对象的listen(...)方法,即:application.listen(8888)
- 执行IOLoop类的类的 start() 方法,即:tornado.ioloop.IOLoop.instance().start()
1.首先我们来分析如下代码:
1 2 3 | application = tornado.web.Application([ (r "/index" , MainHandler), ]) |
源码截图:
由上图Application类的源码我们可以看出,需要传入的第一个参数是handlers,即上述代码可以进行如下表示:
1 2 3 | application = tornado.web.Application(handlers = [ (r "/index" , MainHandler), ]) |
这里的参数handlers非常重要,值得我们更加深入的研究。它应该是一个元组组成的列表,其中每个元组的第一个元素是一个用于匹配的正则表达式,第二个元素是一个RequestHanlder类。在hello.py中,我们只指定了一个正则表达式-RequestHanlder对,但你可以按你的需要指定任意多个。
1 | 2.Application 类的深入:源码截图: |
这是源码中关于静态文件路径的描述,从上到下的意思依次为:
- 如果用户在settings有配置名为“static_path”的文件路径(这就要求我们如果需要配置静态文件路径,则key值必须是“static_path”)
从源码可看出,这是根据字典的方式进行取值,因此可断定settings是字典格式
,这是配置静态文件路径前缀,便于tronado在前端的静态文件路径中找到静态文件,即告诉tronado,我是静态文件,按照静态文件路径查询即可,这里,静态文件前缀也可以理解为静态文件标识。
3.application.listen(8888):
1 | application.listen( 8888 ) |
即Application类的listen方法:
源码截图:
从上述源码可看出listen方法接收port端口,address即ip地址,默认为空。然后实例化HTTPServer对象,执行listen方法:
HTTPServer类listen方法源码截图:
上述listen方法绑定了ip和端口,并开启socket。
bind方法源码截图:
由上述源码可看出,bind方法内部创建socket对象,调用socket对象绑定ip和端口,并进行监听。
4.tornado.ioloop.IOLoop.instance().start():
1 | tornado.ioloop.IOLoop.instance().start() |
这是tronado的ioloop.py文件中的IOLoop类:
instance方法源码截图:
这里使用了类方法,即可以通过类名直接访问。我们需要关注的是最后面的代码:
1 2 3 | if not hasattr ( cls , "_instance" ): cls ._instance = cls () return cls ._instance |
注:这里使用了单例模式,因为我们不需要为每一次连接IO都创建一个对象,换句话说,每次连接IO只需要是同一个对象即可
start方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | class IOLoop( object ): def add_handler( self , fd, handler, events): #HttpServer的Start方法中会调用该方法 self ._handlers[fd] = stack_context.wrap(handler) self ._impl.register(fd, events | self .ERROR) def start( self ): while True : poll_timeout = 0.2 try : #epoll中轮询 event_pairs = self ._impl.poll(poll_timeout) except Exception, e: #省略其他 #如果有读可用信息,则把该socket对象句柄和Event Code序列添加到self._events中 self ._events.update(event_pairs) #遍历self._events,处理每个请求 while self ._events: fd, events = self ._events.popitem() try : #以socket为句柄为key,取出self._handlers中的stack_context.wrap(handler),并执行 #stack_context.wrap(handler)包装了HTTPServer类的_handle_events函数的一个函数 #是在上一步中执行add_handler方法时候,添加到self._handlers中的数据。 self ._handlers[fd](fd, events) except : #省略其他 |
由上述源码中while Ture:可以看出当执行了start方法后程序会进入死循环,不断检测是否有用户发送请求过来,如果有请求到达,则执行封装了HttpServer类的_handle_events方法和相关上下文的stack_context.wrap(handler)(其实就是执行HttpServer类的_handle_events方法),详细见下篇博文,简要代码如下:
class HTTPServer(object): def _handle_events(self, fd, events): while True: try: connection, address = self._socket.accept() except socket.error, e: if e.args[0] in (errno.EWOULDBLOCK, errno.EAGAIN): return raise if self.ssl_options is not None: assert ssl, "Python 2.6+ and OpenSSL required for SSL" try: connection = ssl.wrap_socket(connection, server_side=True, do_handshake_on_connect=False, **self.ssl_options) except ssl.SSLError, err: if err.args[0] == ssl.SSL_ERROR_EOF: return connection.close() else: raise except socket.error, err: if err.args[0] == errno.ECONNABORTED: return connection.close() else: raise try: if self.ssl_options is not None: stream = iostream.SSLIOStream(connection, io_loop=self.io_loop) else: stream = iostream.IOStream(connection, io_loop=self.io_loop) HTTPConnection(stream, address, self.request_callback, self.no_keep_alive, self.xheaders) except: logging.error("Error in connection callback", exc_info=True)
5.tornado.web.RequestHandler
这是所有业务处理handler需要继承的父类,接下来,介绍一些RequestHandler类中常用的一些方法:
- initialize:
从源码中可以看出initialize函数会在RequestHandler类初始化的时候执行,但是源码中initialize函数并没有做任何事情,这其实是tornado为我们预留的修改源码的地方,这就允许程序在执行所有的handler前首先执行我们在initialize中定义的方法。
- write
源码截图:
write方法是后端向前端页面直接展示的方法。从上述源码中可以看出,write方法接收字典和字符串类型的参数,如果用户传来的数据是字典类型,源码中会自动用json对字典进行序列化,最终序列化成字符串。
self._write_buffer是源码中定义的一个临时存放需要输出的字符串的地方,是列表格式。
- 需要write的内容添加到 self._write_buffer后,系统会执行flush方法:
flush方法源码截图:
由上述源码可以看出:flush方法会self._write_buffer列表中的所有元素拼接成字符串,并赋值给chunk,然后清空self._write_buffer列表,然后设置请求头,最终调用request.write方法在前端页面显示。
- render方法:
源码截图:
由上述源码可看出render方法是根据参数渲染模板,下面我们来介绍具体源码中是如何渲染的:
js和css部分的源码截图:
由上述源码可看出,静态文件(以JavaScript为例,css是类似的)的渲染流程是:
首先通过module.embedded_javascript() 获取需要插入JavaScript字符串,添加到js_embed 列表中;
进而通过module.javascript_files()获取已有的列表格式的JavaScript files,最终将它加入js_files.
下面对js_embed和js_files做进一步介绍:
js_embed源码截图:
上图源码即生成script标签,这是一些我们自己定义的一些JavaScript代码;最终是通过字符串拼接方式插入到整个html中。
js_files源码截图:
上图源码即生成script标签,这是一些需要引入的JavaScript代码块;最终是通过字符串拼接方式插入到整个html中。需要注意的是:其中静态路径是调用self.static_url(path)实现的。
static_url方法源码截图:
由上述代码可看出:源码首先会判断用户有没有设置静态路径的前缀,然后将静态路径与相对路径进行拼接成绝对路径,接下来按照绝对路径打开文件,并对文件内容(f.read())做md5加密,最终将根目录+静态路径前缀+相对路径拼接在前端html中展示。
render_string方法:
这是render方法中最重要的一个子方法,它负责去处理Html模板并返回最终结果:
详细流程:
- 创建Loader对象,并执行load方法
-- 通过open函数打开html文件并读取内容,并将内容作为参数又创建一个 Template 对象
-- 当执行Template的 __init__ 方法时,根据模板语言的标签 {{}}、{%%}等分割并html文件,最后生成一个字符串表示的函数 - 获取所有要嵌入到html模板中的变量,包括:用户返回和框架默认
- 执行Template对象的generate方法
-- 编译字符串表示的函数,并将用户定义的值和框架默认的值作为全局变量
-- 执行被编译的函数获取被嵌套了数据的内容,然后将内容返回(用于响应给请求客户端)
源码注释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | class RequestHandler( object ): def render_string( self , template_name, * * kwargs): #获取配置文件中指定的模板文件夹路径,即:template_path = 'views' template_path = self .get_template_path() #如果没有配置模板文件的路径,则默认去启动程序所在的目录去找 if not template_path: frame = sys._getframe( 0 ) web_file = frame.f_code.co_filename while frame.f_code.co_filename = = web_file: frame = frame.f_back template_path = os.path.dirname(frame.f_code.co_filename) if not getattr (RequestHandler, "_templates" , None ): RequestHandler._templates = {} #创建Loader对象,第一次创建后,会将该值保存在RequestHandler的静态字段_template_loaders中 if template_path not in RequestHandler._templates: loader = self .application.settings.get( "template_loader" ) or \ template.Loader(template_path) RequestHandler._templates[template_path] = loader #执行Loader对象的load方法,该方法内部执行执行Loader的_create_template方法 #在_create_template方法内部使用open方法会打开html文件并读取html的内容,然后将其作为参数来创建一个Template对象 #Template的构造方法被执行时,内部解析html文件的内容,并根据内部的 {{}} {%%}标签对内容进行分割,最后生成一个字符串类表示的函数并保存在self.code字段中 t = RequestHandler._templates[template_path].load(template_name) #获取所有要嵌入到html中的值和框架默认提供的值 args = dict ( handler = self , request = self .request, current_user = self .current_user, locale = self .locale, _ = self .locale.translate, static_url = self .static_url, xsrf_form_html = self .xsrf_form_html, reverse_url = self .application.reverse_url ) args.update( self .ui) args.update(kwargs) #执行Template的generate方法,编译字符串表示的函数并将namespace中的所有key,value设置成全局变量,然后执行该函数。从而将值嵌套进html并返回。 return t.generate( * * args) |

class Loader(object): """A template loader that loads from a single root directory. You must use a template loader to use template constructs like {% extends %} and {% include %}. Loader caches all templates after they are loaded the first time. """ def __init__(self, root_directory): self.root = os.path.abspath(root_directory) self.templates = {} Loader.__init__

class Loader(object): def load(self, name, parent_path=None): name = self.resolve_path(name, parent_path=parent_path) if name not in self.templates: path = os.path.join(self.root, name) f = open(path, "r") #读取html文件的内容 #创建Template对象 #name是文件名 self.templates[name] = Template(f.read(), name=name, loader=self) f.close() return self.templates[name] Loader.load

class Template(object): def __init__(self, template_string, name="<string>", loader=None,compress_whitespace=None): # template_string是Html文件的内容 self.name = name if compress_whitespace is None: compress_whitespace = name.endswith(".html") or name.endswith(".js") #将内容封装到_TemplateReader对象中,用于之后根据模板语言的标签分割html文件 reader = _TemplateReader(name, template_string) #分割html文件成为一个一个的对象 #执行_parse方法,将html文件分割成_ChunkList对象 self.file = _File(_parse(reader)) #将html内容格式化成字符串表示的函数 self.code = self._generate_python(loader, compress_whitespace) try: #将字符串表示的函数编译成函数 self.compiled = compile(self.code, self.name, "exec") except: formatted_code = _format_code(self.code).rstrip() logging.error("%s code:\n%s", self.name, formatted_code) raise Template.__init__

class Template(object): def generate(self, **kwargs): """Generate this template with the given arguments.""" namespace = { "escape": escape.xhtml_escape, "xhtml_escape": escape.xhtml_escape, "url_escape": escape.url_escape, "json_encode": escape.json_encode, "squeeze": escape.squeeze, "linkify": escape.linkify, "datetime": datetime, } #创建变量环境并执行函数,详细Demo见上一篇博文 namespace.update(kwargs) exec self.compiled in namespace execute = namespace["_execute"] try: #执行编译好的字符串格式的函数,获取嵌套了值的html文件 return execute() except: formatted_code = _format_code(self.code).rstrip() logging.error("%s code:\n%s", self.name, formatted_code) raise Template.generate
示例html:
源码模板语言处理部分的截图:
结束语
本博文从tornado url正则匹配、路由与映射、底层socket实现、端口监听、多请求并发、Handler类方法的源码剖析,便于tronado web开发人员能够更深入的理解tronado的运行机制,从而更高效的从事tronado web开发。
如果您觉得本文对您有参考价值,欢迎帮博主点击文章下方的推荐,谢谢!
1 |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· 展开说说关于C#中ORM框架的用法!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?