在上一篇博文中,我們借一個小例子簡要講解了創建事件監聽循環,與客戶端建立連接。這次,我們將繼續按照上一篇開頭的例子來探究負責底層傳輸的transport類
# /twisted/internet/tcp.py
def doRead(self):
...
protocol = self.factory.buildProtocol(self._buildAddr(addr))
if protocol is None:
skt.close()
continue
s = self.sessionno
self.sessionno = s+1
transport = self.transport(skt, protocol, addr, self, s, self.reactor)
protocol.makeConnection(transport)
當有客服端鏈接服務器時,reactor會調用負責監聽客戶端鏈接的Port對象的doRead方法,在doRead方法裏面創建了transport對象,這個transport對象與相應的protocol對象綁定,在上一篇博客開頭的例子中,調用了transport的write方法向客戶端中寫入數據,然後斷開與客戶端的鏈接:
class PoetryProtocol(Protocol):
def connectionMade(self):
self.transport.write(self.factory.poem)
self.transport.loseConnection()
transport對象的類對象是Server,我們在上一篇博客中重點關注了相對簡單的doRead方法,即從客戶端讀取數據,而這次我們講關注write方法。
# /twisted/internet/abstract.py
class FileDescriptor(_ConsumerMixin, _LogOwner):
...
def write(self, data):
if isinstance(data, unicode): # no, really, I mean it
# 不支持unicode字符串的直接傳輸,爲啥呢。。
raise TypeError("Data must not be unicode")
if not self.connected or self._writeDisconnected:
#當連接斷開或者連接處於寫關閉的半打開狀態,那麼直接返回
return
if data:
self._tempDataBuffer.append(data)
# 向臨時緩衝去中添加數據
self._tempDataLen += len(data)
self._maybePauseProducer()
# 如果緩衝區數據已經滿了的話,通知生產者暫停向其提供數據
self.startWriting()
# 將其添加到reactor循環的寫監聽列表中
write方法是在Server的抽象基類FileDescriptor中實現的,當有數據需要寫入的話,該方法將會保證數據被寫入到客戶端。
# /twisted/internet/abstract.py
def doWrite(self):
if len(self.dataBuffer) - self.offset < self.SEND_LIMIT:
# 當緩衝區的數據長度小於最大傳輸數據長度的限制時,將零時緩衝區中的數據添加到緩衝區中,並且移除已經發送的數據,offset之前的數據
self.dataBuffer = _concatenate(
self.dataBuffer, self.offset, self._tempDataBuffer)
self.offset = 0
self._tempDataBuffer = []
self._tempDataLen = 0
# 儘可能發送數據
if self.offset:
l = self.writeSomeData(lazyByteSlice(self.dataBuffer, self.offset))
else:
l = self.writeSomeData(self.dataBuffer)
# 如果返回的l是個異常,證明傳輸失敗直接返回
if isinstance(l, Exception) or l < 0:
return l
self.offset += l
# 如果已經沒有數據可以發送了
if self.offset == len(self.dataBuffer) and not self._tempDataLen:
self.dataBuffer = b""
self.offset = 0
# stop writing.
self.stopWriting()
# 如果有一個向我們提供數據的producer,通知其爲我們提供更多的數據
if self.producer is not None and ((not self.streamingProducer)
or self.producerPaused):
self.producerPaused = False
self.producer.resumeProducing()
elif self.disconnecting:
# 如果之前有要求關閉連接,那麼在數據全書寫入客戶端之後斷開連接
return self._postLoseConnection()
elif self._writeDisconnecting:
# 如果之前有要求關閉連接的寫入,即半關閉。socke.shutdown(1),那麼現在也斷開連接
self._writeDisconnected = True
result = self._closeWriteConnection()
return result
return None
def _postLoseConnection(self):
"""通知reactor連接斷開"""
return main.CONNECTION_DONE
def _closeWriteConnection(self):
try:
self.socket.shutdown(1)
# 半關閉連接,後面的任何寫操作都將不被允許
except socket.error:
pass
p = interfaces.IHalfCloseableProtocol(self.protocol, None)
if p:
try:
p.writeConnectionLost()
except:
f = failure.Failure()
log.err()
self.connectionLost(f)
# /twisted/internet/tcp.py
"""
該方法在基類Connection類中實現
"""
def writeSomeData(self, data):
"""
儘可能多的寫入數據
當連接斷開時,將拋出異常否則成功寫入的數據的數量將被返回
"""
limitedData = lazyByteSlice(data, 0, self.SEND_LIMIT)
try:
return untilConcludes(self.socket.send, limitedData)
except socket.error as se:
if se.args[0] in (EWOULDBLOCK, ENOBUFS):
return 0
else:
return main.CONNECTION_LOST
# /twisted/python/util.py
def untilConcludes(f, *a, **kw):
while True:
try:
return f(*a, **kw)
except (IOError, OSError) as e:
if e.args[0] == errno.EINTR:
# 系統調用被打斷
continue
raise
writeSomeData負責將適當的數據寫入,untilConcludes方法確保數據被傳送,同時有異常則拋出。當連接關閉時,loseConnectio方法將被調用來處理掉相應的socket以及produce
# /twisted/internet/abstract.py
# FileDescriptor類的方法
def loseConnection(self, _connDone=failure.Failure(main.CONNECTION_DONE)):
if self.connected and not self.disconnecting:
if self._writeDisconnected:
如果已經半關閉連接
self.stopReading()
self.stopWriting()
# 直接斷開連接
self.connectionLost(_connDone)
else:
self.stopReading()
# 添加到寫監聽列表中,使得在連接關閉之前,將緩衝區中的所有數據都發送出去
self.startWriting()
# 將disconnecting置爲1,當數據全部發送之後斷開連接
self.disconnecting = 1
# twisted/internet/tcp.py
# Connection類的方法
def connectionLost(self, reason):
if not hasattr(self, "socket"):
return
abstract.FileDescriptor.connectionLost(self, reason)
# 調用基類的connectionLost方法,負責將該對象從reactor的讀寫事件監聽列表中移除
self._closeSocket(not reason.check(error.ConnectionAborted))
# 負責關閉socket
protocol = self.protocol
del self.protocol
del self.socket
del self.fileno
protocol.connectionLost(reason)
# 回調對應protocol的connectionLost方法,該方法由用戶實現,用於做一些必要清理工作
# /twisted/internet/abstract.py
# FileDescriptor類的方法
def connectionLost(self, reason):
self.disconnected = 1
self.connected = 0
# producer對象停止提供數據,移除對producer對象的引用
if self.producer is not None:
self.producer.stopProducing()
self.producer = None
self.stopReading()
self.stopWriting()
# /twisted/internet/tcp.py
# _SocketCloser是Connection類的基類
class _SocketCloser(object):
_shouldShutdown = True
def _closeSocket(self, orderly):
skt = self.socket
try:
if orderly:
if self._shouldShutdown:
skt.shutdown(2)
# 未來讀寫都將被禁止
else:
#SO_LINER設置當socket被關閉時,socket緩衝區緩存中殘留的數據的處理,這裏設置爲1 0,表明將放棄這些數據並且發送一個RST給對方
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER,
struct.pack("ii", 1, 0))
except socket.error:
pass
try:
skt.close() # 關閉套接字
except socket.error:
pass
在調用loseConnection方法之前,將會優先把緩存中的數據全部發送掉然後纔會關閉。
transport封裝了底層的傳輸機制,確保了數據能夠被方便及時的讀取或者寫入,transport一般不需要用戶的重寫,用戶只需要重寫與transport綁定的protocol,在procotol的相應回調方法中調用transport的相應方法完成數據的傳輸和讀取,以及連接的斷開