一个最简单的twisted的TCP服务器代码是酱紫的:
from twisted.internet.protocol import Factory, Protocol
from twisted.internet import reactor
class my_protocol(Protocol):
def __init__(self):
print 'init'
def __del__(self):
print 'del'
def connectionMade(self):
print 'connectionMade'
def connectionLost(self, reason):
print 'connectionLost'
def dataReceived(self, data):
print 'dataReceived:%s' % (data)
if __name__=='__main__':
f = Factory()
f.protocol = my_protocol
reactor.listenTCP(8080, f)
reactor.run()
对于reactor,我的理解是:一个非阻塞的单进程单线程的socket服务器,核心采用epoll_wait来等待一个网络事件。网络事件无非是:用户接入(accept),用户断开(close),数据到达。严谨的处理的话,还要考虑发送缓冲区满这样的场景。作为配套的服务,还需要定时器这样的功能。
虽然socket服务器主要都是和IO打交道,但如果IO都是非阻塞的话,CPU的利用率可以很高。
从编程模式上看来,基于网络事件的处理方式表面上很简单,似乎只要在用户接入,用户断开,数据到达等几个事件上写代码就可以了。
实际上,基于事件的模式,其编码远比多线程模式要复杂很多:
1、服务器必须记录所有客户端当前的状态,本质上来说,要维护一个复杂的状态机;
2、服务器必须使用定时器来处理资源的回收和超时问题:因为,如果没有定时器,网络事件不触发,对应的代码就永远无法执行到;
3、业务代码必须短小精悍,不能做长时间的执行,因为这个服务器是单线程的,阻塞意味着其他客户端无法得到处理。(当然,也可以结合多线程,把耗时的任务丢给独立的线程)
4、业务代码往往没办法从头写到尾,需要大量的状态变量和条件判断来判断:事件触发后,应该从哪个中断点继续执行。
典型的两个中断是:接收一个大的请求,事件触发时,只得到了一部分数据,这时不得不跳出函数,等待下次数据到达的时候,再将前一次的数据合并到一起。
第二种中断是:发送大块的数据到客户端,但是发送缓冲区满了。用简单的处理方法的话,可以在一个循环里面轮训发送,直到数据都发送出去,但是极端情况下,这个轮训可能会造成其他客户端阻塞。否则,就只有记录状态,缓冲区腾出空间后,再从未发送的部分开始发送。
再回过头来说这段简单的twisted的TCP服务器代码:
1、每个客户端连接会导致一个my_protocol对象被创建,然后connectionMade事件被触发;
2、数据到达的时候触发dataReceived事件
3、连接断开的时候触发connectionLost事件,然后对象被析构
4、复杂的状态变量,可以放在对象的成员中
5、dataReceived中要写复杂的代码,解决数据分批到达的情况
6、可惜:还没找到关于发送缓冲区满的问题。