洞察纵观鸿蒙next版本,如何凭借FinClip加强小程序的跨平台管理,确保企业在数字化转型中的高效运营和数据安全?
642
2022-09-20
tornado服务器实现原理
本文分析的tornado版本为1.0.0, 它的代码量比较少, 便于我们找到其核心部分. 在这里可以-1.0.0版本的tornado.
一.基本流程
使用下面的代码实现一个最简单的tornado服务器:
这里使用了tornado的httpserver, ioloop和web三个模块, 其中httpserver就是http服务器, 它负责接收和处理连接; ioloop则是底层的事件循环系统, 负责在监听到事件时进行通知; web模块就相当于web应用.
总的来说, 一个tornado服务器可以分为四层, 工作流程大致是下面这样:
上面这张图可能有点复杂, 一时看不懂没关系, 后面会进行详细的讲解.
二.异步非阻塞socket
tornado的高性能主要来自于ioloop.IOLoop和iostream.IOStream两个类, 前者是一个事件循环, 通过epoll对不同的socket对象进行监听和调度. IOStream类则是socket对象的封装, 它依靠着IOLoop的事件循环, 实现了对socket读写功能的非阻塞+异步回调.
ioloop.IOLoop的主要代码如下:
IOLoop的本质是对epoll的封装, 它的用法比较简单: 首先, 我们可以调用add&update&remove_handler方法来设置需要监听的句柄, 对应的事件和回调函数, 然后, 只要调用start方法, IOLoop就会使用epoll一直监听下去, 并且在监听到事件时, 调用对应的回调函数, 这样就实现了监听和调度的功能.
iostream.IOStream类的主要代码如下:
IOStream本质是一个socket对象, 只不过通过事件循环变为异步的了. 我们调用它的read_until或者write方法时, IOStream并不会马上尝试去读取或写入数据, 而是设置一个回调函数, 然后调用_add_io_state方法在事件循环中添加对可读或可写事件的监控. 然后, 事件循环在监听到事件时, 调用IOStream的_handle_events方法, 该方法根据事件的类型再调用_handle_read和_handle_write去读取或写入数据, 并调用之前设定好的回调, 这样一次读取&写入才算结束.
除此之外, IOStream还将自己的socket设置为非阻塞的状态, 避免在socket不可读&不可写的情况下产生阻塞. tornado的高性能主要就是因为事件循环回调和非阻塞socket这两点, 首先, 异步回调的机制可以使tornado在单个线程中同时维护多个socket连接, 当某个连接触发事件时, 调用回调去处理就行. 然后, socket的非阻塞状态可以避免处理事件时产生的阻塞, 从而最大程度地利用CPU时间.
总的来说, IOStream + IOLoop的工作流程如下:
三.web服务器
httpserver模块中有三个类: HTTPServer, HTTPConnection和HTTPRequest, HTTPServer相当于服务端socket的封装, 负责接收客户端的连接. 该连接会交由HTTPConnection去处理, HTTPConnection利用iostream模块读取客户端的请求数据, 然后将请求数据封装成一个HTTPRequest对象, 将这个对象交由web应用去处理.
HTTPServer的主要代码如下:
HTTPServer是web服务器端的入口, 首先, 我们通过实例化这个对象来指定web服务器所配套的web应用. 然后, 调用它的listen方法, 就会通过ioloop监听指定端口的可读事件, 也就是客户端连接. 当有客户端连接时, HTTPServer会首先实例化一个IOStream对象, 这个对象相当于对客户端socket对象的封装, 然后新建一个HTTPConnection对象去处理这个新连接.
HTTPConnection的主要代码如下:
在HTTPServer接收到新连接后, 由HTTPConnection来处理这个新连接. 首先, HTTPConnection使用IOStream异步回调地读取客户端的请求数据, 解析出请求行的内容以及请求头数据之后, 将这些数据封装到一个HTTPRequest对象中, 让web应用去处理这个请求对象. web应用处理结束后, 再调用它的write方法, 通过IOStream将响应数据写入, 最后关闭socket连接, 这样一个请求就处理完毕了.
HTTPRequest主要是对请求数据的封装, 没什么好说的. 它的主要代码如下:
这样, 一个http服务器就完成了, 它的流程像是下面这样:
四.web应用
web应用的职责是, 接收web服务器发过来的请求数据, 根据这些数据执行一些逻辑之后, 返回响应结果. tornado的web模块就负责web应用这块.
我们首先分析web.Application类, 简单来说, 它的代码差不多是下面这样:
web.Application是web应用的入口, 由刚才的代码可以看出来, 它负责路由的分发. 首先我们实例化对象并传入handlers = [(r'/', MainHandler)]这样的参数, 然后调用这个Application对象并传入request, 它就会根据请求数据所给的路径找到对应的handler类, 实例化这个handler类并调用handler的_execute方法, 让handler对象来执行具体的操作.
一般来说, 我们指定的handler类都会继承web.RequestHandler, 它的代码差不多是下面这样:
RequestHandler对响应进行了封装. Application调用它的_execute方法, 就会根据请求类型反射到我们所重写的方法, 比如get方法. 在执行完我们定义的方法之后, 调用自己的finish方法来生成响应消息, 并通过request将响应消息返回.
Application和RequestHandler实现了一个web应用的框架, 用户只需要继承RequestHandler类, 然后重写请求类型的对应方法就可以了. 总的来看, 这个web应用的处理流程如下:
五.总结
综上所述, tornado服务器可以分为四层: 事件循环层, TCP传输层, HTTP层和web应用, 工作起来像是下面这样:
在写demo应用的阶段, 我们做了四件事:
继承RequestHandler, 重写请求类型对应的方法, 比如get方法
定义Application的路由
为HTTPServer指定app和端口
启动IOLoop
这样, 一个tornado应用就启动了, 一个请求的流程是这样的:
IOLoop监听到新的客户端连接, 通知HTTPServer
HTTPServer实例化一个HTTPConnection来处理这个新的客户端
HTTPConnection利用IOStream异步读取客户端的请求数据
IOStream通过IOLoop注册可读事件, 在事件触发时读取数据, 然后调用HTTPConnection的回调函数
HTTPConnection将读取的请求数据进行解析, 用一个HTTPRequest对象封装解析后的请求数据
HTTPConnection把HTTPRequest发送给Application
Application通过路由找到对应的RequestHandler, 让它来处理请求
RequestHandler通过反射找到请求类型对应的处理方法, 处理请求
处理完成后, RequestHandler调用HTTPRequest的write方法写入响应结果
HTTPRequest将响应结果交给HTTPConnection, HTTPConnection使用IOStream来写入响应数据
IOStream继续使用IOLoop异步地写入数据, 写入完毕后, 调用HTTPConnection的回调函数
HTTPConnection被回调, 它关闭socket连接, 请求结束 (http1.1或者keep-alive的情况不讨论)
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。
发表评论
暂时没有评论,来抢沙发吧~