打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
tornado 入门教程(一) | 如风博客

写在前面的一些话

什么是Tornado?

什么是Tornado?老实说,到目前为止,我对这东西也不算很了解。但如果你跟我一样,都是从来都没有完整地学习过网络编程,而从现在开始就要学习Tornado的话,或许看了下面我要摘抄下来的两大段话,也没什么收获的,不信你就试着阅读一下吧:

FriendFeed使用了一款使用 Python 编写的,相对简单的 非阻塞式 Web 服务器。其应用程序使用的 Web 框架看起来有些像 web.py 或者 Google 的 webapp, 不过为了能有效利用非阻塞式服务器环境,这个 Web 框架还包含了一些相关的有用工具 和优化。

Tornado 就是我们在 FriendFeed 的 Web 服务器及其常用工具的开源版本。Tornado 和现在的主流 Web 服务器框架(包括大多数 Python 的框架)有着明显的区别:它是非阻塞式服务器,而且速度相当快。得利于其 非阻塞的方式和对 epoll 的运用,Tornado 每秒可以处理数以千计的连接,因此 Tornado 是实时 Web 服务的一个 理想框架。我们开发这个 Web 服务器的主要目的就是为了处理 FriendFeed 的实时功能 ——在 FriendFeed 的应用里每一个活动用户都会保持着一个服务器连接。(关于如何扩容 服务器,以处理数以千计的客户端的连接的问题,请参阅 The C10K problem )

如何?也许你看得不是很明白吧?别问我为什么还要把它们抄在上面——总之,什么“非阻塞式” 、“epoll” ,我敢保证,如果你是从来都没接触过任何网络编程的知识,看了上面那两大段话,肯定会……呃,此处略省N个字。

别着急,现在并不代表永远,我想如果你给我一点点时间,让我先简单地给你介绍一些关于python中的socket模块的知识,也许你会很快地明白Tornado是怎么回事——至少不会像现在这么……呃,没事,情况会好的!

先来看看socket模块
socket的英文原义是“孔”或“插座”。当然,在网络编程的世界里,它的中文名叫作“套接字”。套接字主要是两个程序之间的“信息通道”。程序可能(通过网络连接)分布在不同的计算机上,通过套接字相互发送信息。在python中的大多数的网络编程都隐藏了socket模块的基本细节,并且不直接和套接字交互。

套接字包括两个:服务器套接字和客户机套接字。创建一个服务器套接字后,让它等待连接。这样它就在某个网络地址处(IP地址和一个端口号的组合)监听。

处理客户端套接字通过常比处理服务器端套接容易,因为服务器必须准备随时处理客户端的连接,同时还要处理多个连接,而客户机只是简单地连接,完成事务,断开连接。

好吧,说了这么多,我是不会像专家那样喜欢一味说教的。邓爷爷告诉我,实践是检验真理的唯一标准。既此,我想我还是先写个运用到socket模块的小服务器(说服务器,可能读者会疑问说“这……是吗?”,没办法大家都这么叫它的)出来吧:

———— sever ————
[cc lang="python"]

Import socket
S = socket.socket() #创建一个socket实例
Host = ‘localhost’ #服务器端的主机名,也可以通过socket.gethostname()获得当前主机名
Port = 8080 #端口号
s.bind(Host, Port) #将socket 绑定到指定的地址上

s.listen(5) #允许5个客户连接到服务器
while True: #循环监听
c, addr = s.accept() #等待客户请求一个连接,并连接
print ‘Got connection from’, addr #打印获取到的客户机地址
c.send(‘Thank you for connect.’) #向客户发送一个信息
c.close() #关闭连接

[/cc]
如何?应该不难看懂我这一小块代码吧?当然,如果你还没看明白的话,或许是卡在上面的三行“红码”中罢?嘻嘻,我早料到会这样了。好,现在就着那三行“红码”,我作个简短解释试试吧——如果看了我的解释还是不懂,或者觉得我的解释不好的话,下文还附录了一些相关网页地址,里面说的,或许可以帮到你哦(我只是个引路人而已):

  1. 如果你在阅读上文的时候够仔细的话,也许你会注意到上面这一句话(我用紫色标出来了):创建一个服务器套接字后,让它等待连接。这样它就在某个网络地址处(IP地址和一个端口号的组合)监听。如何?我也只能解释到这儿了,还记得socket的英文原义吗?插口,不是吗?s.bind(Host, Port) 这句代码形象地讲,就是创建一个插口后,把它插入Host这台电脑的Port插口中,循环监听来自客户端的消息。
  2. c, addr = s.accept() 从代码可知,s.accept()函数返回一个包含两个数据的元组,其中返回给变量c的是一个新的socket,这是一个专门给地址为addr的客户端的socket,通过这个socket,服务器端就可以和地址为addr的客户端交互。
  3. c.send(‘Thank you for connect.’) 说到这段代码就不得不说到send()函数。send,顾名思义,就是用来发送信息的。具体地讲,现在是服务端,当然是给客户端发送信息了。既然已经说到send()函数,当然recv()函数就不得不说了。如果你的英文够好的话,也许你已经猜到这个函数的作用了,不错,就是用来接收信息的。这个recv()函数在下面我贴出的客户机代码中会用到哈。

谢天谢地,我想对于socket模块,你已经有所理解了吧,至少我讲的,你不会再觉得我在胡言乱语吧。再接再励,可别烦哦,再来看看socket客户端的代码吧:

[cc lang="python"]
Import socket
S = socket.socket()
Host = localhost #服务器端地址,现用的是当前主机
Port = 8080 #端口号一定要和服务器端的一样
s.connect((host, port)) #连接服务器
print s.recv(1024) #接收信息,最大可接收1024字节的数据
[/cc]

嗯……客户端……就不用解释了吧?相较于服务器端,上面的代码中只有connect()函数是“生脸”,而且目前我们完全可以顾名思义地去理解它就行了:用来“连接(connect)服务器”的。而recv()最多只能算是“medium rare”(半生熟),讲过的我不想再重复。

Socket、服务器端、客户端,上面说了一大堆,如果你都明白的话,那恭喜你,你已经见识了“阻塞或者称为同步网络编程”。什么意思?阻塞(或者称为同步):即是一次只能连接一个客户机并处理它的请求。读到此,或许你会问:那么……同时能处理多个连接……不就是“非阻塞”?OK,你太聪明了。下面就让我见识下所谓的“非阻塞式网络编程”——当然还不是要讲Tornado,现在咱们还来玩玩socket的朋友先,它就是SocketServer!

再来看看SocketServer

同样地,如果你回溯到上文的话,你会看到我曾说过这么一句话(也用紫色标出了):处理客户端套接字通过常比处理服务器端套接容易……而客户机只是简单地连接,完成事务,断开连接。
有这话在,我想我下面就不用再提客户机了吧?好,咱们就来见识“非阻塞式”的服务器是怎么“炼成”的。
首先,先了解下SocketServer模块中可供使用的类:

  1. BaseServer:包含服务器的核心功能与混合(mix-in)类挂钩;这个类只用于派生,所以不会生成这个类的实例;可以考虑使用TCPServer和UDPServer。
  2. TCPServer/UDPServer:基本的网络同步TCP/UDP服务器。
  3. UnixStreamServer/ UnixDatagramServer:基本的基于文件同步TCP/UDP服务器。
  4. ForkingMixIn/ ThreadingMixIn:实现了核心的进程化或线程化的功能;作为混合类,与服务器类一并使用以提供一些异步特性;这个类不会直接实例化。
  5. ForkingTCPServer/ ForkingUDPServer:ForkingMixIn和TCPServer/UDPServer的组合。
  6. BaseRequestHandler:包含处理服务请求的核心功能。这个类只用于派生,所以不会生成这个类的实例可以考虑使用StreamRequestHandler或DatagramRequestHandler。
  7. StreamRequestHandler/ DatagramRequestHandler:用于TCP/UDP服务器的服务处理工具。

上面例举了这么一些东东,不知道你看了有何感想。不管你怕不怕,我是有点怕的——所以我也不打算全讲了。就图省事,我就直接贴代码吧:
[cc lang="python"]
#coding:gb2312

from SocketServer import TCPServer, ThreadingMixIn, StreamRequestHandler

#打造一个多线程TCP服务器
class Server(ThreadingMixIn, TCPServer):
pass

class Handler(StreamRequestHandler): #定义一个流处理器类

def handle(self): #定义一个特殊的方法handle()
addr = self.request.getpeername()
print ‘Got connection from’, addr
self.wfile.write(‘Thank you for connecting’)

server = Server ((”, 8080), Handler) #’’表示当前主机名,8080是端口号
server.serve_forever() #循环监听

[/cc]

再次提醒下,SocketServer模块所包含的东西可多了,我在这里所要做的,只是带大家一起来理解下其中最最基本的“流程”而已。

代码解释:
server = Server ((”, 8080), Handler)
我想我从这一行代码开始说起,大家会更能明白我在说什么。
1、 嗯嗯,Server()实例化一个多线程TCP服务器,内容直接pass掉,功能完全继承自ThreadingMixIn, TCPServer两个类。
2、 Server()要接收两个参数,第一个参数是一个元组,和socket模块的服务器一样,这是可以说是在指定 服务器程序 要在哪台电脑的哪个端口号上负责监听;第二个参数是一个Handler类。同样地,这个类是继承自StreamRequestHandler类,从而定义了一个流处理器。“流处理器”?别慌,咱们可以这样理解它:流,指数据流,可以想象,客户端的数据像水流一样,通过网路迅速流进服务器中来。处理器,总不能叫服务器“有求所应”吧,那样得需要多大的内存呀——我想就算是Tomcat再怎么“花心”,它至少也得防着“如花”呢——所以需要在Handler类中指定几个函数来指挥服务器怎么去处理数据“流”,而上面的handler()函数就用来指定服务器怎么去处理数据,“流”的。
3、 对于handler()函数必须得先说的是:它是类拟于事件函数一样,是自动调用的——每当服务器收到一个请求(来自客户端的连接)时,就会实例化一个请求处理程序,并且它的各种处理方法(handler methods) 会在处理请求时被调用。
4、 可以看到handler()方法(如果你想称之为函数,貌似并无大碍)里面有几个属性:request、rfile、wfile等等。通过request属性,服务端可以访问到客户端的套接字;rfile(用于读取)和wfile(用于写入)则可以使用 类文件对象 和客户进行通信。“类文件对象”?好吧,请先别纠结于它,如果可以,我希望你可以先用request.recv()和request.send()来代替它们,好不?
终于来到Tornado
很激动吧,现在咱们终于讲回Tornado了。行文将终,有句话我得跟大家提提:在此文前头,我就说过了,我本人对Tornado的了解也不多的,写作此文,亦无非是展示我自己如何通过socket模块来开始渐进Tornado等网络编程的过程。所谓“万事开头难”,也不怕大家笑话,就是理解Tornado中文文档开头的Helloworld这个程序,我就花了好多时间。即便是如今,我也不敢说自己是grok这个看似简单的程序的。但现在,我自认为对Tornado 的理解已开了一个好头,假以时日,我想我对Tornado的理解会显现出突飞猛的势态!!深信,经历这样的过程的人不止我一个。
以下Tornado中文文档中所展示的“经典的‘Hello world’示例”:
[cc lang="python"]

import tornado.ioloop
import tornado.web

class MainHandler(tornado.web.RequestHandler):
def get(self): #请求get处理
self.write(“Hello, world”) #浏览器写入Hello,world

#‘/’就是‘http://localhost:8080/’末尾的’/’
#指定MainHandler来处理来自/的请求
application = tornado.web.Application([
(r"/", MainHandler),
])
if __name__ == “__main__”:
application.listen(8888) #设置端口号为8888
tornado.ioloop.IOLoop.instance().start() #启动Http服务

[/cc]
代码解释:
application.listen(8888)
照旧吧,还是从 __main__ 空间开始来说:
1、 与socket服务器具有很强的对比性——我们可以发现, 上面SocketServer模块服务器的Server()方法就像tornado.web.application,第一个参数都是表示地址(这里是r’/’),第二个参数接收处理器(这里是MainHandler)。
2、 Listen()函数接收的参数表示监听的端口号;
3、 Tornado.ioloop.IOLoop.instance().start()这行代码是用来启动Http服务的,如果对比,也可以发现:loop与forever都是循环之意,都是表示服务的继续。
附录:
Socket模块的知识可参考:

http://blog.sina.com.cn/s/blog_523491650100hikg.html

Tornado的Hello world例程的理解可参考:

http://blog.chinaunix.net/uid-20329571-id-3246879.html

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
老司机应该都会socket!毕竟它这么强大,不是一般人能够驾驭的!
5分钟!教你用C语言发送邮件:附送源码+教学!
Linux下简单的socket通信实例
nginx源码分析(6)——建立连接
linux下socket connect 阻塞方式 阻塞时间控制
面向套接字(Socket)Java编程(单线程+多线程)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服