python 网络编程----非阻塞或异步编程
bj2008_0201
|
1#
bj2008_0201 发表于 2008-09-11 11:23
python 网络编程----非阻塞或异步编程
非阻塞或异步编程
例如,对于一个聊天室来说,因为有多个连接需要同时被处理,所以很显然,阻塞或同步的方法是不合适的,这就像买票只开了一个窗口,佷多人排队等一样。那么我们如何解决这个问题呢?主要有三种方法:forking、threading、异步I/O。 Forking和threading的方法非常简单,通过使用SocketServer服务类的min-in类就可以实现。forking只适用于类Unix平台;threading需要注意内存共享的问题。 异步I/O如果底层的方法来实现是有点困难的。要简单点,我们可以考虑使用标准库中的框架或Twisted(Twisted是一个非常强大的异步网络编程的框架)。 一、用ScoketServer实现Forking和threading 下面我们使用两个例子来分别创建forking服务器和threading服务器。 Forking 服务器: from SocketServer import TCPServer, ForkingMixIn, StreamRequestHandler class Server(ForkingMixIn, TCPServer): pass class Handler(StreamRequestHandler): def handle(self): addr = self.request.getpeername() print 'Got connection from', addr self.wfile.write('Thank you for connecting') server = Server(('', 1234), Handler) server.serve_forever() threading服务器: from SocketServer import TCPServer, ThreadingMixIn, StreamRequestHandler class Server(ThreadingMixIn, TCPServer): pass class Handler(StreamRequestHandler): def handle(self): addr = self.request.getpeername() print 'Got connection from', addr self.wfile.write('Thank you for connecting') server = Server(('', 1234), Handler) server.serve_forever() 二、使用select实现异步I/O 所谓异步I/O,打个比方,就是如果一大群人都想你听他说话,那么你就给他们每人一分钟的时间说,大家轮流说,没说完的待会儿轮到时再继续说。也就是一个时间片的方法。 要实现异步I/O,我们可以通过使用框架asyncore/asynchat或Twisted,它们都是基于select函数或poll函数(poll只适于类Unix系统)的。select和poll函数都来自select模块。 select 函数要求三个必须序列作为参数和一个可选的以秒为单位的超时值。序列中是表示文件描述符的整数值,它们是我们要等待的连接。这三个序列是关于输入、输出和 异常条件的。如果超时值没有给出的话,select将处于阻塞状态(也就是等待)直到有文件描述符准备动作。如果超时值给出了,那么select只阻塞给 定的时间。如果超时值是0的话,那么将不阻塞。select返回的值是一个由三个序列组成的元组,它们分别代表相应参数的活动的子集。例如,第一个序列返 回的是用于读的输入文件描述符构成的序列。 序列可以包含文件对象(不适于Windows)或socket。下面这个例子创建一个使用 select去服务几个连接的服务器(注意:服务端的socket自身也提供给了select,以便于它能够在有新的连接准备接受时发出信号通知)。这个 服务器只是简单地打印接受自客户端的数据。你可以使用telnet(或写一个基于socket的简单的客户端)来连接测试它。 select server import socket, select s = socket.socket() host = socket.gethostname() port = 1234 s.bind((host, port)) s.listen(5) inputs = [s] while True: rs, ws, es = select.select(inputs, [], []) for r in rs: if r is s: c, addr = s.accept() print 'Got connection from', addr inputs.append(c) else: try: data = r.recv(1024) disconnected = not data except socket.error: disconnected = True if disconnected: print r.getpeername(), 'disconnected' inputs.remove(r) else: print data 三、Twisted Twisted 是针对Python的一个事件驱动的网络框架,最初是为了网络游戏而开发的,但是现在被应用于各类网络软件。用Twisted,你可以实现事件处理器,非 常类似用GUI工具包(Tk, GTK, Qt, wxWidgets)。这部分我将介绍一些基本的概念和演示如何使用Twisted来做一些相对简单的 网络编程。Twisted是非常强大的框架并提供了大量的支持,如:Web服务器和客户端、 SSH2, SMTP, POP3, IMAP4, AIM, ICQ, IRC, MSN,Jabber, NNTP, DNS等等。 早先我们所写的基于socket的服务器,它们都有一个显示的事件循环:寻找新的连接和新的数据;基于SocketServer的服务器有一个隐含的循环:寻找连接和为连接创建处理器。但时处理器仍然时显示的读数据。 而 Twisted使用了更多的基于事件的方式。要写一个基本的服务器,你要实现事件处理器,它处理诸如一个新的客户端连接、新的数据到达和客户端连接中断等 情况。在Twisted中,你的事件处理器定义在一个protocol中;你也需要一个factory,当一个新的连接到达时它能够构造这个 protocol对象,但是如果你仅仅想创建一个自定义的Protocol类的实例的话,你可以使用来自Twisted的factory,Factory 类在模块twisted.internet.protocol中。当你写你的protocol时,使用 twisted.internet.protocol模块中的Protocol作为你的父类。当你得到一个连接时,事件处理器 connectionMade被调用;当你丢失了一个连接时,connectionLost被调用。从客户端接受数据使用处理器 dataReceived。但是你不能使用事件处理策略向客户端发送数据;要向客户端发送数据,你可以使用self.transport,它有一个 write方法。它也有一个client属性,其中包含了客户端的地址(主机名和端口)。 下面这个例子是一个Twisted版的服务器。 其中实例化了Factory并设置了它的protocol属性以便它知道使用哪个protocol与客户端通信(这就是所谓的你的自定义 protocol)。然后你使用factory开始监听指定的端口,factory通过实例化的protocol对象处理连接。监听使用reactor模 块中的listenTCP函数。最后,你通过调用reactor模块中的run函数来开始服务器。 from twisted.internet import reactor from twisted.internet.protocol import Protocol, Factory # 定义你Protocol类 class SimpleLogger(Protocol): def connectionMade(self): print 'Got connection from', self.transport.client def connectionLost(self, reason): print self.transport.client, 'disconnected' def dataReceived(self, data): print data # 实例化Factory factory = Factory() # 设置factory的protocol属性以便它知道使用哪个protocol与客户端通信(这就是所谓的你的自定义 # protocol) factory.protocol = SimpleLogger # 监听指定的端口 reactor.listenTCP(1234, factory) # 开始运行主程序 reactor.run() 为 你的处理目的而写一个自定义的protocol是很容易的。模块twisted.protocols.basic中包含了几个有用的已存在的 protocol,其中的LineReceiver执行dataReceived并在接受到了一个完整的行时调用事件处理器lineReceived。如 果当你在接受数据时除了使用lineReceived,还要做些别的,那么你可以使用LineReceiver定义的名为rawDataReceived 事件处理器。下面是一使用LineReceiver的服务器例子: from twisted.internet import reactor from twisted.internet.protocol import Factory from twisted.protocols.basic import LineReceiver class SimpleLogger(LineReceiver): def connectionMade(self): print 'Got connection from', self.transport.client def connectionLost(self, reason): print self.transport.client, 'disconnected' def lineReceived(self, line): print line factory = Factory() factory.protocol = SimpleLogger reactor.listenTCP(1234, factory) reactor.run() |