注意py2 和 py3 的区别:

官方代码环境为 python2.x 使用 python3.x 需要把 Protocol.transport.write() 使用 encode()编码为 b'xxx'收到信息之后使用 decode() 转换为 str

概览

此文档阐述了如何使同 Twisted 实现TCP服务器解析和处理网络协议(SSL和Unix套接字服务器可以重用相同的代码),UDP协议暂且不表。

此处协议处理类通常会是 twisted.internet.protocol.Protocol 的子类,大多数协议处理程序都从这个类或其中一个子进程继承。 在使用过程中,根据需要会实例化一个对象,并且在连接完成时销毁,这意味着在协议中不会保存持久配置。

和 Twisted 实现 TCP 客户端一样,持久配置应该在Factory类中,通常从 twisted.internet.protocol.Factory 继承, Factory 的 buildProtocol 方法用于为每个新连接创建一个 Protocol。

能够在多个端口或网络地址上提供相同的服务很有用,这就是为什么工厂类中通常不监听连接,实际工厂没有网络实例的任何信息,而是一个处理数据的模板。 有关更多信息,请参阅端点文档,或 IReactorTCP.listenTCP 以及端点所基于的较低级别API的其他 IReactor * .listen * API

Protocols

如上所述,通常大多数代码会被编写在此类及其辅助的类和函数中,Twisted 协议以异步方式处理数据。这意味着它从不等待事件,而只是响应(网络)事件。

例程如下:

from twisted.internet.protocol import Protocol

class Echo(Protocol):

    def dataReceived(self, data):
        self.transport.write(data)

这是一个最简单的 protocols 实现之一,它从连接中收到什么就回应什么数据,但是有很多事件它并会不响应(比如:客户端连接事件、断开连接事件等),下面代码用来响应其他部分事件:

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 共同完成计算活动连接数的任务,创建新实例时,必须传递 factory 给Echo._ init_,该工厂用于共享存在于任何给定连接的生命周期之外的状态。

loseConnection() and abortConnection()

在上面的代码中,发送完成数据之后立即调用了 loseConnection ,只有当所有数据被Twisted写入操作系统时,lostConnection调用才会关闭连接,因此在这种情况下可以安全使用,无需担心传输写入丢失。 如果生产者正在与发送数据一起使用,则在一旦生产者未注册连接的情况下,lostConnection将仅关闭连接。有时候我们并不想等待数据全部生产并写入,某些情况下由于网络故障或连接的另一端的错误或恶意连接,写入传输的数据可能无法传输,因此即使丢失连接被称为连接也不会丢失(不会释放)。 在这种情况下,可以使用 abortConnection 立即关闭连接,而不管传输中仍然未写入缓冲的数据,仍然工作的注册生产者。请注意,abortConnection 仅在 Twisted 11.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()  

在这个例子中,创建了一个协议工厂。我想告诉这个工厂,它的工作是构建QOTD协议实例,所以我设置它的 buildProtocol 方法来返回QOTD类的实例。 然后,监听一个TCP端口,所以创建了一个 TCP4ServerEndpoint 来标识我要绑定到的端口,然后将刚才创建的工厂传递给它的监听方法。 endpoint.listen() 告诉反应器(reactor)使用特定协议来处理与端点地址的连接,但 reactor 需要 reactor.run() 启动反应器,然后一直等待连接到达指定端口。
可以通过在终端中 Control-C 或调用 reactor.stop() 来停止反应器。

Helper Protocols

许多协议建立在类似的较低级抽象上, 例如,许多流行的互联网协议是基于行的,包含以换行符(通常为CR-LF)终止的文本数据,而不是直接包含原始数据, 然而,很多协议是混合的 - 它们具有基于行的部分也有原始数据部分。

对于这些情况,有LineReceiver协议,该协议调度到两个不同的事件处理程序 - lineReceived 和 rawDataReceived, 默认情况下,只有lineReceived将被调用,每行一次。但是,如果setRawMode被调用,协议将调用rawDataReceived直到setLineMode被调用,它返回到使用lineReceived。 它还提供了一种方法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 line in self.answers:
            self.sendLine(self.answers[line])
        else:
            self.sendLine(self.answers[None])

请注意,分隔符不是行的一部分。

还有其他几个 helper ,例如基于网络的协议和前缀消息长度协议。

Factories

Simpler Protocol Creation

对于简单实例化特定协议类的实例的工厂来说,实现工厂的方法更简单。buildProtocol方法的默认实现调用工厂的protocol属性来创建一个Protocol实例,然后在工厂上设置一个指向工厂本身的属性。 这允许每个协议访问并可能修改持久配置。以下是使用这些功能而不是覆盖buildProtocol的示例:

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

如果您需要的是一个简单的工厂,构建协议而没有任何其他行为,Twisted 13.1添加了Factory.forProtocol,这是一个更简单的方法。

Factory Startup and Shutdown

工厂有两种方法来执行应用程序特定的构建和拆除(由于工厂经常被保留,在initdel中常常不合适,并且往往太早或太迟)。

以下是允许其协议写入特殊日志文件的工厂的示例:

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

Putting it All Together

作为最后一个例子,这里是一个简单的聊天服务器,允许用户选择用户名,然后与其他用户进行通信,它演示了在工厂中使用共享状态,每个协议使用状态机,以及不同协议之间的通信。

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.name in self.users:
            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 name in self.users:
            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连接到网络的方法。这是端点包装的较低级别的API。

这是聊天会话的示例抄本(强调的文本由用户输入):

 $ telnet 127.0.0.1 8123
 Trying 127.0.0.1...
 Connected to 127.0.0.1.
 Escape character is '^]'.
 What's your name?
 test
 Name taken, please choose another.
 bob
 Welcome, bob!
 hello
 <alice> hi bob
 twisted makes writing servers so easy!
 <alice> I couldn't agree more
 <carrol> yeah, it's great