Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。
一、Tornado的两种模式使用
1.同步阻塞模式
由于doing中sleep10秒,此时其他连接将被阻塞,必须等这次请求完成后其他请求才能连接成功。
1 import tornado.ioloop
2 import tornado.web
3
4
5 class MainHandler(tornado.web.RequestHandler):
6 def get(self):
7 self.doing()
8 self.write("Hello, world")
9
10 def doing(self):
11 time.sleep(10)
12
13
14 application = tornado.web.Application([
15 (r"/index", MainHandler),
16 ])
17
18
19 if __name__ == "__main__":
20 application.listen(8888)
21 tornado.ioloop.IOLoop.instance().start()
同步阻塞
2.异步非阻塞模式
1、基本使用
装饰器 + Future 从而实现Tornado的异步非阻塞:
当发送GET请求时,由于方法被@gen.coroutine装饰且yield 一个 Future对象,那么Tornado会等待,等待用户向future对象中放置数据或者发送信号,如果获取到数据或信号之后,就开始执行doing方法。异步非阻塞体现在当在Tornaod等待用户向future对象中放置数据时,还可以处理其他请求。
注意:在等待用户向future对象中放置数据或信号时,此连接是不断开的。
1 class AsyncHandler(tornado.web.RequestHandler):
2 @gen.coroutine
3 def get(self):
4 future = Future()
5 tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5, self.doing)
6 yield future
7
8
9 def doing(self, *args, **kwargs):
10 self.write('async')
11 self.finish()
异步非阻塞
2、httpclient类库
当服务器接到的请求需要向第三方服务器发送请求才能解决时,Tornado提供了httpclient类库用于发送Http请求,其配合Tornado的异步非阻塞使用。
1 import tornado.web
2 from tornado import gen
3 from tornado import httpclient
4
5 # 方式一:
6 class AsyncHandler(tornado.web.RequestHandler):
7 @gen.coroutine
8 def get(self, *args, **kwargs):
9 print('进入')
10 http = httpclient.AsyncHTTPClient()
11 data = yield http.fetch("http://www.google.com")
12 print('完事',data)
13 self.finish('6666')
14
15 # 方式二:
16 # class AsyncHandler(tornado.web.RequestHandler):
17 # @gen.coroutine
18 # def get(self):
19 # print('进入')
20 # http = httpclient.AsyncHTTPClient()
21 # yield http.fetch("http://www.google.com", self.done)
22 #
23 # def done(self, response):
24 # print('完事')
25 # self.finish('666')
26
27
28
29 application = tornado.web.Application([
30 (r"/async", AsyncHandler),
31 ])
32
33 if __name__ == "__main__":
34 application.listen(8888)
35 tornado.ioloop.IOLoop.instance().start()
异步发送request请求
二、Tornado异步非阻塞的原理
1、普通同步阻塞服务器框架原理
通过select与socket我们可以开发一个微型的框架,使用select实现IO多路复用监听本地服务端socket。当有客户端发送请求时,select监听的本地socket发生变化,通过socket.accept()得到客户端发送来的conn(也是一个socket),并将conn也添加到select监听列表里。当客户端通过conn发送数据时,服务端select监听列表的conn发生变化,我们将conn发送的数据(请求数据)接收保存并处理得到request_header与request_body,然后可以根据request_header中的url来匹配本地路由中的url,然后得到对应的view函数,然后将view的返回值(一般为字符串)通过conn发送回请求客户端,然后将conn关闭,并且移除select监听列表中的conn,这样一次网络IO请求便算结束。
1 import socket
2 import select
3
4 class HttpRequest(object):
5 """
6 用户封装用户请求信息
7 """
8 def __init__(self, content):
9 """
10
11 :param content:用户发送的请求数据:请求头和请求体
12 """
13 self.content = content
14
15 self.header_bytes = bytes()
16 self.body_bytes = bytes()
17
18 self.header_dict = {}
19
20 self.method = ""
21 self.url = ""
22 self.protocol = ""
23
24 self.initialize()
25 self.initialize_headers()
26
27 def initialize(self):
28
29 temp = self.content.split(b'\r\n\r\n', 1)
30 if len(temp) == 1:
31 self.header_bytes += temp
32 else:
33 h, b = temp
34 self.header_bytes += h
35 self.body_bytes += b
36
37 @property
38 def header_str(self):
39 return str(self.header_bytes, encoding='utf-8')
40
41 def initialize_headers(self):
42 headers = self.header_str.split('\r\n')
43 first_line = headers[0].split(' ')
44 if len(first_line) == 3:
45 self.method, self.url, self.protocol = headers[0].split(' ')
46 for line in headers:
47 kv = line.split(':')
48 if len(kv) == 2:
49 k, v = kv
50 self.header_dict[k] = v
51
52 # class Future(object):
53 # def __init__(self):
54 # self.result = None
55
56 def main(request):
57 return "main"
58
59 def index(request):
60 return "indexasdfasdfasdf"
61
62
63 routers = [
64 ('/main/',main),
65 ('/index/',index),
66 ]
67
68 def run():
69 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
70 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
71 sock.bind(("127.0.0.1", 9999,))
72 sock.setblocking(False)
73 sock.listen(128)
74
75 inputs = []
76 inputs.append(sock)
77 while True:
78 rlist,wlist,elist = select.select(inputs,[],[],0.05)
79 for r in rlist:
80 if r == sock:
81 """新请求到来"""
82 conn,addr = sock.accept()
83 conn.setblocking(False)
84 inputs.append(conn)
85 else:
86 """客户端发来数据"""
87 data = b""
88 while True:
89 try:
90 chunk = r.recv(1024)
91 data = data + chunk
92 except Exception as e:
93 chunk = None
94 if not chunk:
95 break
96 # data进行处理:请求头和请求体
97 request = HttpRequest(data)
98 # 1. 请求头中获取url
99 # 2. 去路由中匹配,获取指定的函数
100 # 3. 执行函数,获取返回值
101 # 4. 将返回值 r.sendall(b'alskdjalksdjf;asfd')
102 import re
103 flag = False
104 func = None
105 for route in routers:
106 if re.match(route[0],request.url):
107 flag = True
108 func = route[1]
109 break
110 if flag:
111 result = func(request)
112 r.sendall(bytes(result,encoding='utf-8'))
113 else:
114 r.sendall(b"404")
115
116 inputs.remove(r)
117 r.close()
118
119 if __name__ == '__main__':
120 run()
自定义同步阻塞框架
2、Tornado异步非阻塞实现原理
tornado通过装饰器 + Future 从而实现异步非阻塞。在view中yield一个future对象,然后再在发送相应数据前判断view函数返回来的数据类型,如果是字符串类型直接返回,如果是future对象,则将返回来的future对象添加到async_request_dict中,先不给客户端返回响应数据(此时可以处理其他客户端的连接请求),等future对象的result有值时再返回,还可以设置超时时间,在规定的时间过后返回响应数据。 !! 关键是future对象,future对象里有result属性,默认为None,当result有值时再返回数据。
1 import socket
2 import select
3 import time
4
5 class HttpRequest(object):
6 """
7 用户封装用户请求信息
8 """
9 def __init__(self, content):
10 """
11
12 :param content:用户发送的请求数据:请求头和请求体
13 """
14 self.content = content
15
16 self.header_bytes = bytes()
17 self.body_bytes = bytes()
18
19 self.header_dict = {}
20
21 self.method = ""
22 self.url = ""
23 self.protocol = ""
24
25 self.initialize()
26 self.initialize_headers()
27
28 def initialize(self):
29
30 temp = self.content.split(b'\r\n\r\n', 1)
31 if len(temp) == 1:
32 self.header_bytes += temp
33 else:
34 h, b = temp
35 self.header_bytes += h
36 self.body_bytes += b
37
38 @property
39 def header_str(self):
40 return str(self.header_bytes, encoding='utf-8')
41
42 def initialize_headers(self):
43 headers = self.header_str.split('\r\n')
44 first_line = headers[0].split(' ')
45 if len(first_line) == 3:
46 self.method, self.url, self.protocol = headers[0].split(' ')
47 for line in headers:
48 kv = line.split(':')
49 if len(kv) == 2:
50 k, v = kv
51 self.header_dict[k] = v
52
53 class Future(object):
54 def __init__(self,timeout=0):
55 self.result = None
56 self.timeout = timeout
57 self.start = time.time()
58 def main(request):
59 f = Future(5)
60 return f
61
62 def index(request):
63 return "indexasdfasdfasdf"
64
65
66 routers = [
67 ('/main/',main),
68 ('/index/',index),
69 ]
70
71 def run():
72 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
73 sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
74 sock.bind(("127.0.0.1", 9999,))
75 sock.setblocking(False)
76 sock.listen(128)
77
78 inputs = []
79 inputs.append(sock)
80
81 async_request_dict = {
82 # 'socket': futrue
83 }
84
85 while True:
86 rlist,wlist,elist = select.select(inputs,[],[],0.05)
87 for r in rlist:
88 if r == sock:
89 """新请求到来"""
90 conn,addr = sock.accept()
91 conn.setblocking(False)
92 inputs.append(conn)
93 else:
94 """客户端发来数据"""
95 data = b""
96 while True:
97 try:
98 chunk = r.recv(1024)
99 data = data + chunk
100 except Exception as e:
101 chunk = None
102 if not chunk:
103 break
104 # data进行处理:请求头和请求体
105 request = HttpRequest(data)
106 # 1. 请求头中获取url
107 # 2. 去路由中匹配,获取指定的函数
108 # 3. 执行函数,获取返回值
109 # 4. 将返回值 r.sendall(b'alskdjalksdjf;asfd')
110 import re
111 flag = False
112 func = None
113 for route in routers:
114 if re.match(route[0],request.url):
115 flag = True
116 func = route[1]
117 break
118 if flag:
119 result = func(request)
120 if isinstance(result,Future):
121 async_request_dict[r] = result
122 else:
123 r.sendall(bytes(result,encoding='utf-8'))
124 inputs.remove(r)
125 r.close()
126 else:
127 r.sendall(b"404")
128 inputs.remove(r)
129 r.close()
130
131 for conn in async_request_dict.keys():
132 future = async_request_dict[conn]
133 start = future.start
134 timeout = future.timeout
135 ctime = time.time()
136 if (start + timeout) <= ctime :
137 future.result = b"timeout"
138 if future.result:
139 conn.sendall(future.result)
140 conn.close()
141 del async_request_dict[conn]
142 inputs.remove(conn)
143
144 if __name__ == '__main__':
145 run()
自定义异步非阻塞框架