Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,这意味着对于实时 Web 服务来说,Tornado 是一个理想的 Web 框架。
一、Tornado的两种模式使用
1.同步阻塞模式
由于doing中sleep10秒,此时其他连接将被阻塞,必须等这次请求完成后其他请求才能连接成功。
import tornado.ioloop
import tornado.web class MainHandler(tornado.web.RequestHandler):
def get(self):
self.doing()
self.write("Hello, world") def doing(self):
time.sleep(10) application = tornado.web.Application([
(r"/index", MainHandler),
]) if __name__ == "__main__":
application.listen(8888)
tornado.ioloop.IOLoop.instance().start()
同步阻塞
2.异步非阻塞模式
1、基本使用
装饰器 + Future 从而实现Tornado的异步非阻塞:
当发送GET请求时,由于方法被@gen.coroutine装饰且yield 一个 Future对象,那么Tornado会等待,等待用户向future对象中放置数据或者发送信号,如果获取到数据或信号之后,就开始执行doing方法。异步非阻塞体现在当在Tornaod等待用户向future对象中放置数据时,还可以处理其他请求。
注意:在等待用户向future对象中放置数据或信号时,此连接是不断开的。
class AsyncHandler(tornado.web.RequestHandler):
@gen.coroutine
def get(self):
future = Future()
tornado.ioloop.IOLoop.current().add_timeout(time.time() + 5, self.doing)
yield future def doing(self, *args, **kwargs):
self.write('async')
self.finish()
异步非阻塞
2、httpclient类库
当服务器接到的请求需要向第三方服务器发送请求才能解决时,Tornado提供了httpclient类库用于发送Http请求,其配合Tornado的异步非阻塞使用。
import tornado.web
from tornado import gen
from tornado import httpclient # 方式一:
class AsyncHandler(tornado.web.RequestHandler):
@gen.coroutine
def get(self, *args, **kwargs):
print('进入')
http = httpclient.AsyncHTTPClient()
data = yield http.fetch("http://www.google.com")
print('完事',data)
self.finish('') # 方式二:
# class AsyncHandler(tornado.web.RequestHandler):
# @gen.coroutine
# def get(self):
# print('进入')
# http = httpclient.AsyncHTTPClient()
# yield http.fetch("http://www.google.com", self.done)
#
# def done(self, response):
# print('完事')
# self.finish('666') application = tornado.web.Application([
(r"/async", AsyncHandler),
]) if __name__ == "__main__":
application.listen(8888)
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请求便算结束。
import socket
import select class HttpRequest(object):
"""
用户封装用户请求信息
"""
def __init__(self, content):
""" :param content:用户发送的请求数据:请求头和请求体
"""
self.content = content self.header_bytes = bytes()
self.body_bytes = bytes() self.header_dict = {} self.method = ""
self.url = ""
self.protocol = "" self.initialize()
self.initialize_headers() def initialize(self): temp = self.content.split(b'\r\n\r\n', 1)
if len(temp) == 1:
self.header_bytes += temp
else:
h, b = temp
self.header_bytes += h
self.body_bytes += b @property
def header_str(self):
return str(self.header_bytes, encoding='utf-8') def initialize_headers(self):
headers = self.header_str.split('\r\n')
first_line = headers[0].split(' ')
if len(first_line) == 3:
self.method, self.url, self.protocol = headers[0].split(' ')
for line in headers:
kv = line.split(':')
if len(kv) == 2:
k, v = kv
self.header_dict[k] = v # class Future(object):
# def __init__(self):
# self.result = None def main(request):
return "main" def index(request):
return "indexasdfasdfasdf" routers = [
('/main/',main),
('/index/',index),
] def run():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 9999,))
sock.setblocking(False)
sock.listen(128) inputs = []
inputs.append(sock)
while True:
rlist,wlist,elist = select.select(inputs,[],[],0.05)
for r in rlist:
if r == sock:
"""新请求到来"""
conn,addr = sock.accept()
conn.setblocking(False)
inputs.append(conn)
else:
"""客户端发来数据"""
data = b""
while True:
try:
chunk = r.recv(1024)
data = data + chunk
except Exception as e:
chunk = None
if not chunk:
break
# data进行处理:请求头和请求体
request = HttpRequest(data)
# 1. 请求头中获取url
# 2. 去路由中匹配,获取指定的函数
# 3. 执行函数,获取返回值
# 4. 将返回值 r.sendall(b'alskdjalksdjf;asfd')
import re
flag = False
func = None
for route in routers:
if re.match(route[0],request.url):
flag = True
func = route[1]
break
if flag:
result = func(request)
r.sendall(bytes(result,encoding='utf-8'))
else:
r.sendall(b"") inputs.remove(r)
r.close() if __name__ == '__main__':
run()
自定义同步阻塞框架
2、Tornado异步非阻塞实现原理
tornado通过装饰器 + Future 从而实现异步非阻塞。在view中yield一个future对象,然后再在发送相应数据前判断view函数返回来的数据类型,如果是字符串类型直接返回,如果是future对象,则将返回来的future对象添加到async_request_dict中,先不给客户端返回响应数据(此时可以处理其他客户端的连接请求),等future对象的result有值时再返回,还可以设置超时时间,在规定的时间过后返回响应数据。 !! 关键是future对象,future对象里有result属性,默认为None,当result有值时再返回数据。
import socket
import select
import time class HttpRequest(object):
"""
用户封装用户请求信息
"""
def __init__(self, content):
""" :param content:用户发送的请求数据:请求头和请求体
"""
self.content = content self.header_bytes = bytes()
self.body_bytes = bytes() self.header_dict = {} self.method = ""
self.url = ""
self.protocol = "" self.initialize()
self.initialize_headers() def initialize(self): temp = self.content.split(b'\r\n\r\n', 1)
if len(temp) == 1:
self.header_bytes += temp
else:
h, b = temp
self.header_bytes += h
self.body_bytes += b @property
def header_str(self):
return str(self.header_bytes, encoding='utf-8') def initialize_headers(self):
headers = self.header_str.split('\r\n')
first_line = headers[0].split(' ')
if len(first_line) == 3:
self.method, self.url, self.protocol = headers[0].split(' ')
for line in headers:
kv = line.split(':')
if len(kv) == 2:
k, v = kv
self.header_dict[k] = v class Future(object):
def __init__(self,timeout=0):
self.result = None
self.timeout = timeout
self.start = time.time()
def main(request):
f = Future(5)
return f def index(request):
return "indexasdfasdfasdf" routers = [
('/main/',main),
('/index/',index),
] def run():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("127.0.0.1", 9999,))
sock.setblocking(False)
sock.listen(128) inputs = []
inputs.append(sock) async_request_dict = {
# 'socket': futrue
} while True:
rlist,wlist,elist = select.select(inputs,[],[],0.05)
for r in rlist:
if r == sock:
"""新请求到来"""
conn,addr = sock.accept()
conn.setblocking(False)
inputs.append(conn)
else:
"""客户端发来数据"""
data = b""
while True:
try:
chunk = r.recv(1024)
data = data + chunk
except Exception as e:
chunk = None
if not chunk:
break
# data进行处理:请求头和请求体
request = HttpRequest(data)
# 1. 请求头中获取url
# 2. 去路由中匹配,获取指定的函数
# 3. 执行函数,获取返回值
# 4. 将返回值 r.sendall(b'alskdjalksdjf;asfd')
import re
flag = False
func = None
for route in routers:
if re.match(route[0],request.url):
flag = True
func = route[1]
break
if flag:
result = func(request)
if isinstance(result,Future):
async_request_dict[r] = result
else:
r.sendall(bytes(result,encoding='utf-8'))
inputs.remove(r)
r.close()
else:
r.sendall(b"")
inputs.remove(r)
r.close() for conn in async_request_dict.keys():
future = async_request_dict[conn]
start = future.start
timeout = future.timeout
ctime = time.time()
if (start + timeout) <= ctime :
future.result = b"timeout"
if future.result:
conn.sendall(future.result)
conn.close()
del async_request_dict[conn]
inputs.remove(conn) if __name__ == '__main__':
run()
自定义异步非阻塞框架