基于Twisted的网络服务器编写
博客专区 > 舒运 的博客 > 博客详情
基于Twisted的网络服务器编写
舒运 发表于6个月前
基于Twisted的网络服务器编写
  • 发表于 6个月前
  • 阅读 85
  • 收藏 1
  • 点赞 0
  • 评论 0

腾讯云 新注册用户 域名抢购1元起>>>   

开始

此文档解释了如何使用twisted来实现网络协议栈的解析和TCP服务的处理。(相同的代码可以在SSL和Unix socket servers中复用。)

protocol处理类一般从twisted.internet.protocol.Protocol中继承为子类,大多数的protocol handlers要么从这个类继承,或者从相应的子类中再继承。Protocol类的一个实例是每次连接后的实例化,根据实际情况,当连接结束之后被释放。这意味着这种持久性的配置并非永存于Protocol中。

这种永久性的配置其实保存于Factory类中,一般它从 twisted.internet.protocol.Factory 中继承。这个工厂类的buildProtocol()方法在每次连接到来时用于构建一个Protocol对象。

在通常情况下,对多端口或者网络地址上来提供相同的服务是非常有用的。这就是为何工厂Factory并不提供监听连接的原因,实际上它并不知道任何关于网络方面的事情。

Protocols

上面提到,它在大多数代码中存在的形式是伴随着一种附加类和函数。Twisted的protocol主要以异步的方式处理数据:当网络中的事件到达时,协议才响应事件。事件到达后,会调用协议中的方法。以下是个简单的例子:

from twisted.internet.protocol import Protocol  
class Echo(Protocol):  
    def dataReceived(self, data):  
        self.transport.write(data)  

这是一个非常简单的协议。它只是简单的回写所有接收到的数据,并不响应任何事件。以下是另外一个响应事件的协议:

from twisted.internet.protocol import Protocol  
class QOTD(Protocol):  
def connectionMade(self):  
        self.transport.write("An apple a day keeps the doctor away\r\n")   
        self.transport.loseConnection() 

 

这个协议有一个双引号来响应建立的初始连接,然后结束这个连接。connectionMade是一个事件,通常创建于对象的连接发生后,以及所有的初始greetings(如上QOTD协议,它主要基于RFC865).connectionLost事件是对已经处理的所有连接请求指定对象的销毁。以下是例子:

from twisted.internet.protocol import Protocol  
class Echo(Protocol):  
    def __init__(self, factory):  
        self.factory = factory  
    def connectionMade(self):  
        self.factory.numProtocols = self.factory.numProtocols+1   
        self.transport.write(  
            "Welcome! There are currently %d open connections.\n" %  
            (self.factory.numProtocols,))  
    def connectionLost(self, reason):  
        self.factory.numProtocols = self.factory.numProtocols-1  
    def dataReceived(self, data):  
        self.transport.write(data)  

这里的connectionMade和connectionLost事件相互合作在一个共享对象factory中存放一个活动的协议对象总数。当创建一个实例时Factory必须传递给Echo.__init__。Factory用于共享当前的一些状态,这些状态将超出任何给定连接的生命周期。在下一部分内容中你将看到为何把这个对象称为”factory”

loseConnection()和abortConnection()

上述的代码中,loseConnection()在写入传输通道后被调用。loseConnection()方法在所有的数据被Twisted写入操作系统后,它将会关闭连接。所以在这种情况下不用担心通道写入的的数据会被丢失,它是很安全的

如果”生产者”被用于这种通信传输上时,一旦“生产者”被注销,loseConnection()将仅仅关闭连接,传输的数据有可能未成功写入。

在多数情况下,等待所有的数据被成功写出并非我们所想象。由于网络的失效,bug或者其它连接的恶意攻击,即使loseConnection被调用,连接还建立的情况下,这时写入传输通道的数据有时可能依旧无法传递。在这种情况下,abortConnection可以被很好的使用。它不管当前未传输的缓存中是否有数据,或者在“生产者”依旧处于注册的状态下,它都将立刻关闭连接。注意:abortConnection仅仅在高于或等于Twisted11.1版本上才有效。

Using the Protocol

在这部分,你将学会怎样去运行一个使用你自己的协议的服务器。以前面讨论过QOTD服务器,下面将运行这个服务器:

from twisted.internet.protocol import Factory  
from twisted.internet.endpoints import TCP4ServerEndpoint  
from twisted.internet import reactor  
class QOTDFactory(Factory):  
    def buildProtocol(self, addr):  
        return QOTD()  
# 8007 is the port you want to run under. Choose something >1024  
endpoint = TCP4ServerEndpoint(reactor, 8007)  
endpoint.listen(QOTDFactory())  
reactor.run()  

在这个例子中,它创建了一个协议工厂QOTDFactory,它的主要任务是创建一个QOTD协议的实例,所以通过buildProtocol()方法来返回一个QOTD类的实例。然后,开始监听TCP端口,所以让TCP4ServerEndpoint来识别要绑定的端口地址,最后给它的listen方法传递一个协议工厂对象即可。

由于这是个简短的代码,不需要其它任何东西来启动Twisted reactor. Endpoint.listen告诉reactor通过使用一个特殊的协议(由协议工厂实例化的)来处理连接到endpoint的地址的所有连接请求,但是reactor在它要做事之前必须先run一下,所以reactor.run()用于启动reactor然后一直等待你所期望的到达此端口的任何连接请求。

通过Control-C或者调用reactor.stop()来停止reactor.

 

Helper Protocols

许多协议创建于类似的抽象的底层协议。最流行的互联网协议是基于行的。“行”经常用CR-LF来终止。然而,更多的其它协议是混合的,他们有基于行的部分和原始数据部分。这样的例子包括Http/1.1和Freenet协议。

在大多数情况下,会有LineReceiver协议,这个协议将区分两个不同的事件处理: lineReceived和rawDataReceived.缺省情况下每行数据到达行,lineReceived会被调用。然而,如果setRawMode被调用后,协议将采用rawDataReceived方法来接收数据,除非再调用setLineMode来切换到缺省情况。它也提供sendLine方法,它在传传的数据后面会自动的增加”\r\n”

以下是个使用行接收的例子:

from twisted.protocols.basic import LineReceiver  
class Answer(LineReceiver):  
    answers = {'How are you?': 'Fine', None : "I don't know what you mean"}  
    def lineReceived(self, line):  
        if self.answers.has_key(line):  
            self.sendLine(self.answers[line])  
        else:  
            self.sendLine(self.answers[None])  

注意:在这种情况下就不要再增加\r\n了。

Factories

简单协议创建

对于一个工厂来说,它的主要工作是实例化某个指定协议类的实化。有一个更简单的方法来实现一个工厂。缺省的实现是通过buildProtocol方法调用工厂的protocol属性来创建一个协议实例。这种方式可以让每种协议进行各种的访问,做各种修改,来完成这种配置。以下是代码:

from twisted.internet.protocol import Factory, Protocol  
from twisted.internet.endpoints import TCP4ServerEndpoint  
from twisted.internet import reactor  
class QOTD(Protocol):  
    def connectionMade(self):  
        # self.factory was set by the factory's default buildProtocol:  
        self.transport.write(self.factory.quote + '\r\n')  
        self.transport.loseConnection()  
class QOTDFactory(Factory):  
    # This will be used by the default buildProtocol to create new protocols:  
    protocol = QOTD  
    def __init__(self, quote=None):  
        self.quote = quote or 'An apple a day keeps the doctor away'  
endpoint = TCP4ServerEndpoint(reactor, 8007)  
endpoint.listen(QOTDFactory("configurable quote"))  
reactor.run()  

工厂的启动与关闭

工厂具有两种方式来执行相关应用的创建与销毁。以下是例子:

from twisted.internet.protocol import Factory  
from twisted.protocols.basic import LineReceiver  
class LoggingProtocol(LineReceiver):  
    def lineReceived(self, line):  
        self.factory.fp.write(line+'\n')  
class LogfileFactory(Factory):  
    protocol = LoggingProtocol  
    def __init__(self, fileName):  
        self.file = fileName  
    def startFactory(self):  
        self.fp = open(self.file, 'a')  
    def stopFactory(self):  
        self.fp.close()  

综合

以下是最后一个例子,有一个最简单的聊天服务器允许多用户选择用户名然后与其它用户进行通信。它演示了在工厂中如何使用共享的状态,共享每个独立协议的状态机,以及在不同协议之间的通信情况。

from twisted.internet.protocol import Factory  
from twisted.protocols.basic import LineReceiver  
from twisted.internet import reactor  
  
class Chat(LineReceiver):  
    def __init__(self, users):  
        self.users = users  
        self.name = None  
        self.state = "GETNAME"  
  
    def connectionMade(self):  
        self.sendLine("What's your name?")  
  
    def connectionLost(self, reason):  
        if self.users.has_key(self.name):  
            del self.users[self.name]  
  
    def lineReceived(self, line):  
        if self.state == "GETNAME":  
            self.handle_GETNAME(line)  
        else:  
            self.handle_CHAT(line)  
  
    def handle_GETNAME(self, name):  
        if self.users.has_key(name):  
            self.sendLine("Name taken, please choose another.")  
            return  
        self.sendLine("Welcome, %s!" % (name,))  
        self.name = name  
        self.users[name] = self  
        self.state = "CHAT"  
  
    def handle_CHAT(self, message):  
        message = "<%s> %s" % (self.name, message)  
        for name, protocol in self.users.iteritems():  
            if protocol != self:  
                protocol.sendLine(message)  
  
class ChatFactory(Factory):  
  
    def __init__(self):  
        self.users = {} # maps user names to Chat instances  
  
    def buildProtocol(self, addr):  
        return Chat(self.users)  
  
reactor.listenTCP(8123, ChatFactory())  
reactor.run()  

唯一不熟的API有可能就是listenTCP,这是一个将工厂连接到网络的方法。

以下是简单的聊天会话记录:

共有 人打赏支持
粉丝 6
博文 212
码字总数 508914
×
舒运
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: