Twisted源碼分析2

在上一篇博文中,我們借一個小例子簡要講解了創建事件監聽循環,與客戶端建立連接。這次,我們將繼續按照上一篇開頭的例子來探究負責底層傳輸的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的相應方法完成數據的傳輸和讀取,以及連接的斷開

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