写在前面的一些话
什么是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]
如何?应该不难看懂我这一小块代码吧?当然,如果你还没看明白的话,或许是卡在上面的三行“红码”中罢?嘻嘻,我早料到会这样了。好,现在就着那三行“红码”,我作个简短解释试试吧——如果看了我的解释还是不懂,或者觉得我的解释不好的话,下文还附录了一些相关网页地址,里面说的,或许可以帮到你哦(我只是个引路人而已):
谢天谢地,我想对于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模块中可供使用的类:
上面例举了这么一些东东,不知道你看了有何感想。不管你怕不怕,我是有点怕的——所以我也不打算全讲了。就图省事,我就直接贴代码吧:
[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
联系客服