在前兩節中,我們通過一個簡單的例子從服務器端的角度簡要的研究了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的基類
以上,我們對客戶端部分的一些關鍵組件進行了分析,但是客戶端要研究的不僅僅只有這麼一點,還有很多內容值得我們繼續分析