非阻塞式和基于Linux的Epoll(UNIX为kqueue)的异步网络IO
异步非阻塞IO处理方式,单进程单线程异步IO的网络模型,可以编写异步非阻塞的程序
非常适合开发长轮询、WebSocket和需要与每个用户建立持久连接的应用
既是WebServer也是WebFramework
Web框架(包括用来创建Web应用程序的RequestHandler类,还有很多其它支持的类).
HTTP客户端和服务器的实现(HTTPServer和AsyncHTTPClient).
异步网络库(IOLoop和IOStream),对HTTP的实现提供构建模块,还可以用来实现其他协议.
协程库(tornado.gen)让用户通过更直接的方法来实现异步编程,而不是通过回调的方式.
Tornado的HTTPConnection类用来处理HTTP请求,包括读取HTTP请求头、读取POST传递的数据,调用用户自定义的处理方法,以及把响应数据写给客户端的socket。
`iostream`对非阻塞式的`socket`的封装以便于常见读写操作为了在处理请求时实现对`socket`的**异步读写**,Tornado实现了`IOStream`类用来处理`socket`的异步读写。`ioloop`核心的I/O循环Tornado为了实现**高并发和高性能**,使用了一个`IOLoop`事件循环来处理`socket`的读写事件,`IOLoop`事件循环是**基于Linux的`epoll`模型**,可以高效地响应网络事件,这是Tornado高效的基础保证。
略过
pipinstalltornado
1.Tornado中Application应用类是Handler处理器的集合
Application类的__init__初始化函数原型
#原型def__init__(self,handlers=None,default_host="",transforms=None,wsgi=False,**settings):
2.Tornado的HTTPServer会负责解析用户的HTTPRequest,构造一个request对象。并交给RequestHandler处理,Request的解析是一个规划化的流程,针对Request的处理函数RequestHandler是被自定义的重点部分。
3.由于HTTP是工作在TCP协议之上的,HTTPServer其实是TCPServer的派生类,常规socket编程中启动一个TCPServer有三个必备步骤:
a)创建socket
b)绑定指定地址的端口
c)执行监听
TCPServer类的实现借鉴UNIX/Linux中的Socket机制,也必然存在上述步骤,这几个步骤都是在HTTPServer.listen()函数调用时完成的。
server.listen(options.port)listen函数的参数是端口号,端口定义可通过define来定义。
fromtornado.optionsimportdefine,optionsdefine("port",default=8888,help="runonthegivenport",type=int)define函数是OptionParser类的成员,定义在tornado/options.py文件中,机制于parse_command_line()类似。define定义端口port或,port变量会被存放在options对象的directory成员中,因此可直接使用options.port访问。
4.当使用server.listen(options.port)后,服务器就会在端口上启动一个服务,并开始监听客户端的连接。对于常规的Socket操作,listen之后的操作应该是accept。
在Tornado中accept操作是这样的:
tornado.ioloop.IOLoop.current().start()IOLoop是什么呢?IOLoop于TCPServer之间的关系其实很简单。例如使用C语言编写TCP服务器时,编写完create-bind-listen三段式之后,都需要编写accept/recv/send处理客户端请求。通常会写一个无限循环,不断调用accept来响应客户端连接,其实这个无线循环就是Tornado中的IOLoop。
IOLoop会负责accept这一步,对于recv/send操作通常也是在一个循环中进行的,也可以抽象成IOLoop。
Tornado的Web服务器通常包含四大组件
tornado.ioloop是全局Tornado的IO事件循环,是服务器的引擎核心。
tornado.ioloop是核心IO循环模块,封装了Linux的epoll和BSD的kqueue,是Tornado高性能处理的核心。
tornado.ioloop.IOLoop.current()返回当前线程的IOLoop实例对象
tornado.ioloop.IOLoop.current().start()用于启动IOLoop实例对象的IO循环并开启监听
#加载Tornado核心IO事件循环模块importtornado.ioloop#默认Tornado的ioloop实例tornado.ioloop.IOLoop.current()
app实例代表了一个完成的后端应用,它会挂接一个服务端套接字端口并对外提供服务,一个ioloop事件循环实例中可以包含多个app实例。
#创建应用实例app=tornado.web.Application(urls)#监听端口app.listen(options.port)
路由表用于将指定URL规则和处理器Handler挂接起来形成路由映射表,当请求到来时会根据请求的访问URL查询路由映射表来查询对应业务的处理器Handler。
urls=[(r"/",MainHandler),]
handler类代表着业务逻辑,在进行服务端开发时也就是在编写处理器,用以服务客户端请求。
#当处理请求时会进行实例化并调用HTTP请求对应的方法classMainHandler(tornado.web.RequestHandler):#定义get方法对HTTP的GET请求做出响应defget(self):#从querystring查询字符串中获取id参数的值,若无则默认为0.id=self.get_argument("id",0)#write方法将字符串写入HTTP响应self.write("helloworldid="+id)四大组件的关系
ioloop是服务的引擎核心是发动机,负责接收和响应客户端请求,负责驱动业务处理器handler的运行,负责服务器内部定时任务的执行。同一个ioloop实例会运行在一个单线程环境下。
ioloopioloop是服务的引擎核心是发动机,负责接收和响应客户端请求,负责驱动业务处理器handler的运行,负责服务器内部定时任务的执行。同一个ioloop实例会运行在一个单线程环境下。
当一个请求到来时,IO事件循环ioloop会读取请求并解包形成一个HTTP请求对象,并找到该套接字上对应应用app的路由表urls,通过请求对象的URL查询路由表中挂接的处理器Handler,然后执行处理器Handler。handler处理器执行后会返回一个对象,ioloop负责将对象包装成HTTP响应对象并序列化发送给客户端。
一个简单的同步函数:
Tornado中推荐用协程来编写异步代码.协程使用Python中的关键字yield来替代链式回调来实现挂起和继续程序的执行(像在gevent中使用的轻量级线程合作的方法有时也称作协程,但是在Tornado中所有协程使用异步函数来实现的明确的上下文切换).
协程和异步编程的代码一样简单,而且不用浪费额外的线程,.它们还可以减少上下文切换让并发更简单.
从Tornado4.3开始,在协程基础上你可以使用这些来代替yield.简单的通过使用asyncdeffoo()来代替@gen.coroutine装饰器,用await来代替yield.文档的剩余部分还是使用yield来兼容旧版本的Python,但是async和await在可用时将会运行的更快:
虽然原生的协程不依赖于某种特定的框架(例如.它并没有使用像tornado.gen.coroutine或者asyncio.coroutine装饰器),不是所有的协程都和其它程序兼容.这里有一个协程运行器在第一个协程被调用时进行选择,然后被所有直接调用await的协程库共享.Tornado协程运行器设计时就时多用途且可以接受任何框架的awaitable对象.其它协程运行器可能会有更多的限制(例如,asyncio协程运行器不能接收其它框架的协程).由于这个原因,我们推荐你使用Tornado的协程运行器来兼容任何框架的协程.在Tornado协程运行器中调用一个已经用了asyncio协程运行器的协程,只需要用tornado.platform.asyncio.to_asyncio_future适配器.
一个含有yield的函数时一个生成器.所有生成器都是异步的;调用它时将会返回一个对象而不是将函数运行完成.@gen.coroutine修饰器通过yield表达式通过产生一个Future对象和生成器进行通信.
装饰器从生成器接收一个Future对象,等待(非阻塞的)Future完成,然后“解开”Future将结果像yield语句一样返回给生成器.大多数异步代码从不直接接触到Future类,除非Future立即通过异步函数返回给yield表达式.
协程在一般情况下不抛出异常:在Future被生成时将会把异常报装进来.这意味着正确的调用协程十分的重要,否则你可能忽略很多错误:
#TheIOLoopwillcatchtheexceptionandprintastacktracein#thelogs.Notethatthisdoesn'tlooklikeanormalcall,since#wepassthefunctionobjecttobecalledbytheIOLoop.IOLoop.current().spawn_callback(divide,1,0)
@gen.coroutinedefcall_task():#Notethattherearenoparensonsome_function.#ThiswillbetranslatedbyTaskinto#some_function(other_args,callback=callback)yieldgen.Task(some_function,other_args)
thread_pool=ThreadPoolExecutor(4)@gen.coroutinedefcall_blocking():yieldthread_pool.submit(blocking_func,args)
@gen.coroutinedefget(self):fetch_future=self.fetch_next_chunk()whileTrue:chunk=yieldfetch_futureifchunkisNone:breakself.write(chunk)fetch_future=self.fetch_next_chunk()yieldself.flush()
importmotordb=motor.MotorClient().test@gen.coroutinedefloop_example(collection):cursor=db.collection.find()while(yieldcursor.fetch_next):doc=cursor.next_object()
@gen.coroutinedefminute_loop():whileTrue:yielddo_something()yieldgen.sleep(60)#Coroutinesthatloopforeveraregenerallystartedwith#spawn_callback().IOLoop.current().spawn_callback(minute_loop)有时可能会遇到一些复杂的循环.例如,上一个循环每60+N秒运行一次,其中N时do_something()的耗时.为了精确运行60秒,使用上面的交叉模式:
@gen.coroutinedefminute_loop2():whileTrue:nxt=gen.sleep(60)#Starttheclock.yielddo_something()#Runwhiletheclockisticking.yieldnxt#Waitforthetimertorunout.
有两种方式可以操作数据库。
第一种:第一种其实感觉用起来并没有那么舒服,是一个模块,对数据库支持没有非常完善。
第二种:数据库模块,我们自己写的数据库模块来代替Tornado提供的原生方式。