posts - 56,  comments - 12,  trackbacks - 0

          概要
            上一節我們分析了BT客戶端與tracker之間的通信過程。通過與 tracker 的通信,客戶端獲得了參與下載的其它peers 的列表。有了這些 peers 的信息,客戶端就可以主動向它們發起連接,為進一步從它們那里獲取所需要的文件片斷做好準備。這些連接稱為“主動連接”。因為連接的發起方是客戶端自己。
            同時,每個客戶端在啟動以后,都會監聽某個端口,用于接受其它 peers 的連接請求。P2P的核心理念就是“公平、對等”,你向別人有所求(下載),就必須有付出(上傳)??蛻舳嗽诮邮芡鈦淼倪B接請求之后,就會產生一個新的連 接,稱之為“被動連接”,因為連接的發起方是其它 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 苦笑枯 閱讀(654) 評論(0)  編輯  收藏 所屬分類: P2P
          收藏來自互聯網,僅供學習。若有侵權,請與我聯系!

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

          常用鏈接

          留言簿(2)

          隨筆分類(56)

          隨筆檔案(56)

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 买车| 龙江县| 双峰县| 大化| 高碑店市| 邻水| 武邑县| 城市| 青川县| 延吉市| 昌平区| 葫芦岛市| 桂东县| 连平县| 洛隆县| 乌什县| 七台河市| 磐石市| 夏河县| 昭苏县| 札达县| 嘉黎县| 蓬安县| 宁波市| 宿迁市| 仲巴县| 白水县| 海宁市| 曲周县| 阆中市| 辽阳市| 苍溪县| 赞皇县| 汝南县| 河北区| 余姚市| 惠州市| 青龙| 瓦房店市| 日照市| 会理县|