BT源代码学习心得(十三):客户端源代码分析(对等客户的连接建立及其握手协议) 转自CSDN:gushenghua的专栏
BT源代码学习心得(十三):客户端源代码分析(对等客户的连接建立及其握手协议)
发信人: wolfenstein (NeverSayNever), 个人文集
标 题: BT源代码学习心得(十三):客户端源代码分析(对等客户的连接建立及其握手协议)
发信站: 水木社区 (Wed Aug 17 11:48:45 2005), 文集
标 题: BT源代码学习心得(十三):客户端源代码分析(对等客户的连接建立及其握手协议)
发信站: 水木社区 (Wed Aug 17 11:48:45 2005), 文集
(本文包含HTML标记,终端模式下可能无法正确浏览)
上一次我们分析到了一个客户是如何得获取到对等客户的信息,现在终于要开始建立连
接了。这一次我们将分析两个对等客户之间的连接的建立以及连接对象为它们之间通信提供
的基础框架设施。
Encoder.start_connection建立到某个对等客户的连接。dns参数是IP地址和端口号,
id是对方的peerid。首先检查对方的IP地址是否在banned列表内,如果在,直接返回,就是
说不会再和对方建立连接。这就是通过一个黑名单的机制,避免和某些对等客户连接。这个
黑名单也有一些生成的策略,后面我们可以看到,通常是发现某个对等客户传来的错误数据
过多就将其加入到黑名单中。然后,当然对方id不能等于自己的id,以及在已有的连接中进
行查找,不能重复连接。然后是检查连接数,如果连接数大于某个配置值,那么将这个连接
的信息暂存入spares列表,日后再取出来。接下来就可以让RawServer进行网络连接了。如
果网络连接成功建立,那么一个新的Connection对象就会被创建,并且该网络连接的数据处
理对象(data_came_in)也会被交给这个Connection对象。
现在我们来看看网络的另外一头,就是说对方收到连接后会执行什么代码。由于在
Multitorrent中定义了一个SingleportListener来侦听本地端口,也就是说,所有的外部网
络连接的处理对象都是这唯一的SingleportListener,那么很自然,对方收到连接后,
SingleportListener.external_connection_made会被调用。注意到SingleportListener和
Encoder都定义在BitTorrent/Encoder.py中,看来作者是认为它们关系比较紧密。
SingleportListener.external_connection_made中所做的事情也是创建一个Connection对
上一次我们分析到了一个客户是如何得获取到对等客户的信息,现在终于要开始建立连
接了。这一次我们将分析两个对等客户之间的连接的建立以及连接对象为它们之间通信提供
的基础框架设施。
Encoder.start_connection建立到某个对等客户的连接。dns参数是IP地址和端口号,
id是对方的peerid。首先检查对方的IP地址是否在banned列表内,如果在,直接返回,就是
说不会再和对方建立连接。这就是通过一个黑名单的机制,避免和某些对等客户连接。这个
黑名单也有一些生成的策略,后面我们可以看到,通常是发现某个对等客户传来的错误数据
过多就将其加入到黑名单中。然后,当然对方id不能等于自己的id,以及在已有的连接中进
行查找,不能重复连接。然后是检查连接数,如果连接数大于某个配置值,那么将这个连接
的信息暂存入spares列表,日后再取出来。接下来就可以让RawServer进行网络连接了。如
果网络连接成功建立,那么一个新的Connection对象就会被创建,并且该网络连接的数据处
理对象(data_came_in)也会被交给这个Connection对象。
现在我们来看看网络的另外一头,就是说对方收到连接后会执行什么代码。由于在
Multitorrent中定义了一个SingleportListener来侦听本地端口,也就是说,所有的外部网
络连接的处理对象都是这唯一的SingleportListener,那么很自然,对方收到连接后,
SingleportListener.external_connection_made会被调用。注意到SingleportListener和
Encoder都定义在BitTorrent/Encoder.py中,看来作者是认为它们关系比较紧密。
SingleportListener.external_connection_made中所做的事情也是创建一个Connection对
象,并且完成网络连接的数据处理对象的重定位工作。
两种不同的情况都会有一个新的Connection对象被创建,但是初始化它们的参数不太相
同,另外它们都会被维护到一个字典中,Encoder中的这个字典记录了某个种子文件下载任
务的所有连接,而SingleportListener中的这个字典记录了所有外部来的连接(就是说,还
不知道应该把它们交由哪个Encoder进行管理)。
现在我们来看Connection对象被创建时所做的初始化工作。第一个参数是encoder,就
是说该Connection对象属于哪个encoder管理,这个参数也有可能是SingleportListener,
第二个参数就是由RawServer创建的SingleSocket对象,它通常是用来作为所有连接的字典
中的关键字,另外可以用它来完成具体的网络读写操作。第三个参数是id,指的是对方的
peerid,如果是外部连接(SingleportListener处理的),那么这个参数是None,即还不知道
对方的peerid,最后一个参数是一个布尔值,说明该连接是本地发起的
(Encoder.start_connection)还是外部连入的
(SingleportListener.external_connection_made)。开始的初始化工作基本上是对一些变
量的初始化,注意到_reader变量,_read_messages()函数是一个多次使用了yield关键字的
函数,所以这里_read_messages没有被执行,然后下面的_next_len的赋值部分,使
_read_messages()执行了一些,即开始的yield 1。这样_next_len就等于1,且
_read_messages()函数执行冻结在了这里。最后,如果是主动连接的话,那么就往网络上发
送后面的那一串东西。如果不是主动连接就不用了。发送的那些数据就是BT通信协议的握手
部分的内容。
现在就可以来看Connection.data_came_in函数是如何处理到来的网络数据。
_next_len表示的是下一条完整的消息的长度,_buffer是缓冲区中暂存的信息(因为还没有
达到_next_len的要求),_buffer_len则是缓冲区中的信息的长度。每一次试图从网络数据
两种不同的情况都会有一个新的Connection对象被创建,但是初始化它们的参数不太相
同,另外它们都会被维护到一个字典中,Encoder中的这个字典记录了某个种子文件下载任
务的所有连接,而SingleportListener中的这个字典记录了所有外部来的连接(就是说,还
不知道应该把它们交由哪个Encoder进行管理)。
现在我们来看Connection对象被创建时所做的初始化工作。第一个参数是encoder,就
是说该Connection对象属于哪个encoder管理,这个参数也有可能是SingleportListener,
第二个参数就是由RawServer创建的SingleSocket对象,它通常是用来作为所有连接的字典
中的关键字,另外可以用它来完成具体的网络读写操作。第三个参数是id,指的是对方的
peerid,如果是外部连接(SingleportListener处理的),那么这个参数是None,即还不知道
对方的peerid,最后一个参数是一个布尔值,说明该连接是本地发起的
(Encoder.start_connection)还是外部连入的
(SingleportListener.external_connection_made)。开始的初始化工作基本上是对一些变
量的初始化,注意到_reader变量,_read_messages()函数是一个多次使用了yield关键字的
函数,所以这里_read_messages没有被执行,然后下面的_next_len的赋值部分,使
_read_messages()执行了一些,即开始的yield 1。这样_next_len就等于1,且
_read_messages()函数执行冻结在了这里。最后,如果是主动连接的话,那么就往网络上发
送后面的那一串东西。如果不是主动连接就不用了。发送的那些数据就是BT通信协议的握手
部分的内容。
现在就可以来看Connection.data_came_in函数是如何处理到来的网络数据。
_next_len表示的是下一条完整的消息的长度,_buffer是缓冲区中暂存的信息(因为还没有
达到_next_len的要求),_buffer_len则是缓冲区中的信息的长度。每一次试图从网络数据
s中得到组成下一个完整的消息的数据,因此首先计算长度,_next_len-_buffer_len说明要
从s中得到多少数据。如果s中没有这么多数据就把s中的数据暂存到缓冲区中,然后就返回
。这样下一次调用data_came_in时就可以继续组建需要的数据了。如果s中有足够的数据,
则将其组成合适的长度(_next_len),放入_message中,以方便处理。然后让
_read_messages()继续往下执行。如果s中还有数据,则while循环要继续进行。我们看到,
在data_came_in的这种设计框架下,_read_messages()函数每次yield一个值,当它接下来
恢复执行时,_message中就已经有它要的值了。
这样,_read_messages()就可以专门处理协议。我们现在就可以对比本地发起的网络连
接的初始化过程中发出的那个握手字符串来分析握手协议。首先yield 1,然后一个字节的
数据进入了_message,这就是chr(len(protocol_name)),协议名称的长度。通过把这个字
符还原成整数,看它是否和协议名称相同。接下来yield len(protocol_name),然后进入
_message的数据就是protocol_name,检查看它是否是'BitTorrent protocol',然后yield
8,这是8个字节的保留串,不用进行任何处理,继续yield 20。这是download_id的值,
encoder.download_id是什么呢?就是种子文件的infohash。然后检查
self.encoder.download_id,如果是None,那么说明这个Connection对象是
SingleportListener建立的,就是说这是网络中来的连接,因此程序运行到这里就可以做的
一件事情就是看看这个infohash到底是本地的哪个下载任务,更准确的说,这个
Connection对象应该交给哪个Encoder进行管理。所以它调用了encoder.select_torrent(其
实就是SingleportListener.select_torrent),这个函数从维护的torrents字典中根据
infohash查找对应的Encoder,然后让Encoder.singleport_connection进行Connection的交
接。在Encoder.singleport_connection中做的事情包括检查对方的IP是否在banned列表中
,否则拒绝其连接。然后将Connection对象添加到自己维护的字典中,并且将其从
从s中得到多少数据。如果s中没有这么多数据就把s中的数据暂存到缓冲区中,然后就返回
。这样下一次调用data_came_in时就可以继续组建需要的数据了。如果s中有足够的数据,
则将其组成合适的长度(_next_len),放入_message中,以方便处理。然后让
_read_messages()继续往下执行。如果s中还有数据,则while循环要继续进行。我们看到,
在data_came_in的这种设计框架下,_read_messages()函数每次yield一个值,当它接下来
恢复执行时,_message中就已经有它要的值了。
这样,_read_messages()就可以专门处理协议。我们现在就可以对比本地发起的网络连
接的初始化过程中发出的那个握手字符串来分析握手协议。首先yield 1,然后一个字节的
数据进入了_message,这就是chr(len(protocol_name)),协议名称的长度。通过把这个字
符还原成整数,看它是否和协议名称相同。接下来yield len(protocol_name),然后进入
_message的数据就是protocol_name,检查看它是否是'BitTorrent protocol',然后yield
8,这是8个字节的保留串,不用进行任何处理,继续yield 20。这是download_id的值,
encoder.download_id是什么呢?就是种子文件的infohash。然后检查
self.encoder.download_id,如果是None,那么说明这个Connection对象是
SingleportListener建立的,就是说这是网络中来的连接,因此程序运行到这里就可以做的
一件事情就是看看这个infohash到底是本地的哪个下载任务,更准确的说,这个
Connection对象应该交给哪个Encoder进行管理。所以它调用了encoder.select_torrent(其
实就是SingleportListener.select_torrent),这个函数从维护的torrents字典中根据
infohash查找对应的Encoder,然后让Encoder.singleport_connection进行Connection的交
接。在Encoder.singleport_connection中做的事情包括检查对方的IP是否在banned列表中
,否则拒绝其连接。然后将Connection对象添加到自己维护的字典中,并且将其从
SingleportListener的字典中删除,然后将Connection的encoder指向自己,这样这个
Connection就正式归这个Encoder管理了。再返回到_read_messages()函数中,对
encoder.download_id的检查就可以证明以上过程是否成功完成了。下面一个elif则是主动
的连接,对方返回的download_id在_message中,如果和自己的encoder.download_id不符,
则中断该连接。下面检查是否是本地发起的连接,如果不是本地发起的连接,则也向对方发
送握手协议(这样对方的_read_messages()函数也可以开始运行了)。接下来yield 20,得到
peer id。这是对方的ID,Connection中保留的id都是对方的ID,自己的ID保留在Encoder中
。下面的这一段代码对得到peer id进行处理,如果需要则保留到自己的id变量中,并且根
据自己的Encoder的Connection字典进行检查,以避免两个对等客户在同一种子文件下载任
务中的重复连接。
在握手协议的最后一步调用了Encoder.connection_completed,说明这个连接建立成功
,可以正式进行数据的交互了。在这个函数中做的工作就是为这个Connection生成一个
Upload和SingleDownload对象,并且把这个Connection交给Choker进行管理。
回到_read_messages()中,下面我们可以看到,握手协议已经成功完成,开始传送其它
数据。每一条消息都分割成一个四字节长的长度和消息本身,因此while循环中不断的
yield 4和yield l,然后_got_message来处理每个消息。
通过这一次的分析,我们知道了两个对等客户之间的连接的握手协议,以及Encoder,
Connection,Upload,SingleDownload这些基本对象在连接建立时的基本关系。下一次就可
以开始分析BT通信协议中的其它部分。
Connection就正式归这个Encoder管理了。再返回到_read_messages()函数中,对
encoder.download_id的检查就可以证明以上过程是否成功完成了。下面一个elif则是主动
的连接,对方返回的download_id在_message中,如果和自己的encoder.download_id不符,
则中断该连接。下面检查是否是本地发起的连接,如果不是本地发起的连接,则也向对方发
送握手协议(这样对方的_read_messages()函数也可以开始运行了)。接下来yield 20,得到
peer id。这是对方的ID,Connection中保留的id都是对方的ID,自己的ID保留在Encoder中
。下面的这一段代码对得到peer id进行处理,如果需要则保留到自己的id变量中,并且根
据自己的Encoder的Connection字典进行检查,以避免两个对等客户在同一种子文件下载任
务中的重复连接。
在握手协议的最后一步调用了Encoder.connection_completed,说明这个连接建立成功
,可以正式进行数据的交互了。在这个函数中做的工作就是为这个Connection生成一个
Upload和SingleDownload对象,并且把这个Connection交给Choker进行管理。
回到_read_messages()中,下面我们可以看到,握手协议已经成功完成,开始传送其它
数据。每一条消息都分割成一个四字节长的长度和消息本身,因此while循环中不断的
yield 4和yield l,然后_got_message来处理每个消息。
通过这一次的分析,我们知道了两个对等客户之间的连接的握手协议,以及Encoder,
Connection,Upload,SingleDownload这些基本对象在连接建立时的基本关系。下一次就可
以开始分析BT通信协议中的其它部分。