Twisted官方文档(一)编写一个Server

2 篇文章 0 订阅
2 篇文章 0 订阅

概要

这篇文档告诉你如何使用 twisted 为 TCP 服务实现网络协议解析和处理。

你的处理协议的类通常继承自  twisted.internet.protocol.Protocol 。大部分的处理器都继承这个类或者是继承了比较合适的它的子类。一个协议类的实例是已经实例化了它的每个连接,一经要求,它便会开始执行直到连接结束。这意味着那些持久性的配置是不会保存在协议里面的。

这些持久性的配置是保存在一个 Factory 类里面的,通常都继承自  twisted.internet.protocol.Factory 。Factory 类里面的 buildProtocol 方法通常用于来对每个新的连接创建一个协议。

在多个网络地址和端口提供相同的服务同时是非常有用的。这就是为什么工厂类不监听连接,事实上对于网络它一无所知。

这篇文档会说明这里面执行的步骤。

协议  

就像上面提到的,这里,连同一些辅助的类和功能,就是最主要的代码。Twisted协议使用异步的方法来处理数据,协议在事件通过网络传输过来并触发的时候进行响应,事件触发后就会调用协议里面的相应方法。

下面是一个简单的例子:

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 的事件会在连接建立的时候触发。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 合力来维持一些共享对象中的协议,工厂类。工厂类必须在创建新实例的时候跳转到 Echo.__init__  。这个工厂类用于分享那些存在于超越了生命周期的连接的状态。

loseConnection( ) 和 abortConnection( )
在上面的代码里面,loseConnection 会在向 transport 写入数据后调用。这个 loseConnection 只会在所有的数据输出到操作系统之后被调用并且会关闭连接。所以它十分安全,不用担心传输的数据会丢失。如果一个producer 传输数据,loseConnection 只会在 producer 没有注册的情况下关闭连接。

在一些情况下,一直等到所有数据写出来不是你想要的。由于网络问题,或者是程序里的 bug 等等问题存在于网络的另一端的时候,写入 transport 的数据是无法传输的,那么尽管 loseConnection  被调用,连接却不会被中断。在这种情况下, 就可以使用 abortConnection 了,它能直接关闭连接,不管 transport 里面是否还有为写入的缓冲数据,或者已经注册的 producers 。abortConnection 在 Twisted 11.1 或者更新的版本里面才能够使用。

使用协议

在这部分,你将学习如何用你的协议运行一个服务。

下面就是运行上面提到的 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()
在这个例子里,我创建了一个协议 factory 。我想告诉这个 factoryt 它的任务是创建一个QOTD 协议的实例。所以我将 buildProtocol 方法设置成返回一个 QOTD 类的实例。然后,我想监听一个 TCP 端口,所以我使用了 TCP4ServerEndpoint 方法来设定绑定的特定端口,然后使用 listen 方法,里面的参数就是刚才创建的工厂类。

因为这是个很短的例子,没有别的东西已经启动了 reactor 。endpoint.listen 告诉 reactor 用一个合适的协议来处理到终端地址的连接,但是 reactor 需要运行起来处理相应的任务。 reactor.run( ) 会启动 reactor 然后一直等待有连接连到我们制定的端口上。

你可以在终端里使用 Control-C 或者是 reactor.stop 方法来停止 reactor 。

助手协议   
许多协议都是基于一个较低层次的抽象。大部分流行的互联网协议都是基于行的。在终端里通常可以使用 CR-LF 的组合键来终止它。

可是,很有一些协议时比较混杂的,它有基于行的部分和原始数据部分。例如 HTTP/1.1 和 Freenet 协议。

针对这些情况,有一个 lineReceiver 协议。这个协议调度两个不同的事件处理器 - lineReceived 和 rawReceived 。默认情况下,只有 lineReceived 会被调用,每行调用一次。可是,如果 setRawMode 被调用,那么协议就会调用 rawDataReceived 直到 setLineMode 被调用,它的返回会使用 lineReceived。它还提供了一个方法,sendLine 将数据写入到 transport 并包含了那些用于分隔行的分隔符。

下面是一个使用 lineReceiver 的简单例子:

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

状态机

许多 twisted 协议处理器需要编写一个状态机来记录他们所处的状态。下面有一些帮助建议:

1.不要写一个很大的状态机。倾向于编写一个一次只处理一层抽象的状态机。

2.不要用协议处理代码与应用程序的特定代码混合,当协议处理器必须进行一个应用程序特定的调用时,把它作为一个方法来调用。


Factory

较简单地创建协议

对于一个简单实例化特定协议类的 Factory,有一个更简单的方法来实现它。buildProtocol 方法的默认实现是调用 factory 的 protocol 属性来创建一个协议实例,然后在这个实例上创建一个 factory 属性指向这个 factory 自身。这会让每一个协议都能通过,并且很可能会修改一些持久配置。下面是一个简单的例子:

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()
Factory的启动和停止
一个 Factory 有两个方法来进行应用程序指定的新建和撤销操作。

下面的一个 factory 的例子可以让协议来写入一个特定的日志文件。

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


将它们全部组合起来

作为最后的一个例子,这是一个简单的聊天服务端可以让用户选择一个用户名并且能与其他用户交流。它证明了在 factory 里分享状态的作用,每一个独立的协议都会由一个状态机,并且进行不同协议的通信。

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 了。listenTCP 是让 factory 连接到网络的方法,这是一个终端包装的底层方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值