先来看一个简单的示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#!/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'
,
8008
))
sock.listen(
5
)
while
True
:
connection,address
=
sock.accept()
handle_request(connection)
connection.close()
def
handle_request(client):
but
=
client.recv(
1024
)
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
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()
整个过程其实就是在创建一个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__:
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
45
46
47
48
49
50
51
52
53
|
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:
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
|
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[
-
1
][
0
].pattern
=
=
'.*$'
:
self
.handlers.insert(
-
1
, (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
(
2
,
3
)
pattern
=
spec[
0
]
handler
=
spec[
1
]
if
len
(spec)
=
=
3
:
kwargs
=
spec[
2
]
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:
1
2
3
4
5
6
7
8
9
|
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方法。
1
2
3
4
5
6
|
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类代码
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
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(
1
)
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,
0
)
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,
1
)
self
._socket.setblocking(
0
)
#绑定IP和端口
self
._socket.bind((address, port))
#最大阻塞数量
self
._socket.listen(
128
)
def
start(
self
, num_processes
=
1
):
assert
not
self
._started
self
._started
=
True
if
num_processes
is
None
or
num_processes <
=
0
:
num_processes
=
_cpu_count()
if
num_processes >
1
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
#如果进程数大于1
if
num_processes >
1
:
logging.info(
"Pre-forking %d server processes"
, num_processes)
for
i
in
range
(num_processes):
if
os.fork()
=
=
0
:
import
random
from
binascii
import
hexlify
try
:
# If available, use the same method as
# random.py
seed
=
long
(hexlify(os.urandom(
16
)),
16
)
except
NotImplementedError:
# Include the pid to avoid initializing two
# processes to the same value
seed(
int
(time.time()
*
1000
) ^ 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
,
0
)
#进程数等于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[
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
:
#====important=====#
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)
#====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类代码:
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
45
46
47
48
49
50
|
class
IOLoop(
object
):
# Constants from the epoll module
_EPOLLIN
=
0x001
_EPOLLPRI
=
0x002
_EPOLLOUT
=
0x004
_EPOLLERR
=
0x008
_EPOLLHUP
=
0x010
_EPOLLRDHUP
=
0x2000
_EPOLLONESHOT
=
(
1
<<
30
)
_EPOLLET
=
(
1
<<
31
)
# Our events map exactly to the epoll events
NONE
=
0
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"
,
0
)
self
._waker_writer
=
os.fdopen(w,
"wb"
,
0
)
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)
|
上述代码本质上就干了以下这么四件事:
-
把包含了各种配置信息的application对象封装到了HttpServer对象的request_callback字段中
-
创建了服务端socket对象
-
单例模式创建IOLoop对象,然后将socket对象句柄作为key,被封装了的函数_handle_events作为value,添加到IOLoop对象的_handlers字段中
-
向epoll中注册监听服务端socket对象的读可用事件
目前,我们只是看到上述代码大致干了这四件事,而其目的有什么?他们之间的联系又是什么呢?
答:现在不妨先来做一个猜想,待之后再在源码中确认验证是否正确!猜想:通过epoll监听服务端socket事件,一旦请求到达时,则执行3中被封装了的_handle_events函数,该函数又利用application中封装了的各种配置信息对客户端url来指定判定,然后指定对应的Handler处理该请求。