1. 程式人生 > >基於Twisted的網路伺服器編寫

基於Twisted的網路伺服器編寫

開始

此文件解釋瞭如何使用twisted來實現網路協議棧的解析和TCP服務的處理。(相同的程式碼可以在SSLUnix socket servers中複用。)

protocol處理類一般從twisted.internet.protocol.Protocol中繼承為子類,大多數的protocol handlers要麼從這個類繼承,或者從相應的子類中再繼承。Protocol類的一個例項是每次連線後的例項化,根據實際情況,當連線結束之後被釋放。這意味著這種永續性的配置並非永存於Protocol中。

這種永久性的配置其實保存於Factory類中,一般它從 twisted.internet.protocol.Factory

 中繼承。這個工廠類的buildProtocol()方法在每次連線到來時用於構建一個Protocol物件。

在通常情況下,對多埠或者網路地址上來提供相同的服務是非常有用的。這就是為何工廠Factory並不提供監聽連線的原因,實際上它並不知道任何關於網路方面的事情。

Protocols

上面提到,它在大多數程式碼中存在的形式是伴隨著一種附加類和函式。Twistedprotocol主要以非同步的方式處理資料:當網路中的事件到達時,協議才響應事件。事件到達後,會呼叫協議中的方法。以下是個簡單的例子:

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)

這裡的connectionMadeconnectionLost事件相互合作在一個共享物件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.1Freenet協議。

在大多數情況下,會有LineReceiver協議,這個協議將區分兩個不同的事件處理: lineReceivedrawDataReceived.預設情況下每行資料到達行,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,這是一個將工廠連線到網路的方法。

以下是簡單的聊天會話記錄: