Twisted源碼分析3

在前兩節中,我們通過一個簡單的例子從服務器端的角度簡要的研究了twisted的源碼。在本節,我們將通過另外一個例子,從客戶端的角度研究twisted的相關源碼,完整例子在這裏

下面是簡化的代碼:

class PoetryProtocol(Protocol):

    poem = ''

    def dataReceived(self, data):
        self.poem += data

    def connectionLost(self, reason):
        self.poemReceived(self.poem)

    def poemReceived(self, poem):
        self.factory.poem_finished(poem)


class PoetryClientFactory(ClientFactory):

    protocol = PoetryProtocol

    def poem_finished(self, poem=None):
        print poem
        reactor.stop()

    def clientConnectionFailed(self, connector, reason):
        print 'Failed to connect to:', connector.getDestination()
        self.poem_finished()


def main():
    address = parse_args() # 解析地址
    factory = PoetryClientFactory()
    from twisted.internet import reactor
    host, port = address
    reactor.connectTCP(host, port, factory)
    reactor.run()


if __name__ == "__main__":
    main()

我們可以看到,在main函數中,前四行代碼與上兩節的例子相差不多,但是第五行代碼reactor.connectTCP(host, port, factory)卻和上一節的reactor.listenTCP不一樣,在上一節中,我們構建的是一個服務,服務器需要監聽所有客戶端的連接。而在本節中,我們要實現的是一個客戶端,所以需要建立對於服務器端的連接請求。然後啓動reactor的事件監聽循環。

# /twisted/internet/posixbase.py

class PosixReactorBase(_SignalReactorMixin, _DisconnectSelectableMixin,ReactorBase):
    def connectTCP(self, host, port, factory, timeout=30, bindAddress=None):
        c = tcp.Connector(host, port, factory, timeout, bindAddress, self)
        c.connect()
        return c

reactor的connectTCP方法是由reactor的基類PosixReactorBase實現的,該方法創建了一個Connector對象,並且調用了該對象的connect方法,並且返回該對象。

# /twisted/internet/tcp.py
class Connector(base.BaseConnector):
    _addressType = address.IPv4Address

    def __init__(self, host, port, factory, timeout, bindAddress, reactor=None):
        if isinstance(port, _portNameType):
            try:
                port = socket.getservbyname(port, 'tcp')
            except socket.error as e:
                raise error.ServiceNameUnknownError(string="%s (%r)" % (e, port))
        self.host, self.port = host, port
        if abstract.isIPv6Address(host):
            self._addressType = address.IPv6Address
        self.bindAddress = bindAddress
        base.BaseConnector.__init__(self, factory, timeout, reactor)

    def _makeTransport(self):
        """創建Client對象綁定到Connector對象"""
        return Client(self.host, self.port, self.bindAddress, self, self.reactor)

# /twisted/internet/base.py
@implementer(IConnector)
class BaseConnector:
    """Basic implementation of connector.
    State can be: "connecting", "connected", "disconnected"
    """
    timeoutID = None
    factoryStarted = 0

    def __init__(self, factory, timeout, reactor):
        self.state = "disconnected"
        self.reactor = reactor
        self.factory = factory
        self.timeout = timeout

    ...

    def connect(self):
        """Start connection to remote server."""
        if self.state != "disconnected":
            raise RuntimeError("can't connect in this state")

        self.state = "connecting"
        if not self.factoryStarted:
            self.factory.doStart()
            self.factoryStarted = 1
        self.transport = transport = self._makeTransport()
        if self.timeout is not None:
            self.timeoutID = self.reactor.callLater(self.timeout, transport.failIfNotConnected, error.TimeoutError())
        # 調用factory對象的startedConnecting方法,該方法由用戶重載,當連接建立時調用
        self.factory.startedConnecting(self)

在connect方法中,調用_makeTransport方法創建了一個Client對象綁定到Connector對象的transport屬性上,有前兩節我們知道,transport負責底層的數據傳輸工作,而這個Client對象就是建立到服務器端的連接

# /twisted/internet/tcp.py
class Client(_BaseTCPClient, BaseClient):
    """
    A transport for a TCP protocol; either TCPv4 or TCPv6.
    Do not create these directly; use L{IReactorTCP.connectTCP}.
    """

Client類是一個拓展的Connection類,它的基類是_BaseTCPClient和BaseClient,而BaseClient的基類是_BaseBaseClient和Connection,Client的構造函數是_BaseTCPClient的構造函數

# /twisted/internet/tcp.py
class _BaseTCPClient(object):
    """
    Code shared with other (non-POSIX) reactors for management of outgoing TCP
    connections (both TCPv4 and TCPv6).
    """

    _addressType = address.IPv4Address

    def __init__(self, host, port, bindAddress, connector, reactor=None):
        # BaseClient.__init__ is invoked later
        self.connector = connector
        self.addr = (host, port)

        whenDone = self.resolveAddress # IP地址處理方法
        err = None
        skt = None

        if abstract.isIPAddress(host):
            self._requiresResolution = False
        elif abstract.isIPv6Address(host):
            self._requiresResolution = False
            self.addr = _resolveIPv6(host, port)
            self.addressFamily = socket.AF_INET6
            self._addressType = address.IPv6Address
        else:
            # 如果host參數不是IP地址格式,那麼該屬性設爲True
            self._requiresResolution = True
        try:
            skt = self.createInternetSocket()
            # createInternetSocket方法是BaseClient實現的,創建一個socket
        except socket.error as se:
            err = error.ConnectBindError(se.args[0], se.args[1])
            whenDone = None
        if whenDone and bindAddress is not None:
            # 如果需要綁定地址
            try:
                if abstract.isIPv6Address(bindAddress[0]):
                    bindinfo = _resolveIPv6(*bindAddress)
                else:
                    bindinfo = bindAddress
                skt.bind(bindinfo)
            except socket.error as se:
                err = error.ConnectBindError(se.args[0], se.args[1])
                whenDone = None
        self._finishInit(whenDone, skt, err, reactor)

_finishInit是由基類_BaseBaseClient實現的

# /twisted/internet/tcp.py

class _BaseBaseClient(object):
    def _finishInit(self, whenDone, skt, error, reactor):
        """
        由子類調用來繼續socket連接的初始化工作
        """
        if whenDone:
            self._commonConnection.__init__(self, skt, None, reactor)
            # _commonConnection就是Connection類
            # 調用Connection類的構造函數
            reactor.callLater(0, whenDone)
            # 當reactor啓動之後回調whenDone方法
        else:
            reactor.callLater(0, self.failIfNotConnected, error)

    def resolveAddress(self):
        """
        如果主機名不是Ip地址的格式,那麼就將其轉化爲Ip地址格式
        """
        if self._requiresResolution:
            d = self.reactor.resolve(self.addr[0])
            d.addCallback(lambda n: (n,) + self.addr[1:])
            d.addCallbacks(self._setRealAddress,
            self.failIfNotConnected)
        else:
            self._setRealAddress(self.addr)

    def _setRealAddress(self, address):
        self.realAddress = address
        self.doConnect()

_finishInit進一步完成了Client對象的初始化工作,resolveAddress解決IP地址的問題, reactor.resolve函數負責將將主機名轉化爲IP地址,該方法返回了一個已經激活的deferred,將立即回調_setRealAddress方法,關於deferred我們放到以後再研究。_setRealAddress中會調用Baseclient的doConnect方法初始化連接。

# /twisted/internet/tcp.py

class BaseClient(_BaseBaseClient, _TLSClientMixin, Connection):
    def doConnect(self):
        self.doWrite = self.doConnect
        self.doRead = self.doConnect
        # 將doWrite和doRead置爲doConnect方法,防止如果同時又該對象的doRead方法調用使得客戶端出現不可預測的情況
        if not hasattr(self, "connector"):
            return

        err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR) # 檢查套接字是否有錯誤產生
        if err:
            self.failIfNotConnected(error.getConnectError((err, strerror(err))))
            return

        # doConnect gets called twice.  The first time we actually need to
        # start the connection attempt.  The second time we don't really
        # want to (SO_ERROR above will have taken care of any errors, and if
        # it reported none, the mere fact that doConnect was called again is
        # sufficient to indicate that the connection has succeeded), but it
        # is not /particularly/ detrimental to do so.  This should get
        # cleaned up some day, though.
        try:
            connectResult = self.socket.connect_ex(self.realAddress)
        except socket.error as se:
            connectResult = se.args[0]
        if connectResult:
            # socket正處於連接狀態
            if connectResult == EISCONN:
                pass
            # on Windows EINVAL means sometimes that we should keep trying:
            # http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winsock/winsock/connect_2.asp
            # socket處於阻塞狀態或者非法參數的情況
            elif ((connectResult in (EWOULDBLOCK, EINPROGRESS, EALREADY)) or
                  (connectResult == EINVAL and platformType == "win32")):
                # select繼續讀和寫事件的監聽
                self.startReading()
                self.startWriting()
                return
            else:
                self.failIfNotConnected(error.getConnectError((connectResult, strerror(connectResult))))
                return

        # If I have reached this point without raising or returning, that means
        # that the socket is connected.
        del self.doWrite
        del self.doRead
        # we first stop and then start, to reset any references to the old doRead
        self.stopReading()
        self.stopWriting()
        self._connectDone()

socket.connect_ex和socket.connect很像,但是當錯誤發生時,connect_ex將返回錯誤碼,而不是拋出異常。failIfNotConnected是在_BaseBaseClient實現的

def failIfNotConnected(self, err):
        """
        當連接失敗是調用,來進行清理工作,調用connectionFailed方法,阻止對該socket的讀和寫事件的監聽
        """
        if (self.connected or self.disconnected or
            not hasattr(self, "connector")):
            return

        self._stopReadingAndWriting() # 移除對該對象的讀和寫事件的監聽
        try:
            self._closeSocket(True)
            # 關閉socket,並進行一些清理工作
        except AttributeError:
            pass
        else:
            self._collectSocketDetails()
        self.connector.connectionFailed(failure.Failure(err))
        # 調用connector的connectionFailed方法,該方法會回調factory對象的clientConnectionFailed方法,並進行一些清理工作
        del self.connector

failIfNotConnected方法可靠的完成了當客戶端連接失敗是的清理工作,當doConnect方法執行到最後時,會調用_connectDone方法:

def _connectDone(self):
        self.protocol = self.connector.buildProtocol(self.getPeer())
        # 創建一個protocol對象
        self.connected = 1
        logPrefix = self._getLogPrefix(self.protocol)
        self.logstr = "%s,client" % logPrefix
        if self.protocol is None:
            self.protocol = Protocol()
            # But dispose of the connection quickly.
            self.loseConnection() # 斷開連接
        else:
            self.startReading()
            self.protocol.makeConnection(self)

在該方法中,我們創建了Protocol對象,該類由用戶自定義,來實現具體的服務。從Client類中,我們可以看到,相比於在上一節我們研究服務器端的時候遇到的Server類(其實就是Connection類),Client比Server多了很多對於連接的檢查和錯誤處理(有一些我們並沒有分析到,以後再研究其他部分時會再講),這對於客戶端而言是很重要的。因爲服務器端要爲許多客戶端的連接提供服務,所以如果有一些意外的錯誤出現,服務器在進行一定的處理之後只能關掉連接然後恢復爲其他客戶端連接服務,所以服務器端對於錯誤處理並不如客戶端那樣嚴格。而客戶端則不一樣,首先客戶端要確保連接能夠成功,如果不成功也要給出正確的關閉和清理措施,然後將錯誤報告給用戶,當遇到錯誤時也要保證佔用的資源能正確釋放,所以Client要比Server更加複雜,同時Client也要具有Server提供的基本的傳輸數據的功能,所以在這裏Server是Client的基類


以上,我們對客戶端部分的一些關鍵組件進行了分析,但是客戶端要研究的不僅僅只有這麼一點,還有很多內容值得我們繼續分析

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章