先来看一个简单的示例:

#!/usr/bin/env  python
#coding:utf8 import socket def run():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1',))
sock.listen() while True:
connection,address = sock.accept()
handle_request(connection)
connection.close() def handle_request(client):
but = client.recv()
client.send("HTTP/1.1 200 OK \r\n\r\n")
client.send("Hello everyone!") if __name__ == "__main__":
run()

上述分析:

  1、浏览器其实就是一个socket客户端,而web应用其实就是一个socket服务端,并且web应用在服务器上一直在监听某个端口。

  2、当浏览器请求某个web应用时,需要指定服务器的IP(DNS解析)和端口建立一个socket连接。

  3、建立链接后,web应用根据请求的不同,给用户返回相应的数据。

  4、断开socket连接。(之所以说http是短链接,其实就是因为每次请求完成后,服务器就会断开socket链接)

  对于Web框架来说,一般分为两类,其中一类则是包含上述 4部分 内容的框架,另外一类就是只包含 第3部分 功能的框架。tornado就是一中属于前者的框架。tornado 是一个基于 Python 开发的web框架,较其他 Web 框架的区别是:采用了非阻塞的方式和对epoll的应用。这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。

tornado经典官网DEMO

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()
tornado.ioloop.IOLoop.instance().start()

运行该脚本,依次执行:

  • 创建一个Application对象,并把一个正则表达式'/'和类名MainHandler传入构造函数:tornado.web.Application(...)  

  • 执行Application对象的listen(...)方法,即:application.listen(8888)

  • 执行IOLoop类的类的 start() 方法,即:tornado.ioloop.IOLoop.instance().start()

整个过程其实就是在创建一个socket服务端并监听8888端口,当请求到来时,根据请求中的url和请求方式(post、get或put等)来指定相应的类中的方法来处理本次请求,在上述demo中只为url为http://127.0.0.1:8888/index的请求指定了处理类MainHandler。所以,在浏览器上访问:http://127.0.0.1:8888/index,则服务器给浏览器就会返回 Hello,world ,否则返回 404: Not Found(tornado内部定义的值), 即完成一次http请求和响应。

tornado处理请求的步骤大体可以分为两个部分:启动程序阶段和处理请求阶段

1、在启动程序阶段,第一步,获取配置文件然后生成url映射(即:一个url对应一个XXRequestHandler,从而让XXRequestHandler来处理指定url发送的请求);第二步,创建服务器socket对象并添加到epoll中;第三步,创建无线循环去监听epoll。

2、在接收并处理请求阶段,第一步,接收客户端socket发送的请求(socket.accept);第二步,从请求中获取请求头信息,再然后根据请求头中的请求url去匹配某个XXRequestHandler;第三步,匹配成功的XXRequestHandler处理请求;第四步,将处理后的请求发送给客户端;第五步,关闭客户端socket。

启动程序阶段:

1、执行Application类的构造函数,并传入一个列表类型的参数,这个列表里保存的是url规则和对应的处理类,即:当客户端的请求url可以配置这个规则时,那么该请求就交由对应的Handler(继承自RequestHandler的所有类)去执行。

Application.__init__:

class Application(object):
def __init__(self, handlers=None, default_host="", transforms=None,wsgi=False, **settings):
#设置响应的编码和返回方式,对应的http相应头:Content-Encoding和Transfer-Encoding
#Content-Encoding:gzip 表示对数据进行压缩,然后再返回给用户,从而减少流量的传输。
#Transfer-Encoding:chunck 表示数据的传送方式通过一块一块的传输。
if transforms is None:
self.transforms = []
if settings.get("gzip"):
self.transforms.append(GZipContentEncoding)
self.transforms.append(ChunkedTransferEncoding)
else:
self.transforms = transforms #将参数赋值为类的变量
self.handlers = []
self.named_handlers = {}
self.default_host = default_host
self.settings = settings
#ui_modules和ui_methods用于在模版语言中扩展自定义输出
#这里将tornado内置的ui_modules和ui_methods添加到类的成员变量self.ui_modules和self.ui_methods中
self.ui_modules = {'linkify': _linkify,
'xsrf_form_html': _xsrf_form_html,
'Template': TemplateModule,
}
self.ui_methods = {}
self._wsgi = wsgi
#获取获取用户自定义的ui_modules和ui_methods,并将他们添加到之前创建的成员变量self.ui_modules和self.ui_methods中
self._load_ui_modules(settings.get("ui_modules", {}))
self._load_ui_methods(settings.get("ui_methods", {})) #设置静态文件路径,设置方式则是通过正则表达式匹配url,让StaticFileHandler来处理匹配的url
if self.settings.get("static_path"):
#从settings中读取key为static_path的值,用于设置静态文件路径
path = self.settings["static_path"]
#获取参数中传入的handlers,如果空则设置为空列表
handlers = list(handlers or [])
#静态文件前缀,默认是/static/
static_url_prefix = settings.get("static_url_prefix","/static/")
#在参数中传入的handlers前再添加三个映射:
#【/static/.*】 --> StaticFileHandler
#【/(favicon\.ico)】 --> StaticFileHandler
#【/(robots\.txt)】 --> StaticFileHandler
handlers = [
(re.escape(static_url_prefix) + r"(.*)", StaticFileHandler,dict(path=path)),
(r"/(favicon\.ico)", StaticFileHandler, dict(path=path)),
(r"/(robots\.txt)", StaticFileHandler, dict(path=path)),
] + handlers
#执行本类的Application的add_handlers方法
#此时,handlers是一个列表,其中的每个元素都是一个对应关系,即:url正则表达式和处理匹配该正则的url的Handler
if handlers: self.add_handlers(".*$", handlers)
# Automatically reload modified modules
#如果settings中设置了 debug 模式,那么就使用自动加载重启
if self.settings.get("debug") and not wsgi:
import autoreload
autoreload.start()

2、Application.add_Handlers:

class Application(object):
def add_handlers(self, host_pattern, host_handlers):
#如果主机模型最后没有结尾符,那么就为他添加一个结尾符。
if not host_pattern.endswith("$"):
host_pattern += "$"
handlers = []
#对主机名先做一层路由映射,例如:http://www.5ihouse.com 和 http://safe.5ihouse.com
#即:safe对应一组url映射,www对应一组url映射,那么当请求到来时,先根据它做第一层匹配,之后再继续进入内部匹配。 #对于第一层url映射来说,由于.*会匹配所有的url,所将 .* 的永远放在handlers列表的最后,不然 .* 就会先匹配了...
#re.complie是编译正则表达式,以后请求来的时候只需要执行编译结果的match方法就可以去匹配了
if self.handlers and self.handlers[-][].pattern == '.*$':
self.handlers.insert(-, (re.compile(host_pattern), handlers))
else:
self.handlers.append((re.compile(host_pattern), handlers))
#遍历我们设置的和构造函数中添加的【url->Handler】映射,将url和对应的Handler封装到URLSpec类中(构造函数中会对url进行编译)
#并将所有的URLSpec对象添加到handlers列表中,而handlers列表和主机名模型组成一个元祖,添加到self.Handlers列表中。
for spec in host_handlers:
if type(spec) is type(()):
assert len(spec) in (, )
pattern = spec[]
handler = spec[]
if len(spec) == :
kwargs = spec[]
else:
kwargs = {}
spec = URLSpec(pattern, handler, kwargs)
handlers.append(spec)
if spec.name:
#未使用该功能,默认spec.name = None
if spec.name in self.named_handlers:
logging.warning("Multiple handlers named %s; replacing previous value",spec.name)
self.named_handlers[spec.name] = spec

3、URLspec:

class URLSpec(object):
def __init__(self, pattern, handler_class, kwargs={}, name=None):
if not pattern.endswith('$'):
pattern += '$'
self.regex = re.compile(pattern)
self.handler_class = handler_class
self.kwargs = kwargs
self.name = name
self._path, self._group_count = self._find_groups()

上述三个步骤主要完成了:

1、静态文件路径设置

2、ui_modules和ui_methods(模板语言中使用)

3、是否debug模式运行

4、生成URL映射

5、封装数据,将配置信息和url映射关系封装到Application对象中

6、保存编码和返回方式信息(self.transforms)

7、self.settings 保存配置信息

8、self.Handler 保存着所有的主机名对应的Handlers,每个handlers则是url正则对应的Handler

以上为application = tornado.web.Application([(r"/index",MainHandler),])完成的工作

 

   4、application.listen(8888)

1-3步的操作将配置和url映射等信息封装到了application对象中,而这第二步执行application对象的listen方法,该方法内部又把之前包含各种信息的application对象封装到了一个HttpServer对象中,然后继续调用HttpServer对象的liseten方法。

class Application(object):
#创建服务端socket,并绑定IP和端口并添加相应设置,注:未开始通过while监听accept,等待客户端连接
def listen(self, port, address="", **kwargs):
from tornado.httpserver import HTTPServer
server = HTTPServer(self, **kwargs)
server.listen(port, address)

5、HTTPServer类代码

class HTTPServer(object):
def __init__(self, request_callback, no_keep_alive=False, io_loop=None,xheaders=False, ssl_options=None):
#Application对象
self.request_callback = request_callback
#是否长连接
self.no_keep_alive = no_keep_alive
#IO循环
self.io_loop = io_loop
self.xheaders = xheaders
#Http和Https
self.ssl_options = ssl_options
self._socket = None
self._started = False
def listen(self, port, address=""):
self.bind(port, address)
self.start()
def bind(self, port, address=None, family=socket.AF_UNSPEC):
assert not self._socket
#创建服务端socket对象,IPV4和TCP连接
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, )
flags = fcntl.fcntl(self._socket.fileno(), fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self._socket.fileno(), fcntl.F_SETFD, flags)
#配置socket对象
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, )
self._socket.setblocking()
#绑定IP和端口
self._socket.bind((address, port))
#最大阻塞数量
self._socket.listen()
def start(self, num_processes=):
assert not self._started
self._started = True
if num_processes is None or num_processes <= :
num_processes = _cpu_count()
if num_processes > and ioloop.IOLoop.initialized():
logging.error("Cannot run in multiple processes: IOLoop instance "
"has already been initialized. You cannot call "
"IOLoop.instance() before calling start()")
num_processes =
#如果进程数大于1
if num_processes > :
logging.info("Pre-forking %d server processes", num_processes)
for i in range(num_processes):
if os.fork() == :
import random
from binascii import hexlify
try:
# If available, use the same method as
# random.py
seed = long(hexlify(os.urandom()), )
except NotImplementedError:
# Include the pid to avoid initializing two
# processes to the same value
seed(int(time.time() * ) ^ os.getpid())
random.seed(seed)
self.io_loop = ioloop.IOLoop.instance()
self.io_loop.add_handler(
self._socket.fileno(), self._handle_events,
ioloop.IOLoop.READ)
return
os.waitpid(-, )
#进程数等于1,默认
else:
if not self.io_loop:
#设置成员变量self.io_loop为IOLoop的实例,注:IOLoop使用methodclass完成了一个单例模式
self.io_loop = ioloop.IOLoop.instance()
#执行IOLoop的add_handler方法,将socket句柄、self._handle_events方法和IOLoop.READ当参数传入
self.io_loop.add_handler(self._socket.fileno(),
self._handle_events,
ioloop.IOLoop.READ)
def _handle_events(self, fd, events):
while True:
try:
#====important=====#
connection, address = self._socket.accept()
except socket.error, e:
if e.args[] 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:
#====important=====#
connection = ssl.wrap_socket(connection,server_side=True,do_handshake_on_connect=False,**self.ssl_options)
except ssl.SSLError, err:
if err.args[] == ssl.SSL_ERROR_EOF:
return connection.close()
else:
raise
except socket.error, err:
if err.args[] == 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)
#====important=====#
HTTPConnection(stream, address, self.request_callback,self.no_keep_alive, self.xheaders)
except:
logging.error("Error in connection callback", exc_info=True)

6、IOloop类代码:

class IOLoop(object):
# Constants from the epoll module
_EPOLLIN = 0x001
_EPOLLPRI = 0x002
_EPOLLOUT = 0x004
_EPOLLERR = 0x008
_EPOLLHUP = 0x010
_EPOLLRDHUP = 0x2000
_EPOLLONESHOT = ( << )
_EPOLLET = ( << )
# Our events map exactly to the epoll events
NONE =
READ = _EPOLLIN
WRITE = _EPOLLOUT
ERROR = _EPOLLERR | _EPOLLHUP | _EPOLLRDHUP
def __init__(self, impl=None):
self._impl = impl or _poll()
if hasattr(self._impl, 'fileno'):
self._set_close_exec(self._impl.fileno())
self._handlers = {}
self._events = {}
self._callbacks = []
self._timeouts = []
self._running = False
self._stopped = False
self._blocking_signal_threshold = None
# Create a pipe that we send bogus data to when we want to wake
# the I/O loop when it is idle
if os.name != 'nt':
r, w = os.pipe()
self._set_nonblocking(r)
self._set_nonblocking(w)
self._set_close_exec(r)
self._set_close_exec(w)
self._waker_reader = os.fdopen(r, "rb", )
self._waker_writer = os.fdopen(w, "wb", )
else:
self._waker_reader = self._waker_writer = win32_support.Pipe()
r = self._waker_writer.reader_fd
self.add_handler(r, self._read_waker, self.READ) @classmethod
def instance(cls):
if not hasattr(cls, "_instance"): #单例模式
cls._instance = cls()
return cls._instance
def add_handler(self, fd, handler, events):
"""Registers the given handler to receive the given events for fd."""
self._handlers[fd] = stack_context.wrap(handler)
self._impl.register(fd, events | self.ERROR)

上述代码本质上就干了以下这么四件事:

  1. 把包含了各种配置信息的application对象封装到了HttpServer对象的request_callback字段中

  2. 创建了服务端socket对象

  3. 单例模式创建IOLoop对象,然后将socket对象句柄作为key,被封装了的函数_handle_events作为value,添加到IOLoop对象的_handlers字段中

  4. 向epoll中注册监听服务端socket对象的读可用事件

目前,我们只是看到上述代码大致干了这四件事,而其目的有什么?他们之间的联系又是什么呢?

答:现在不妨先来做一个猜想,待之后再在源码中确认验证是否正确!猜想:通过epoll监听服务端socket事件,一旦请求到达时,则执行3中被封装了的_handle_events函数,该函数又利用application中封装了的各种配置信息对客户端url来指定判定,然后指定对应的Handler处理该请求。

 

tornado源码分析系列一的更多相关文章

  1. Tornado源码分析系列之一: 化异步为'同步'的Future和gen.coroutine

    转自:http://blog.nathon.wang/2015/06/24/tornado-source-insight-01-gen/ 用Tornado也有一段时间,Tornado的文档还是比较匮乏 ...

  2. jQuery源码分析系列

    声明:本文为原创文章,如需转载,请注明来源并保留原文链接Aaron,谢谢! 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://git ...

  3. jQuery-1.9.1源码分析系列完毕目录整理

    jQuery 1.9.1源码分析已经完毕.目录如下 jQuery-1.9.1源码分析系列(一)整体架构 jQuery-1.9.1源码分析系列(一)整体架构续 jQuery-1.9.1源码分析系列(二) ...

  4. MyCat源码分析系列之——结果合并

    更多MyCat源码分析,请戳MyCat源码分析系列 结果合并 在SQL下发流程和前后端验证流程中介绍过,通过用户验证的后端连接绑定的NIOHandler是MySQLConnectionHandler实 ...

  5. MyCat源码分析系列之——SQL下发

    更多MyCat源码分析,请戳MyCat源码分析系列 SQL下发 SQL下发指的是MyCat将解析并改造完成的SQL语句依次发送至相应的MySQL节点(datanode)的过程,该执行过程由NonBlo ...

  6. MyCat源码分析系列之——BufferPool与缓存机制

    更多MyCat源码分析,请戳MyCat源码分析系列 BufferPool MyCat的缓冲区采用的是java.nio.ByteBuffer,由BufferPool类统一管理,相关的设置在SystemC ...

  7. MyCat源码分析系列之——前后端验证

    更多MyCat源码分析,请戳MyCat源码分析系列 MyCat前端验证 MyCat的前端验证指的是应用连接MyCat时进行的用户验证过程,如使用MySQL客户端时,$ mysql -uroot -pr ...

  8. MyCat源码分析系列之——配置信息和启动流程

    更多MyCat源码分析,请戳MyCat源码分析系列 MyCat配置信息 除了一些默认的配置参数,大多数的MyCat配置信息是通过读取若干.xml/.properties文件获取的,主要包括: 1)se ...

  9. 开源分布式数据库中间件MyCat源码分析系列

    MyCat是当下很火的开源分布式数据库中间件,特意花费了一些精力研究其实现方式与内部机制,在此针对某些较为重要的源码进行粗浅的分析,希望与感兴趣的朋友交流探讨. 本源码分析系列主要针对代码实现,配置. ...

随机推荐

  1. TOSCA自动化测试工具--openURL

    在folder下面create test case 输入自己的url,actionMode 是input, String类型

  2. docker issue-Cannot connect to the Docker daemon. Is 'docker -d' running on this host?

    Here is my docker version when i run docker version : Client: Version: 1.8.1 API version: 1.20 Go ve ...

  3. docker基本用法和命令

    1.安装docker 检查有没有curl which curl 如果没有用以下命令可安装:sudo apt-get install curl 通过官方提供的脚本安装最新docker curl -sSL ...

  4. 照着官网来安装openstack pike之glance安装

    镜像服务image service(glance)的安装还是在控制节点上进行: 1.前提条件,数据库为glance创建库和账户密码来连接数据库 # mysql -u root -p MariaDB [ ...

  5. 重新想,重新看——CSS3变形,过渡与动画①

    学习CSS3,觉得最难记忆的部分除了flex特性之外,就要属变形,过渡和动画部分了.作为初学者,总有种犯懒的心理,想着既然IE8浏览器都不完全支持CSS动画属性,还要考虑浏览器兼容问题,那么就不那么着 ...

  6. Java 可重入锁

    一般意义上的可重入锁就是ReentrantLock http://www.cnblogs.com/hongdada/p/6057370.html 广义上的可重入锁是指: 可重入锁,也叫做递归锁,指的是 ...

  7. 混合开发的大趋势之一React Native之简单的登录界面

    转载请注明出处:王亟亟的大牛之路 这些天都在学习RN这部分吧,然后写了个简单的登陆业务,从"实战"中讲解吧 还是继续安利:https://github.com/ddwhan0123 ...

  8. 爬虫框架Scrapy之详解

    Scrapy 框架 Scrapy是用纯Python实现一个为了爬取网站数据.提取结构性数据而编写的应用框架,用途非常广泛. 框架的力量,用户只需要定制开发几个模块就可以轻松的实现一个爬虫,用来抓取网页 ...

  9. windchill系统安装大概步骤

    1.安装VMware Workstation虚拟机 2.win7的64位操作系统(为什么不用32位?因为32位的内存最大只能设置4G) 3.安装Oracle数据库(映射iso文件[上面栏的虚拟机-&g ...

  10. SOA和SaaS的区别

    SOA,Service Oriented ArchITecture,面向服务的架构 SaaS,Software as a Service https://blog.csdn.net/chenyi888 ...