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()