<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    posts - 56,  comments - 12,  trackbacks - 0

    概要
      上一節我們分析了BT客戶端與tracker之間的通信過程。通過與 tracker 的通信,客戶端獲得了參與下載的其它peers 的列表。有了這些 peers 的信息,客戶端就可以主動向它們發起連接,為進一步從它們那里獲取所需要的文件片斷做好準備。這些連接稱為“主動連接”。因為連接的發起方是客戶端自己。
      同時,每個客戶端在啟動以后,都會監聽某個端口,用于接受其它 peers 的連接請求。P2P的核心理念就是“公平、對等”,你向別人有所求(下載),就必須有付出(上傳)。客戶端在接受外來的連接請求之后,就會產生一個新的連 接,稱之為“被動連接”,因為連接的發起方是其它 peer。
     無論是被動連接,還是主動連接,一旦連接建立之后,雙方就要進行“BT對等協議”的握手。握手成功之后,雙方才可以通過這個連接互相傳遞消息了。為什么要進行握手了?主要目的是為了防止一些錯誤的連接。這就好比地下黨接頭,暗號對上了,彼此才建立信任。

     

     

     

     

     

     

     

     

     在這個示意圖中,客戶端 A 與 其它 peers B、C、D、E 都建立了連接。通過箭頭來表示連接建立的方向。A主動與 B、D、E 建立連接;A被動接收C的連接。同時C與D、E與D、B與D之間也都有連接。這樣,所有下載者之間就形成了一個網狀的結構。
     同時,這些下載者都要與 tracker 之間不斷通信。
     無論是被動連接,還是主動連接,一旦在“BT對等握手”成功之后,它們就沒有任何區別了。下載通過這個連接進行,上傳也是通過這個連接進行。
     
     本文重點分析BT客戶端如何主動向其它 peers 發起連接;BT客戶端如何被動接收其它 peers 的連接請求;以及在連接建立成功之后,如何進行BT對等協議握手的過程。

    客戶端主動發起連接
    【Encrypter.py】
     上一節的最后,我們看到調用 Encoder::start_connection() 來向其它 peer 發起連接。所以,從這里開始看起。

    class Encoder:
    # start_connection() 需要兩個參數:
    dns:對方的ip、port 的組合。
    id: 對方的 id。每個BT客戶端都要創建一個唯一的 id 號,并且把這個 id 號報告給 tracker。Id號唯一標識了一個客戶端。Id 號的計算方式在 download.py 中。
    myid = 'M' + version.replace('.', '-')
    myid = myid + ('-' * (8 - len(myid))) + b2a_hex(sha(repr(time()) + ' ' +
    str(getpid())).digest()[-6:])
    seed(myid)
    ①def start_connection(self, dns, id):
            if id:
                # 如果 id 是自己,不連接
                if id == self.my_id:
                    return
                # 如果已經和這個peer建立了連接,也不再建立連接。
                for v in self.connections.s():
                    if v.id == id:
    return
      # 如果當前連接數過多,暫時不發起連接
            if len(self.connections) >= self.max_initiate:
    # self.spares 起到緩存的作用。在當前連接數過多的情況下,把本次要連接的 peer 的 ip、port 緩存起來。一旦當前連接數小于設定值 max_initiate, 則可以從 spares 中取出備用的 peers。
                if len(self.spares) < self.max_initiate and dns not in self.spares:
                    self.spares.append(dns)
                return
            try:
    # 調用 RawServer::start_connection(),發起 TCP 的連接。RawServer的代碼分析請參看“服務器源碼分析”系列文章,不再贅述
    # 返回的 c 是一個 SingleSocket 對象,它封裝了 socket 句柄
                # 如果出錯,拋出 socketerror 異常
                c = self.raw_server.start_connection(dns)
    # 成功建立 TCP 連接。構造一個 Connection對象,加入 connections 字典中。注意,最后一個參數是 True,它表面這條連接是由客戶端主動發起建立的。我們立刻去看 Connection 類的構造
                self.connections[c] = Connection(self, c, id, True)
            except socketerror:
                pass
     
    【Encrypter.py】
    class Connection:
      ②  def __init__(self, Encoder, connection, id, is_local):
            self.encoder = Encoder
            self.connection = connection #這個 connection 是 SingleSocket 對象,就是上面 RawServer::start_connection() 返回的值,它封裝了對socket句柄的操作。名字起的不好,容易弄混淆。
            self.id = id
            self.locally_initiated = is_local #這個連接是否由本地發起?
            self.complete = False
            self.closed = False
            self.buffer = StringIO()
            self.next_len = 1
            ⑦self.next_func = self.read_header_len
      
    # 如果由本地發起,那么給對方發送BT對等連接的握手消息。
            ④if self.locally_initiated:
                connection.write(chr(len(protocol_name)) + protocol_name +
                    (chr(0) * 8) + self.encoder.download_id)
                if self.id is not None:
                    connection.write(self.encoder.my_id)

    客戶端被動接受外來連接
    客 戶端在與 tracker 通信的時候,已經把自己的 ip 和監聽的 port 報告給了 tracker。這樣,其它 peers 就可以通過這個 ip 和 port 來連接它了。例如上圖中的C,就主動給 A 發一個連接,從C的角度來說,它是“主動連接”,但從A的角度,它是“被動連接”。
    一旦有外來連接請求,就會調用 RawServer::handle_events(),下面是摘錄的“Tracker 服務器源碼分析之二:RawServer類”中的一段。

     

     

     

     

     

     

     

     

     

    最 后調用的是 Handle 的 external_connection_made(),對客戶端來說,這個Handle 是 Encoder 類對象。所以,外來連接建立成功后,最后調用的是 Encoder:: external_connection_made():

    ③def external_connection_made(self, connection):
     # 同樣是創建一個新的 Connection 類,并加入 connections 字典中。但不同之處在于最后一個參數是 False,表明這個連接是由外部發起的。
    self.connections[connection] = Connection(self, connection, None, False)

    BT對等連接握手:第一步

    如果是主動連接,那么一旦連接建立成功之后,就給對方發送一個握手消息,我們折回去看序號為 4 的代碼:
    if self.locally_initiated:
         connection.write(chr(len(protocol_name)) + protocol_name +
                    (chr(0) * 8) + self.encoder.download_id)
            if self.id is not None:
         connection.write(self.encoder.my_id)

    在《BT協議規范》中,如此描述握手消息:
    對等協議由一個握手開始,后面是循環的消息流,每個消息的前面,都有一個數字來表示消息的長度。握手的過程首先是先發送19,然后發送協議名稱“BitTorrent protocol”。19就是“BitTorrent protocol”的長度。
    后續的所有的整數,都采用big-endian 來編碼為4個字節。
    在協議名稱之后,是8個保留的字節,這些字節當前都設置為0。
    接下來對元文件中的 info 信息,通過 sha1 計算后得到的 hash值,20個字節長。接收消息方,也會對 info 進行一個 hash 運算,如果這兩個結果不一樣,那么說明雙方要下載的文件不一致,會切斷連接。
    接下來是20個字節的 peer id。
    可 以看到,最后兩項就是 Encoder::download_id 和 Encoder::my_id。download_id是如何得來的?它是首先對 torrent 文件中 info 關鍵字所包含的信息進行 Bencoding 方式的編碼(請看《BT協議規范》關于 Bencoding的介紹),然后再通過 sha 函數計算出的 hash(摘要)值。(相關代碼都在 download.py 中)。在一次下載過程中,所有的下載者根據 torrent 文件計算出的這個 download_id應該都是一樣的,否則就說明不處于同一個下載過程中。
    至于 peer_id,可以看出是可選的。它的計算方式也在 download.py 中:
    myid = 'M' + version.replace('.', '-')
    myid = myid + ('-' * (8 - len(myid))) + b2a_hex(sha(repr(time()) + ' ' + str(getpid())).digest()[-6:])
    seed(myid)

    它用來唯一標識一個 peer。
    握手過程已經完成了第一步,還需要第二步,接收到對方的握手消息,握手過程才算完成。所以接下去看在有數據到來的時候,是如何處理的。

    BT對等連接握手:第二步
    當TCP連接上有數據到來的時候, RawServer 會調用到 Encoder:: data_came_in()

    【Encoder】
    ⑤def data_came_in(self, connection, data):
            self.connections[connection].data_came_in(data)

    進一步調用 Connection::data_came_in()

    【Connection】
    ⑥def data_came_in(self, s):
     #這個循環處理用來對BT對等連接中的消息進行分析
            while True:
                if self.closed:
                    return
                i = self.next_len - self.buffer.tell()
                if i > len(s):
                    self.buffer.write(s)
                    return
                self.buffer.write(s[:i])
                s = s[i:]
                m = self.buffer.get()
                self.buffer.reset()
                self.buffer.truncate()
                try:
                    x = self.next_func(m) #調用消息分析函數,第一個被調用的是read_header_len
                except:
                    self.next_len, self.next_func = 1, self.read_dead
                    raise
                if x is None:
                    self.close()
                    return
                self.next_len, self.next_func = x

    ⑧def read_header_len(self, s):
     # 協議的長度是否為 19?
            if ord(s) != len(protocol_name):
                return None
            return len(protocol_name), self.read_header # 下一個處理函數

    def read_header(self, s):
     # 協議名稱是否是“BitTorrent protocol”?
            if s != protocol_name:
                return None
            return 8, self.read_reserved # 下一個處理函數

    def read_reserved(self, s):
     #8個保留字節
            return 20, self.read_download_id # 下一個處理函數

    def read_download_id(self, s):
     對方 download_id 和自己計算出的是否一致?
            if s != self.encoder.download_id:
    return None
      檢查完 download_id,就認為對方已經通過檢查了。
    這里很關鍵!!!,需要仔細體會。這實際上就是在被動連接情況下,完成握手過程的處理。如果連接不是由本地發起的(被動接收到一個握手消息),那么給對方回一個握手消息。這里的握手消息發送處理和第一步是一樣的
            if not self.locally_initiated:
                self.connection.write(chr(len(protocol_name)) + protocol_name +
                    (chr(0) * 8) + self.encoder.download_id + self.encoder.my_id)
            return 20, self.read_peer_id #下一個處理函數

    def read_peer_id(self, s):
    # Connection 類用來管理一個 BT 對等連接。在握手完成之后,就用對方的 peer_id 來唯一標識這個 Connection。這個值被保存在 self.id 中。顯然,在握手完成之前,這個 id 還是空值。
            if not self.id:
      
       # 對方的peer_id 可千萬別跟自己的一樣
                if s == self.encoder.my_id:
                    return None
       唔,如果 peer_id 已經收到過,也不繼續下去了
                for v in self.encoder.connections.s():
                    if s and v.id == s:
    return None
       用對方的 peer_id 為 self.id 賦值,唯一標識這個 Connection
                self.id = s
                if self.locally_initiated:
                    self.connection.write(self.encoder.my_id)
                else:
                    self.encoder.everinc = True
            else:
       
       if s != self.id:
                    return None
      # OK,握手完成!!!
            self.complete = True
            self.encoder.connecter.connection_made(self)
            return 4, self.read_len #下一個處理函數。從此以后,就是對其它BT對等消息的處理過程了。這是我們下一節分析的問題。

    小結:
     這篇文章重點分析了BT客戶端主動發起連接和被動接收連接的過程,以及在這兩種情況下,如何進行BT對等握手的處理。
     在BT對等握手完成之后,連接的雙方就可以互相發送BT對等消息了,這是下一節的內容。
    posted on 2007-01-19 00:24 苦笑枯 閱讀(649) 評論(0)  編輯  收藏 所屬分類: P2P
    收藏來自互聯網,僅供學習。若有侵權,請與我聯系!

    <2007年1月>
    31123456
    78910111213
    14151617181920
    21222324252627
    28293031123
    45678910

    常用鏈接

    留言簿(2)

    隨筆分類(56)

    隨筆檔案(56)

    搜索

    •  

    最新評論

    閱讀排行榜

    評論排行榜

    主站蜘蛛池模板: 亚洲国产精品国自产拍电影| 亚洲AV日韩AV一区二区三曲 | 亚洲日韩在线第一页| 亚洲视频在线观看不卡| 热99RE久久精品这里都是精品免费| 国产大片免费观看中文字幕| 精品国产成人亚洲午夜福利| 毛片视频免费观看| 亚洲字幕AV一区二区三区四区| 免费观看的毛片大全| 亚洲人成在线中文字幕| 日韩不卡免费视频| 97se亚洲国产综合自在线| 免费精品人在线二线三线区别| 香蕉大伊亚洲人在线观看| 成人午夜18免费看| 免费播放国产性色生活片| 亚洲国产高清精品线久久| 一级一片免费视频播放| 婷婷亚洲久悠悠色悠在线播放 | 高清永久免费观看| 亚洲AV无码专区电影在线观看| 特级无码毛片免费视频尤物| 亚洲精品午夜在线观看| 成人免费福利电影| 羞羞视频免费网站日本| 亚洲AV无码一区东京热| 国产精品久久免费| 国产偷国产偷亚洲清高APP| 国产成人精品久久亚洲| 久久国产乱子伦精品免费不卡| 久久精品国产亚洲AV忘忧草18 | 亚洲欧洲无码一区二区三区| 午夜亚洲国产成人不卡在线| a毛片在线免费观看| 亚洲天堂中文字幕在线观看| 免费看香港一级毛片| 日韩精品无码免费专区网站| 亚洲永久中文字幕在线| 亚洲国产综合精品中文字幕| 无码国产精品一区二区免费vr|