﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-苍穹之铁十字-随笔分类-P2P</title><link>http://www.blogjava.net/kuxiaoku/category/21189.html</link><description>收藏</description><language>zh-cn</language><lastBuildDate>Mon, 02 Apr 2007 00:08:28 GMT</lastBuildDate><pubDate>Mon, 02 Apr 2007 00:08:28 GMT</pubDate><ttl>60</ttl><item><title>BT客户端源码分析之八：BT对等连接的建立过程</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94822.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:24:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94822.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94822.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94822.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94822.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94822.html</trackback:ping><description><![CDATA[<p>概要<br>&nbsp;
上一节我们分析了BT客户端与tracker之间的通信过程。通过与 tracker 的通信，客户端获得了参与下载的其它peers
的列表。有了这些 peers
的信息，客户端就可以主动向它们发起连接，为进一步从它们那里获取所需要的文件片断做好准备。这些连接称为&#8220;主动连接&#8221;。因为连接的发起方是客户端自己。<br>&nbsp;
同时，每个客户端在启动以后，都会监听某个端口，用于接受其它 peers
的连接请求。P2P的核心理念就是&#8220;公平、对等&#8221;，你向别人有所求（下载），就必须有付出（上传）。客户端在接受外来的连接请求之后，就会产生一个新的连
接，称之为&#8220;被动连接&#8221;，因为连接的发起方是其它 peer。<br>&nbsp;无论是被动连接，还是主动连接，一旦连接建立之后，双方就要进行&#8220;BT对等协议&#8221;的握手。握手成功之后，双方才可以通过这个连接互相传递消息了。为什么要进行握手了？主要目的是为了防止一些错误的连接。这就好比地下党接头，暗号对上了，彼此才建立信任。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;在这个示意图中，客户端 A 与 其它 peers B、C、D、E 都建立了连接。通过箭头来表示连接建立的方向。A主动与 B、D、E 建立连接；A被动接收C的连接。同时C与D、E与D、B与D之间也都有连接。这样，所有下载者之间就形成了一个网状的结构。<br>&nbsp;同时，这些下载者都要与 tracker 之间不断通信。<br>&nbsp;无论是被动连接，还是主动连接，一旦在&#8220;BT对等握手&#8221;成功之后，它们就没有任何区别了。下载通过这个连接进行，上传也是通过这个连接进行。<br>&nbsp;<br>&nbsp;本文重点分析BT客户端如何主动向其它 peers 发起连接；BT客户端如何被动接收其它 peers 的连接请求；以及在连接建立成功之后，如何进行BT对等协议握手的过程。</p>
<p>客户端主动发起连接<br>【Encrypter.py】<br>&nbsp;上一节的最后，我们看到调用 Encoder::start_connection() 来向其它 peer 发起连接。所以，从这里开始看起。</p>
<p>class Encoder:<br># start_connection() 需要两个参数：<br>dns：对方的ip、port 的组合。<br>id： 对方的 id。每个BT客户端都要创建一个唯一的 id 号，并且把这个 id 号报告给 tracker。Id号唯一标识了一个客户端。Id 号的计算方式在 download.py 中。<br>myid = 'M' + version.replace('.', '-')<br>myid = myid + ('-' * (8 - len(myid))) + b2a_hex(sha(repr(time()) + ' ' + <br>str(getpid())).digest()[-6:])<br>seed(myid)<br>①def start_connection(self, dns, id):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # 如果 id 是自己，不连接<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if id == self.my_id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # 如果已经和这个peer建立了连接，也不再建立连接。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for v in self.connections.s():<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if v.id == id:<br>return<br>&nbsp;&nbsp;# 如果当前连接数过多，暂时不发起连接<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if len(self.connections) &gt;= self.max_initiate: <br># self.spares 起到缓存的作用。在当前连接数过多的情况下，把本次要连接的 peer 的 ip、port 缓存起来。一旦当前连接数小于设定值 max_initiate， 则可以从 spares 中取出备用的 peers。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if len(self.spares) &lt; self.max_initiate and dns not in self.spares:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.spares.append(dns)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try:<br># 调用 RawServer::start_connection()，发起 TCP 的连接。RawServer的代码分析请参看&#8220;服务器源码分析&#8221;系列文章，不再赘述<br># 返回的 c 是一个 SingleSocket 对象，它封装了 socket 句柄<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # 如果出错，抛出 socketerror 异常<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = self.raw_server.start_connection(dns)<br># 成功建立 TCP 连接。构造一个 Connection对象，加入 connections 字典中。注意，最后一个参数是 True，它表面这条连接是由客户端主动发起建立的。我们立刻去看 Connection 类的构造<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connections[c] = Connection(self, c, id, True)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; except socketerror:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pass<br>&nbsp;<br>【Encrypter.py】<br>class Connection:<br>&nbsp; ②&nbsp; def __init__(self, Encoder, connection, id, is_local):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.encoder = Encoder<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
self.connection = connection #这个 connection 是 SingleSocket 对象，就是上面
RawServer::start_connection() 返回的值，它封装了对socket句柄的操作。名字起的不好，容易弄混淆。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.id = id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.locally_initiated = is_local #这个连接是否由本地发起？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.complete = False<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.closed = False<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.buffer = StringIO()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.next_len = 1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ⑦self.next_func = self.read_header_len<br>&nbsp;&nbsp;<br># 如果由本地发起，那么给对方发送BT对等连接的握手消息。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ④if self.locally_initiated:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; connection.write(chr(len(protocol_name)) + protocol_name + <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (chr(0) * 8) + self.encoder.download_id)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.id is not None:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; connection.write(self.encoder.my_id)</p>
<p>客户端被动接受外来连接<br>客
户端在与 tracker 通信的时候，已经把自己的 ip 和监听的 port 报告给了 tracker。这样，其它 peers 就可以通过这个
ip 和 port 来连接它了。例如上图中的C，就主动给 A 发一个连接，从C的角度来说，它是&#8220;主动连接&#8221;，但从A的角度，它是&#8220;被动连接&#8221;。<br>一旦有外来连接请求，就会调用 RawServer::handle_events()，下面是摘录的&#8220;Tracker 服务器源码分析之二：RawServer类&#8221;中的一段。</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>最
后调用的是 Handle 的 external_connection_made()，对客户端来说，这个Handle 是 Encoder
类对象。所以，外来连接建立成功后，最后调用的是 Encoder:: external_connection_made()：</p>
<p>③def external_connection_made(self, connection):<br>&nbsp;# 同样是创建一个新的 Connection 类，并加入 connections 字典中。但不同之处在于最后一个参数是 False，表明这个连接是由外部发起的。<br>self.connections[connection] = Connection(self, connection, None, False)</p>
<p>BT对等连接握手：第一步</p>
<p>如果是主动连接，那么一旦连接建立成功之后，就给对方发送一个握手消息，我们折回去看序号为 4 的代码：<br>if self.locally_initiated:<br>&nbsp;&nbsp;&nbsp; &nbsp;connection.write(chr(len(protocol_name)) + protocol_name + <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (chr(0) * 8) + self.encoder.download_id)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.id is not None:<br>&nbsp;&nbsp;&nbsp; &nbsp;connection.write(self.encoder.my_id)</p>
<p>在《BT协议规范》中，如此描述握手消息：<br>对等协议由一个握手开始，后面是循环的消息流，每个消息的前面，都有一个数字来表示消息的长度。握手的过程首先是先发送19，然后发送协议名称&#8220;BitTorrent protocol&#8221;。19就是&#8220;BitTorrent protocol&#8221;的长度。<br>后续的所有的整数，都采用big-endian 来编码为4个字节。<br>在协议名称之后，是8个保留的字节，这些字节当前都设置为0。<br>接下来对元文件中的 info 信息，通过 sha1 计算后得到的 hash值，20个字节长。接收消息方，也会对 info 进行一个 hash 运算，如果这两个结果不一样，那么说明双方要下载的文件不一致，会切断连接。<br>接下来是20个字节的 peer id。<br>可
以看到，最后两项就是 Encoder::download_id 和
Encoder::my_id。download_id是如何得来的？它是首先对 torrent 文件中 info 关键字所包含的信息进行
Bencoding 方式的编码（请看《BT协议规范》关于 Bencoding的介绍），然后再通过 sha 函数计算出的
hash（摘要）值。（相关代码都在 download.py 中）。在一次下载过程中，所有的下载者根据 torrent 文件计算出的这个
download_id应该都是一样的，否则就说明不处于同一个下载过程中。<br>至于 peer_id，可以看出是可选的。它的计算方式也在 download.py 中：<br>myid = 'M' + version.replace('.', '-')<br>myid = myid + ('-' * (8 - len(myid))) + b2a_hex(sha(repr(time()) + ' ' + str(getpid())).digest()[-6:])<br>seed(myid)</p>
<p>它用来唯一标识一个 peer。<br>握手过程已经完成了第一步，还需要第二步，接收到对方的握手消息，握手过程才算完成。所以接下去看在有数据到来的时候，是如何处理的。</p>
<p>BT对等连接握手：第二步<br>当TCP连接上有数据到来的时候， RawServer 会调用到 Encoder:: data_came_in()</p>
<p>【Encoder】<br>⑤def data_came_in(self, connection, data):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connections[connection].data_came_in(data)</p>
<p>进一步调用 Connection::data_came_in()</p>
<p>【Connection】<br>⑥def data_came_in(self, s):<br>&nbsp;#这个循环处理用来对BT对等连接中的消息进行分析<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while True:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.closed:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i = self.next_len - self.buffer.tell()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i &gt; len(s):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.buffer.write(s)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.buffer.write(s[:i])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = s[i:]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m = self.buffer.get()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.buffer.reset()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.buffer.truncate()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x = self.next_func(m) #调用消息分析函数，第一个被调用的是read_header_len<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; except:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.next_len, self.next_func = 1, self.read_dead<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; raise<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if x is None:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.close()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.next_len, self.next_func = x</p>
<p>⑧def read_header_len(self, s):<br>&nbsp;# 协议的长度是否为 19？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ord(s) != len(protocol_name):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return len(protocol_name), self.read_header # 下一个处理函数</p>
<p>def read_header(self, s):<br>&nbsp;# 协议名称是否是&#8220;BitTorrent protocol&#8221;？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s != protocol_name:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 8, self.read_reserved # 下一个处理函数</p>
<p>def read_reserved(self, s):<br>&nbsp;#8个保留字节<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 20, self.read_download_id # 下一个处理函数</p>
<p>def read_download_id(self, s):<br>&nbsp;对方 download_id 和自己计算出的是否一致？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s != self.encoder.download_id:<br>return None<br>&nbsp;&nbsp;检查完 download_id，就认为对方已经通过检查了。<br>这里很关键！！！，需要仔细体会。这实际上就是在被动连接情况下，完成握手过程的处理。如果连接不是由本地发起的（被动接收到一个握手消息），那么给对方回一个握手消息。这里的握手消息发送处理和第一步是一样的<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not self.locally_initiated:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connection.write(chr(len(protocol_name)) + protocol_name + <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (chr(0) * 8) + self.encoder.download_id + self.encoder.my_id)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 20, self.read_peer_id #下一个处理函数</p>
<p>def read_peer_id(self, s):<br># Connection 类用来管理一个 BT 对等连接。在握手完成之后，就用对方的 peer_id 来唯一标识这个 Connection。这个值被保存在 self.id 中。显然，在握手完成之前，这个 id 还是空值。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not self.id:<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;# 对方的peer_id 可千万别跟自己的一样<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == self.encoder.my_id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None<br>&nbsp;&nbsp;&nbsp;唔，如果 peer_id 已经收到过，也不继续下去了<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for v in self.encoder.connections.s():<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s and v.id == s:<br>return None<br>&nbsp;&nbsp;&nbsp;用对方的 peer_id 为 self.id 赋值，唯一标识这个 Connection<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.id = s<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.locally_initiated:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connection.write(self.encoder.my_id)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.encoder.everinc = True<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;if s != self.id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None<br>&nbsp;&nbsp;# OK，握手完成！！！<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.complete = True<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.encoder.connecter.connection_made(self)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 4, self.read_len #下一个处理函数。从此以后，就是对其它BT对等消息的处理过程了。这是我们下一节分析的问题。</p>
小结：<br>&nbsp;这篇文章重点分析了BT客户端主动发起连接和被动接收连接的过程，以及在这两种情况下，如何进行BT对等握手的处理。<br>&nbsp;在BT对等握手完成之后，连接的双方就可以互相发送BT对等消息了，这是下一节的内容。  <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94822.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:24 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94822.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之七：客户端与tracker通信过程</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94821.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:23:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94821.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94821.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94821.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94821.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94821.html</trackback:ping><description><![CDATA[<p>概要<br>&nbsp;上一节我们分析了BT客户端主程序的框架：一个循环的服务器程序。在这个循环过程中，客户端要完成如下任务：<br>?&nbsp;与 tracker 服?&nbsp;务器通信；汇报自己的状态，同?&nbsp;时获取其它 peers 的信息；<br>?&nbsp;接受其它 peers 的连接请求；<br>?&nbsp;主动向某些 peers 发起连接请求；<br>?&nbsp;对BT对等协议的分析处理；<br>?&nbsp;启用片断选择算法，?&nbsp;通过这些主动或被动的连接去下载所需要的片断；<br>?&nbsp;为其它 peers 提供片断上传；启用阻塞算法，?&nbsp;阻塞某些 peers 的上传请求；<br>?&nbsp;将下载的片断写入磁盘；<br>?&nbsp;其它任务；</p>
<p>&nbsp;
这一节我们分析BT客户端与 tracker 服务器的通信过程。在整个下载过程中，客户端必须不断的与 tracker
通信，报告自己的状态，同时也从 tracker 那里获取当前参与下载的其它 peers 的信息（ip地址、端口等）。一旦不能与 tracker
通信，则下载过程无法继续下去。<br>&nbsp;客户端与 tracker 之间采用 HTTP 协议通信，客户端把状态信息放在
HTTP协议的GET命令的参数中传递给 tracker，tracker 则把 peers列表等信息放在 中传递给
客户端。这种设计非常简捷巧妙。采用HTTP协议也更使得穿越防火墙的阻拦更容易（不过实际应用中，tracker通常不会使用 80 端口）。<br>&nbsp;请务必参考服务器端源码分析的几篇相关文章（论坛中有）。<br>&nbsp;Rerequester 类全权负责与 tracker 的通信任务，我们首先看客户端主程序 download.py中相应的代码：</p>
<p>【download.py】<br>①rerequest = Rerequester(response['announce'], config['rerequest_interval'], <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rawserver.add_task, connecter.how_many_connections, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; config['min_peers'], encoder.start_connection, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; rawserver.add_task, storagewrapper.get_amount_left, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; upmeasure.get_total, downmeasure.get_total, listen_port, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; config['ip'], myid, infohash, config['http_timeout'], errorfunc, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; config['max_initiate'], doneflag, upmeasure.get_rate, <br>downmeasure.get_rate,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; encoder.ever_got_incoming<br>②rerequest.begin()</p>
<p>Rerequester
类的构造函数中传递了一大堆参数，重点是 rawserver.add_task和
encoder.start_connection（rawserver和encoder
这两个对象，在上一节中，我们已经看到了它们的构造过程），分别对应RawServer::add_task() 和
Encoder::start_connection()，稍后会看到它们的作用。<br>一切从 Rerequest::begin() 开始。</p>
<p>【rerequester.py】</p>
<p>class Rerequester:<br>&nbsp;# 这个构造函数传递了一大堆参数，现在没必要搞的那么清楚，关键是 sched 和 connect 这两个参数，对照前面的代码，很容易知道它们是什么。<br>&nbsp;&nbsp;&nbsp; def __init__(self, url, interval, sched, howmany, minpeers, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; connect, externalsched, amount_left, up, down,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; port, ip, myid, infohash, timeout, errorfunc, maxpeers, doneflag,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; upratefunc, downratefunc, ever_got_incoming):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.url = ('%s?info_hash=%s&amp;peer_id=%s&amp;port=%s&amp;key=%s' %<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (url, quote(infohash), quote(myid), str(port),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; b2a_hex(''.join([chr(randrange(256)) for i in xrange(4)]))))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ip != '':<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.url += '&amp;ip=' + quote(ip)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.interval = interval<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.last = None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.trackerid = None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.announce_interval = 30 * 60<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.sched = sched # －》RawServer::add_task()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.howmany = howmany # 当前有多少个连接 peer<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.minpeers = minpeers<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connect = connect #－》 Encoder::start_connection()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.externalsched = externalsched<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.amount_left = amount_left<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.up = up<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.down = down<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.timeout = timeout<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.errorfunc = errorfunc<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.maxpeers = maxpeers<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.doneflag = doneflag<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.upratefunc = upratefunc<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.downratefunc = downratefunc<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.ever_got_incoming = ever_got_incoming<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.last_failed = True<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.last_time = 0</p>
<p>④&nbsp;def c(self):<br>&nbsp;&nbsp;# 再调用一次 add_task()，把自身加入到客户端的任务队列中，这样就形成了任务循环，每隔一段时间，就会执行这个函数。从而，客户端与 tracker 的通信能够持续的进行下去。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.sched(self.c, self.interval)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.ever_got_incoming():<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
# 如果曾经接受到过外来的连接，那么说明这个客户端是可以接受外来连接的，也就是说很可能处于&#8220;公网&#8221;之中，所以并非很迫切的需要从 tracker
那里获取 peers 列表。（既可以主动连接别人，也可以接受别人的连接，所以有点不紧不慢的味道）。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getmore = self.howmany() &lt;= self.minpeers / 3<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # 如果从来没有接受到过外来的连接，那么很有可能处于内网之中，根本无法接受外来的连接。这种情况下，只有主动的去连接外部的 peers，所以要尽可能的向 tracker 请求更多的 peers。（只能去连接别人，所以更迫切一些）。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getmore = self.howmany() &lt; self.minpeers<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if getmore or time() - self.last_time &gt; self.announce_interval:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # 如果有必要从 tracker 那里获取 peers 列表，且任务执行时间已到，则调用 announce() 开始与 tracker 通信<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.announce()</p>
<p>③&nbsp;&nbsp;&nbsp; def begin(self): <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # 调用前面提到的 RawServer::add_task()，把 Rerequester::c() 加入到 rawserver对象的任务队列中<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.sched(self.c, self.interval)<br>&nbsp;&nbsp;# 宣布与 tracker 的通信开始。。。。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.announce(0)</p>
<p>⑤&nbsp;&nbsp;&nbsp; def announce(self, event = None):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.last_time = time()<br>&nbsp;&nbsp;# 传递给 tracker 的信息，放在 HTTP GET 命令的参数中。所以，首先就是构造这个参数。<br>&nbsp;&nbsp;# 传递给 tracker 的信息，必须包括 uploaded、downloaded、left 信息。其它入 last、trackerid、numwant、compact、event 是可选的。这些参数的解释请看 BT协议规范。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s = ('%s&amp;uploaded=%s&amp;downloaded=%s&amp;left=%s' %<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (self.url, str(self.up()), str(self.down()), <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; str(self.amount_left())))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.last is not None:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s += '&amp;last=' + quote(str(self.last))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.trackerid is not None:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s += '&amp;trackerid=' + quote(str(self.trackerid))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.howmany() &gt;= self.maxpeers:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s += '&amp;numwant=0' <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s += '&amp;compact=1'<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if event != None:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; s += '&amp;event=' + ['started', 'completed', 'stopped'][event]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; set = SetOnce().set</p>
<p>&nbsp;&nbsp;# 函数中可以嵌套定义函数，这是 python 语法特别的地方。<br>&nbsp;&nbsp;# 调用 RawServer::add_task() 把 checkfail() 加入到任务队列中。在 timeout 时间之后，检查向 tracker 发的GET命令是否失败。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; def checkfail(self = self, set = set):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if set():<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.last_failed and self.upratefunc() &lt; 100 and self.downratefunc() &lt; 100:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.errorfunc('Problem connecting to tracker - timeout exceeded')<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.last_failed = True<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.sched(checkfail, self.timeout)</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # 创建一个线程，执行 Rerequester::rerequest()<br>&nbsp;&nbsp;# 可见，客户端与 tracker之间的通信是由单独的线程完成的<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Thread(target = self.rerequest, args = [s, set]).start()</p>
<p>⑥&nbsp;&nbsp;&nbsp; def rerequest(self, url, set):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try:<br>&nbsp;&nbsp;&nbsp;# 调用 urlopen() ，向 tracker 发送命令<br>h = urlopen(url)<br># read() 返回 tracker 的响应数据。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r = h.read()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; h.close()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if set():<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; def add(self = self, r = r):<br>self.last_failed = False<br># 从 tracker 响应回来的数据，由 postrequest() 处理。<br>self.postrequest(r)<br># externalsched() 同样是调用 RawServer::add_task()，这样，由主线程调用 postrequest() 函数，处理 tracker 响应的数据。（不要忘记，我们现在是处在一个单独的子线程中。）<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.externalsched(add, 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; except (IOError, error), e:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if set():<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; def fail(self = self, r = 'Problem connecting to tracker - ' + str(e)):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.last_failed:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.errorfunc(r)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.last_failed = True<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.externalsched(fail, 0)</p>
<p>⑦&nbsp;&nbsp;&nbsp; def postrequest(self, data):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try:<br>&nbsp;&nbsp;&nbsp;# 对服务器响应的数据解码。请参看BT协议规范。<br>r = bdecode(data)<br># 检查 peers 有效性？<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; check_peers(r)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if r.has_key('failure reason'):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.errorfunc('rejected by tracker - ' + r['failure reason'])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if r.has_key('warning message'):<br>self.errorfunc('warning from tracker - ' + r['warning message'])</p>
<p>&nbsp;# 根据 tracker 的响应，调整BT客户端的参数。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.announce_interval = r.get('interval', self.announce_interval)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.interval = r.get('min interval', self.interval)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.trackerid = r.get('tracker id', self.trackerid)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.last = r.get('last')</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;# 将其它下载者的信息保存到 peers 数组中。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p = r['peers']<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; peers = []<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if type(p) == type(''):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for x in xrange(0, len(p), 6):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ip = '.'.join([str(ord(i)) for i in p[x:x+4]])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; port = (ord(p[x+4]) &lt;&lt; 8) | ord(p[x+5])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; peers.append((ip, port, None))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for x in p:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; peers.append((x['ip'], x['port'], x.get('peer id')))<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ps = len(peers) + self.howmany()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ps &lt; self.maxpeers:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.doneflag.isSet():<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if r.get('num peers', 1000) - r.get('done peers', 0) &gt; ps * 1.2:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.last = None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if r.get('num peers', 1000) &gt; ps * 1.2:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.last = None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # 这里的 connect，即前面的Encoder::start_connection()，<br>&nbsp;&nbsp;&nbsp;&nbsp;# 至此，已经成功的完成了一次与 tracker 服务器的通信，并且从它那里获取了 peers 列表；下面就与这些 peers 挨个建立连接；至于建立连接的过程，就要追踪到 Encoder 类中了，且听下回分解。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for x in peers:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # x[0]:ip, x[1]:port, x[2]:id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connect((x[0], x[1]), x[2])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; except Error, e:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if data != '':<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.errorfunc('bad data from tracker - ' + str(e))</p>
<p>
<br>小结：<br>&nbsp;
这篇文章，我们重点分析了BT客户端与 tracker 服务器通信的过程。我们知道了，客户端要每隔一段时间，就去连接 tracker
一次，它是以一个单独的线程执行的。这个线程在接受到 tracker 的响应数据后，交给主线程（既主程序）来进行分析。主程序从响应数据中，获得
peers 列表。然后调用 Encoder::start_connection() 挨个与这些 peers 尝试建立连接。</p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94821.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:23 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94821.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之四：PiecePicker 类</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94818.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:22:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94818.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94818.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94818.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94818.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94818.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: PiecePicker 用于实现&#8220;片断选择算法&#8221;，片断选择算法在《Incentives Build Robustness in BitTorrent》一文中有介绍，我把相关内容列出来。&nbsp;BT的片断选择算法，综合下面几种策略。&nbsp;l&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbs...&nbsp;&nbsp;<a href='http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94818.html'>阅读全文</a><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94818.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:22 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94818.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之五：Encoder 类和 Connection 类</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94819.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:22:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94819.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94819.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94819.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94819.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94819.html</trackback:ping><description><![CDATA[<p>Encoder 是一种 Handler 类（关于 Handler类，请参看前面的分析文章）。它在 download.py 中被初始化。它与 Connection类一起，完成&#8220;BT对等连接&#8221;的建立，以及&#8220;BT对等协议&#8221;的分析。<br>为了有助于理解，我添加了一些用圆圈括起来的序号，建议你按照这个顺序去阅读。</p>
<p>class Connection:<br>&nbsp;&nbsp;&nbsp; ②def __init__(self, Encoder, connection, id, is_local):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.encoder = Encoder<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connection = connection<br>如果是本地发起连接，那么 id 是对方的 id，否则 id 为 None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.id = id<br>如果连接是由本地发起的，那么 is_local 为 True，否则为 False<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.locally_initiated = is_local<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.complete = False<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.closed = False<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.buffer = StringIO()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.next_len = 1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.next_func = self.read_header_len<br>如果连接是由本地主动发起建立的，那么需要向对方发送一个握手消息。（如果不是由本地主动发起的，那么就是被动建立的，那么不能在这里发送握手消息，而必须在分析完对方的握手消息之后，再去回应一个握手西消息，请看read_download_id() 中的处理。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.locally_initiated:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; connection.write(chr(len(protocol_name)) + protocol_name + <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (chr(0) * 8) + self.encoder.download_id)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.id is not None:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; connection.write(self.encoder.my_id)</p>
<p>&nbsp;&nbsp;&nbsp; def get_ip(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return self.connection.get_ip()</p>
<p>&nbsp;&nbsp;&nbsp; def get_id(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return self.id</p>
<p>&nbsp;&nbsp;&nbsp; def is_locally_initiated(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return self.locally_initiated</p>
<p>&nbsp;&nbsp;&nbsp; def is_flushed(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return self.connection.is_flushed()</p>
<p>&nbsp;&nbsp;&nbsp; ⑦def read_header_len(self, s):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if ord(s) != len(protocol_name):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return len(protocol_name), self.read_header</p>
<p>&nbsp;&nbsp;&nbsp; def read_header(self, s):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s != protocol_name:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 8, self.read_reserved</p>
<p>&nbsp;&nbsp;&nbsp; def read_reserved(self, s):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 20, self.read_download_id</p>
<p>&nbsp;&nbsp;&nbsp; def read_download_id(self, s):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s != self.encoder.download_id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None<br>这一步很重要， 如果连接是由对方发起的，那么，给对方发送一个握手消息。为什么不在读完了 peer id 之后才发送这个消息了？这是因为 peer id 是可选的，所以只要分析完 download id 之后，就要立即发送握手消息。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not self.locally_initiated:<br>self.connection.write(chr(len(protocol_name)) + <br>protocol_name + <br>(chr(0) * 8) +<br>self.encoder.download_id + self.encoder.my_id)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 20, self.read_peer_id</p>
<p>&nbsp;&nbsp;&nbsp; def read_peer_id(self, s):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not self.id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果 peer id 是自己，那么出错了<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s == self.encoder.my_id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for v in self.encoder.connections.s():<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果已经跟该 peer 建立了连接了，那么也出错了<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s and v.id == s:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.id = s<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.locally_initiated:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connection.write(self.encoder.my_id)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.encoder.everinc = True<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果 peer id 和 xxx 不符，那么出错了。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s != self.id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None</p>
<p>&#8220;BT对等连接&#8221;的握手过程正式宣告完成，此后，双方就可以通过这个连接互相发送消息了。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.complete = True</p>
<p>调用Connecter::connection_made()，这个函数的意义，我们到分析 Connecter 类的时候，再记得分析。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.encoder.connecter.connection_made(self)</p>
<p>下面进入 BT 消息的处理过程。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 4, self.read_len</p>
<p>&nbsp;&nbsp;&nbsp; def read_len(self, s):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; l = toint(s)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if l &gt; self.encoder.max_len:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return l, self.read_message</p>
<p>消息处理，交给了 Connecter::got_message()，所以下一篇我们要分析 Connecter 类。<br>&nbsp;&nbsp;&nbsp; def read_message(self, s):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if s != '':<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.encoder.connecter.got_message(self, s)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 4, self.read_len</p>
<p>&nbsp;&nbsp;&nbsp; def read_dead(self, s):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return None</p>
<p>&nbsp;&nbsp;&nbsp; def close(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if not self.closed:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connection.close()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.sever()</p>
<p>&nbsp;&nbsp;&nbsp; def sever(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.closed = True<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; del self.encoder.connections[self.connection]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.complete:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.encoder.connecter.connection_lost(self)</p>
<p>&nbsp;&nbsp;&nbsp; def send_message(self, message):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connection.write(tobinary(len(message)) + message)</p>
<p>
<br>⑤在 Encoder::data_came_in() 中调用下面这个函数，表示某个连接上有数据可读。如果有数据可读，那么我们就按照 BT 对等协议的规范来进行分析。。。<br>def data_came_in(self, s):</p>
<p>进入协议分析循环。。。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while True:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if self.closed:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return</p>
<p>self.next_len表示按照BT对等协议规范，下一段要分析的数据的长度<br>self.buffer.tell() 表示缓冲区中剩下数据的长度<br>那么 i 就表示：为了完成接下来的协议分析，还需要多少数据？<br>i = self.next_len - self.buffer.tell()<br>如果 i 大于所读到的数据的长度，那表示数据还没有读够，无法继续协议分析，需要等读到足够多的数据才能继续，所以只能退出。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if i &gt; len(s):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.buffer.write(s)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br>否则表示这次读到的数据已经足够完成一步协议分析。<br>只取满足这一步协议分析的数据放入 buffer 中（因为 buffer中可能还有上一步协议分析后留下的一些数据，要加在一起），剩下的数据保留在 s 中。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.buffer.write(s[:i])<br>s = s[i:]</p>
<p>从 buffer 中取出数据，这些数据就是这一步协议分析所需要的数据。然后把 buffer 清空。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m = self.buffer.get()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.buffer.reset()<br>self.buffer.truncate()</p>
<p>next_func 就是用于这一步协议分析的函数。<br>返回的 x 是一个二元组，包含了下一步协议分析的数据长度和协议分析函数。这样，就形成了一个协议分析循环。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; x = self.next_func(m)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; except:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.next_len, self.next_func = 1, self.read_dead<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; raise<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if x is None:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.close()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br>从 x 中分解出 next_len和 next_func。<br>self.next_len, self.next_func = x<br>⑥那么BT对等协议分析的第一步是什么了？<br>请看初始化函数：<br>self.next_len = 1<br>self.next_func = self.read_header_len<br>显然，第一步协议分析是由 read_header_len() 来完成的。<br>在BT源码中，有多处采用了这种协议分析的处理方式。</p>
<p>class Encoder:<br>&nbsp;&nbsp;&nbsp; def __init__(self, connecter, raw_server, my_id, max_len,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; schedulefunc, keepalive_delay, download_id, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; max_initiate = 40):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.raw_server = raw_server<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connecter = connecter<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.my_id = my_id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.max_len = max_len<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.schedulefunc = schedulefunc<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.keepalive_delay = keepalive_delay<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.download_id = download_id<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 最大发起的连接数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.max_initiate = max_initiate<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.everinc = False<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connections = {}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.spares = []<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; schedulefunc(self.send_keepalives, keepalive_delay)</p>
<p>为了保持连接不因为超时而被关闭，所以可能需要随机的发送一些空消息，它的目的纯粹是为了保证连接的&#8220;活力&#8221;<br>&nbsp;&nbsp;&nbsp; def send_keepalives(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.schedulefunc(self.send_keepalives, self.keepalive_delay)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for c in self.connections.s():<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if c.complete:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c.send_message('')</p>
<p>③主动向对方发起一个连接，这个函数什么时候调用？<br>请看 download.py 中 Rerequester 类的初始化函数，其中传递的一个参数是 encoder.start_connection。<br>再看 Rerequester.py 中，Rerequester::postrequest() 的最后，<br>for x in peers:<br>self.connect((x[0], x[1]), x[2])<br>这里调用的 connect() 就是初始化的时候传递进来的 encoder.start_connection，也就是下面这个函数了。<br>也就是说，当客户端从 tracker 服务器那里获取了 peers 列表之后，就逐一向这些 peers 主动发起连接。<br>&nbsp;&nbsp;&nbsp; def start_connection(self, dns, id):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 跟自己不用建立连接。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if id == self.my_id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果已经与对方建立起连接，也不再建立连接<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for v in self.connections.s():<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if v.id == id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 如果当前连接数，已经超过设定的&#8220;最大发起连接数&#8221;，那么就暂时不建立连接。<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if len(self.connections) &gt;= self.max_initiate:<br>如果空闲连接数还小于 &#8220;最大发起连接数&#8221;，那么把对方的 ip 先放到spares中，等以后网络稍微空闲一点的时候，再从 spares 中取出来，实际去建立连接。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if len(self.spares) &lt; self.max_initiate and dns not in self.spares:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.spares.append(dns)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; try:<br>调用 RawServer::start_connection 与对方建立TCP连接<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = self.raw_server.start_connection(dns)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>创建 Connection 对象，加入到 connections 字典中，注意，最后一个参数是 True，表示是这个连接是由本地主动发起的。这样，在 Connection 的初始化函数中，会与对方进行 BT 对等协议的握手。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connections[c] = Connection(self, c, id, True)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; except socketerror:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pass<br>&nbsp;&nbsp;&nbsp; <br>这个内部函数好像没有用到<br>&nbsp;&nbsp;&nbsp; def _start_connection(self, dns, id):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; def foo(self=self, dns=dns, id=id):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.start_connection(dns, id)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.schedulefunc(foo, 0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; def got_id(self, connection):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for v in self.connections.s():<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if connection is not v and connection.id == v.id:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; connection.close()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connecter.connection_made(connection)</p>
<p>&nbsp;&nbsp;&nbsp; def ever_got_incoming(self):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return self.everinc</p>
<p>①在 RawServer 中，当从外部发起的一个TCP成功建立后，调用此函数。<br>这里传递进来的参数 connection 是 SingleSocket 类型<br>def external_connection_made(self, connection):<br>&nbsp;创建一个 Connection 对象，加入到 connections 字典中。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connections[connection] = Connection(self, connection, None, False)</p>
<p>&nbsp;&nbsp;&nbsp; def connection_flushed(self, connection):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; c = self.connections[connection]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if c.complete:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connecter.connection_flushed(c)</p>
<p>关闭连接的时候调用此函数<br>&nbsp;&nbsp;&nbsp; def connection_lost(self, connection):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connections[connection].sever()<br>关闭一个连接之后，连接数量可能就没有达到&#8220;最大连接数&#8221;，所以如果 spares 中有一些等待建立的 ip ，现在可以取出来，主动向对方发起连接。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while len(self.connections) &lt; self.max_initiate and self.spares:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.start_connection(self.spares.pop(), None)</p>
④某个连接上（无论该连接上主动建立还是被动建立的）有数据可读的时候，调用此函数。在 RawServer 中被调用。转而去调 Connection::data_came_in()。<br>&nbsp;&nbsp;&nbsp; def data_came_in(self, connection, data):<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; self.connections[connection].data_came_in(data)  <img src ="http://www.blogjava.net/kuxiaoku/aggbug/94819.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:22 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94819.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之六：客户端的主程序</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94820.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:22:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94820.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94820.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94820.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94820.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94820.html</trackback:ping><description><![CDATA[<p>前言：<br>&nbsp;自从7月份写完&#8220;客户端源码分析之五：Encoder 与 Connection 类&#8221;后，我就停止了继续对BT源码的分析。原因很多，最主要的还是懒惰吧。临到岁末，终于下定决心，无论如何，要完成这一系列的文章，对自己也算有个交待。<br>&nbsp;
前面的几篇文章，都是深入到源码的某一部分细节之中，虽然很清晰，但无助于读者对整体构架的把握（其实我自己当时也比较糊涂）。这一次重新开始读源码，重
点顺着几条线索往下读，感觉原来杂乱无序的代码突然变得清晰明了起来，呵呵，其实不是代码杂乱，只是我原来的阅读思路比较混乱的缘故。<br>&nbsp;读者可以抛开前面几篇文章，从这一篇开始往下读，希望能有所收获。<br>&nbsp;源码分析类的文章，比较难写，小马哥毕竟没有候捷的春秋笔法（甚至连作文都写不好），能把一个深奥复杂的STL源码分析的如此透彻，所以只好尝试一些傻办法，这些傻办法包括：<br>在源码上直接加注释。但我只对重点的部分增加一些注释，细节的东西就不再深究，这样有助于写作的进度。<br>用①、②、③这样的序号来指引代码的阅读线索<br>用不同颜色标注出代码中值得注意的地方。<br>你有什么好的建议，欢迎提出来。<br>好，我们进入正题。</p>
<p>BT客户端的 main() 函数：<br>&nbsp;C
和c++的可执行程序，通常都有一个 main() 函数，一切从这里开始。而 python 这种解释性语言，并没有 main()
函数的概念。你传递给解释器一个 .py
扩展名的python源码文件，解释器就会顺序去解释执行这个文件中的代码。所以，BT客户端的执行，是从最先被 python
解释器解释执行的那个文件开始的。这个文件应该是 btdownloadheadless.py，至于谁又来通知让 python
解释器执行这个文件的，我们以后再讨论。</p>
<p>【btdownloadheadless.py】（在BT源码的根目录下）<br>②def run(params):<br>&nbsp;&nbsp;&nbsp; try:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; import curses<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; curses.initscr()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cols = curses.COLS<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; # endwin() De-initialize the library, and return terminal to normal status<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; curses.endwin()<br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; except:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cols = 80<br>h = HeadlessDisplayer()<br># 调用 download.py 中的 download 函数，我们的重点将转移到 download.py 文件，注意，该文件及以后要分析的代码都在 BT源码的 BitTorrent 子目录下。<br>③&nbsp;&nbsp;&nbsp; download(params, h.chooseFile, h.display, h.finished, h.error, Event(), cols, h.newpath)<br>&nbsp;&nbsp;&nbsp; if not h.done:<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; h.failed()</p>
<p>&nbsp;# 所有的 python的入门书籍中，都会告诉你下面这段代码的含义。这就是最先被解释执行的代码所在。<br>①if __name__ == '__main__':<br>run(argv[1:])</p>
<p>
<br>【download.py】<br>这个文件中，最主要的就是 download() 函数，客户端所有的一切都是从这里开始。这个函数代码比较多，我必须挑出其中最重要的代码，其它的部分，后续再分析。</p>
<p>④rawserver = RawServer(doneflag, config['timeout_check_interval'], config['timeout'], errorfunc <br>= errorfunc, maxconnects = config['max_allow_in'])</p>
<p># 选择一个可用的端口作为监听端口<br>for listen_port in xrange(config['minport'], config['maxport'] + 1):<br>try:<br>&nbsp;&nbsp;&nbsp; &nbsp;rawserver.bind(listen_port, config['bind'])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; break<br>except socketerror, e:<br>&nbsp;&nbsp;&nbsp; &nbsp;pass<br>else:<br>errorfunc("Couldn't listen - " + str(e))<br>return</p>
<p>encoder = Encoder(connecter, rawserver, <br>myid, config['max_message_length'], rawserver.add_task, <br>config['keepalive_interval'], infohash, config['max_initiate'])</p>
<p>rawserver.listen_forever(encoder)</p>
<p>客
户端的核心类就是 RawServer，这个类我在&#8220;服务器端源码分析&#8221;（可在论坛中找到）文章中分析过它的作用，这里不再赘述。通过调用它的
listen_forever() 函数，BT
客户端就进入了一个循环之中。此后，所有的下载、上传、文件存储以及其它工作都在这一次次的循环之中完成。<br>注意到，在进入循环之前，调用了
RawServer::bind() 函数，BT客户端会选择一个可用的端口，然后通过监听这个端口，从而可以接受其它 peer
的连接请求（也就是BT对等连接）。这个端口可以称为&#8220;监听端口&#8221;。如果你有网络服务器的编程经验，从它的循环处理逻辑、监听端口的处理可以知道，这显然
就是一个典型的网络服务器的表现。所以，我在以前的文章中曾经说过，BT客户端同时也是一个服务器。<br>一旦在监听端口上有某个peer发来连接请
求，BT客户端会创建一个新的端口，这个端口用来与请求者建立连接；有几个peer请求连接，就会创建几个端口。我们可以说这是一些&#8220;被动端口&#8221;。在循环
的处理过程中，BT客户端还会根据情况，向其它
peer主动发出连接请求，每个连接也需要一个端口，这些端口可以称为&#8220;主动端口&#8221;。其实，这些都是网络编程中最基本的概念，不清楚的朋友还是首先要去看
看这方面的书籍。<br>这样，就有了一个&#8220;监听端口&#8221;，几个&#8220;被动端口&#8221;和几个&#8220;主动端口&#8221;，BT客户端通过 select()
函数来监视这些端口，一旦&#8220;监听端口&#8221;或者&#8220;被动端口&#8221;上有数据到来，或者有数据需要从&#8220;主动端口&#8221;上发送出去，那么select()
都可以及时感知并进行处理（其实是一个轮询的过程），这就称为&#8220;I/O多路复用&#8221;。如果&#8220;被动端口&#8221;上有数据到来，那么这是BT对等连接的协议数据，需要
按照BT对等协议进行分析，并根据分析结果进行相应处理，这个分析处理的工作就是由 Encoder 类来完成的。所以在
listen_forever() 函数中传递的参数就是一个 Encoder 类对象。关于对 Encoder
类的分析，以后分析到BT对等协议的处理的时候再说。</p>
<p>小结：<br>&nbsp;通过这篇文章，我们了解了BT客户端是从哪里执行的，相应的源码在哪
里。同时我们知道了BT客户端是以一个服务器循环的形式运行的，它需要监听一个端口，用于接受其它peers的连接请求；在循环过程中，它通过
select 来实现&#8220;I/O多路复用&#8221;；并由 Encoder 类来完成BT对等协议的分析处理。</p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94820.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:22 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94820.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之三：StorageWrapper类</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94817.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:21:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94817.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94817.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94817.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94817.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94817.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: StorageWrapper 的作用：把文件片断进一步切割为子片断，并且为这些子片断发送 request消息。在获得子片断后，将数据写入磁盘。请结合 Storage 类的分析来看。&nbsp;几点说明：1、&nbsp; 为了获取传输性能，BT把文件片断切割为多个子片断。2、&nbsp; BT为获取一...&nbsp;&nbsp;<a href='http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94817.html'>阅读全文</a><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94817.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:21 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94817.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之二： Storage 类</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94816.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:20:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94816.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94816.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94816.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94816.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94816.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 作者：小马哥日期：2004-6-28&nbsp;由于 Storage 类比较简单，我直接在源码基础上进行注释。掌握Storage，为进一步分析 StorageWrapper 类打下基础。&nbsp;几点说明：1、&nbsp; Storage 类...&nbsp;&nbsp;<a href='http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94816.html'>阅读全文</a><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94816.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:20 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94816.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之一：总述</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94815.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:19:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94815.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94815.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94815.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94815.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94815.html</trackback:ping><description><![CDATA[<p>BT客户端源码分析之一：总述<br>作者：小马哥<br>日期：2004-6-24</p>
<p>概述：<br>相对于 tracker 服务器来说，BT客户端要复杂的多，Bram Cohen 花了一年 full time 的时间来完成 BT，我估计其中大部分时间是用在 BT 客户端的实现和调试上了。<br>由
于 BT 客户端涉及的代码比较多，我不能再象分析 tracker
服务器那样，走上来就深入到细节之中去，那样的话，我写的晕晕糊糊，大家看起来也不知所云。所以第一篇文章先来谈谈客户端的功能、相关协议，以及客户端的
总体架构和相关的类的层次结构。这样，从整体上把握之后，大家在自己分析代码的过程中，就能做到胸有成竹。</p>
<p>客户端的功能：</p>
<p>不看代码，只根据 BT 的相关原理，大致可以推测，客户端需要完成以下功能：<br>1、解析 torrent 文件，获取要下载的文件的详细信息，并在磁盘上创建空文件。<br>2、与 tracker服务器 建立连接，并交互消息。<br>3、根据从 tracker 得到的信息，跟其它 peers 建立连接，并下载需要的文件片断<br>4、监听某端口，等待其它peers 的连接，并提供文件片断的上传。</p>
<p>相关协议：<br>对客户端来说，它需要处理两种协议：<br>1、与 tracker 服务器交互的 track HTTP协议。<br>2、与其它 peers 交互的 BT 对等协议。</p>
<p>总体架构：<br>从总体上来看，BT客户端实际是以一个服务器的形式在运行。这一点似乎有些难以理解，但确实是这样。<br>为什么是一个服务器了？<br>客
户端的主要功能是下载文件，但作为一种P2P软件，同时它必须提供上传服务，也就是它必须守候在某一个端口上，等待其它peers
的连接请求。从这一点上来说，它必须以一个服务器的形式运行。我们在后面实际分析代码的时候，可以看到，客户端复用了 RawServer
类用来实现网络服务器。<br>客户端的代码，是从 download.py
开始的，首先完成功能1，之后就进入服务器循环，在每一次循环过程中，完成功能 2、3、4。其中，Rerequester 类负责完成功能2，它通过
RawServer::add_task()，向 RawServer 添加自己的任务函数，这个任务函数，每隔一段时间与 tracker
服务器进行通信。而Encoder、Connecter 等多个类组合在一起，完成功能3和4。</p>
<p>类层次结构：</p>
<p>BT 客户端涉及的类比较多，我首先大致描述一下这些类的功能，然后给出它们的一个层次结构。</p>
<p>1、RawServer：负责实现网络服务器</p>
<p>2、Rerequester：负责和 tracker 通信。它调用 RawServer::add_task() ，向 RawServer 添加自己的任务函数 Rerequester::c()。</p>
<p>3、Encoder：一种 Handler类（在分析 tracker 服务器时候提到），负责处理与其它peers建立连接和以及对读取的数据按照BT对等协议进行分析。</p>
<p>Encoder
类在Encrypter.py中，该文件中，还有一个 Connection 类，而在 Connecter.py 文件中，也有一个
Connection 类，这两个同名的 Connection 类有些蹊跷，为了区分，我把它们重新命名为 E-Connection 和
C-Connection。</p>
<p>
<br>3.1、E-Connection：负责 TCP 层次上的连接工作<br>这两个
Connection 是有区别的，这是因为BT对等协议需要在两个层次上建立连接。首先是 TCP 层次上的连接，也就是经过 TCP
的三次握手之后，建立连接，这个连接由 E-Connection 来管理。在 Encoder::
external_connection_made() 函数中可以看到，一旦有外部连接到来，则创建一个 E-Connection 类。</p>
<p>3.2、C-Connection：管理对等协议层次上的连接。<br>在 TCP 连接之上，是 BT对等协议的连接，它需要经过BT对等协议的两次&#8220;握手&#8221;，握手的细节大家去看BT对等协议。过程是这样的：<br>为了便于述说，我们假设一个BT客户端为 A，另一个客户端为 X。<br>如
果是X主动向A发起连接，那么在TCP连接建立之后，A立刻利用这个连接向X发送BT对等协议的&#8220;握手&#8221;消息。同样，X在连接一旦建立之后，向
A发送BT对等协议的&#8220;握手&#8221;消息。A一旦接收到X的&#8220;握手&#8221;消息，那么它就认为&#8220;握手&#8221;成功，建立了BT对等协议层次上的连接。我把它叫做&#8220;对等连
接&#8221;。A 发送了一个消息，同时接收了一个消息，所以这个握手过程是两次&#8220;握手&#8221;。<br>同样，对X 来说，因为连接是它主动发起的，所以它在发送完&#8220;握手&#8221;消息之后，就等待A的&#8220;握手&#8221;消息，如果收到，那么它也认为&#8220;对等连接&#8221;建立了。<br>一旦&#8220;对等连接&#8221;建立之后，双方就可以通过这个连接传递消息了。</p>
<p>这样，原来我所疑惑的一个问题也就有了答案。就是：如果 X 需要从 A 这里下载数据，那么它会同 A 建立一个连接。假如 A 又希望从 X 那里下载数据，它需不需要重新向 X 发起另外一个连接了？答案显然是不用，它会利用已有的一条连接。</p>
<p>也就是说，不管是X主动向A发起的连接，还是 A 主动向 X发起的连接，一旦建立之后，它们的效果是一样的。这个同我们平时做 C/S结构的网络开发是有区别的。</p>
<p>我们可以看到在 E-Connection的初始化函数中，会主动连接的另一方发送&#8220;握手&#8221;消息，在 E-Connection::data_came_in() 中，会首先对对方的&#8220;握手&#8221;消息进行处理。这正是我上面所描述的情形。<br>在 E-Connection::read_peer_id() 中，是对&#8220;握手&#8221;消息的最后一项 peer id进行处理，一旦正确无误，那么就认为&#8220;对等连接&#8221;完成，</p>
<p>self.encoder.connecter.connection_made(self)</p>
<p>在 Connecter::connection_made() 函数中，就创建了管理&#8220;对等连接&#8221;的 C-Connectinon类。所以，更高一层的&#8220;对等连接&#8221;是由 C-Connection 来管理的。</p>
<p>3.3、Connecter：连接器，管理下载、上传、阻塞、片断选择、读写磁盘等等。</p>
<p>下载和上传不是孤立的，它们之间相互影响。下载需要有片断选择算法，上传的时候要考虑阻塞，片断下载之后，要写到磁盘上。上传的时候，也需要从磁盘读取。<br>这些任务，是由 Connecter 来统一调度的。</p>
<p>
<br>类层次结构，我用缩进来表示一种包含关系。</p>
<p>Encoder:<br>&nbsp;&nbsp;&nbsp; E-Connection<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; C-Connection<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Upload<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SingleDownloader<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Connecter<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Choker：负责阻塞的管理<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Downloader：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SingleDownloader<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Picker：片断选择策略<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; StorageWrapper：</p>
<p>&nbsp;</p>
<p>先写这些吧，有什么我再补充进来。</p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94815.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:19 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94815.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tracker 服务器源码分析之二：RawServer类</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94812.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:18:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94812.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94812.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94812.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94812.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94812.html</trackback:ping><description><![CDATA[<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">这篇文章，我们来分析</span>
<span lang="EN"> RawServer </span>
<span style="font-family: 宋体;">以及一些相关的类。</span>
<span lang="EN">RawServer </span>
<span style="font-family: 宋体;">类的实现代码，在</span>
<span lang="EN"> BitTorrent </span>
<span style="font-family: 宋体;">子目录的</span>
<span lang="EN">RawServer.py </span>
<span style="font-family: 宋体;">中</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">RawServer </span>
<span style="font-family: 宋体;">这个类的作用是实现一个网络服务器。关于网络编程的知识，《</span>
<span lang="EN">unix</span>
<span style="font-family: 宋体;">网络编程：卷</span>
<span lang="EN">1</span>
<span style="font-family: 宋体;">》是最经典的书籍，你如果对这块不了解，建议抽时间看看这本书。</span>
<span lang="EN">RawServer </span>
<span style="font-family: 宋体;">实现的是一种事件多路复用、非阻塞的网络模型。它使用的是</span>
<span lang="EN"> poll() </span>
<span style="font-family: 宋体;">（而不是我们常见的</span>
<span lang="EN">select()</span>
<span style="font-family: 宋体;">，关于</span>
<span lang="EN"> poll</span>
<span style="font-family: 宋体;">和</span>
<span lang="EN">select</span>
<span style="font-family: 宋体;">的比较，也在《</span>
<span lang="EN">unix</span>
<span style="font-family: 宋体;">网络编程：卷</span>
<span lang="EN">1</span>
<span style="font-family: 宋体;">》中有介绍）函数，处理过程大致是这样的：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">首先创建一个监听</span>
<span lang="EN"> socket</span>
<span style="font-family: 宋体;">，然后将这个</span>
<span lang="EN"> socket </span>
<span style="font-family: 宋体;">加入</span>
<span lang="EN"> poll </span>
<span style="font-family: 宋体;">的事件源；</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">随后进入服务处理循环，即：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">调用</span>
<span lang="EN"> poll() </span>
<span style="font-family: 宋体;">函数，这个函数会阻塞，直到网络上有某些事件发生或者超时才返回给调用者；</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">在</span>
<span lang="EN"> poll()</span>
<span style="font-family: 宋体;">返回之后，先检查一下是否有没有处理的任务，如果有，那么先完成这些任务。然后根据事件类型进行处理。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">如果是连接请求（监听</span>
<span lang="EN"> socket</span>
<span style="font-family: 宋体;">上的</span>
<span lang="EN">POLLIN</span>
<span style="font-family: 宋体;">事件）到来，它</span>
<span lang="EN"> accept</span>
<span style="font-family: 宋体;">这个请求，如果</span>
<span lang="EN"> accept </span>
<span style="font-family: 宋体;">成功，那么就和一个</span>
<span lang="EN"> client</span>
<span style="font-family: 宋体;">建立了连接，于是将</span>
<span lang="EN"> accept() </span>
<span style="font-family: 宋体;">新创建的</span>
<span lang="EN"> socket </span>
<span style="font-family: 宋体;">加入</span>
<span lang="EN"> poll </span>
<span style="font-family: 宋体;">的事件源；</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">如果在已经建立的连接上（连接</span>
<span lang="EN">socket</span>
<span style="font-family: 宋体;">上的</span>
<span lang="EN">POLLIN</span>
<span style="font-family: 宋体;">事件），有数据可读，那么将数据从</span>
<span lang="EN"> client </span>
<span style="font-family: 宋体;">端读过来，做进一步处理；</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">如果已经建立的连接已经准备好（连接</span>
<span lang="EN">socket</span>
<span style="font-family: 宋体;">上的</span>
<span lang="EN">POLLOUT</span>
<span style="font-family: 宋体;">事件），可以发送数据，则检查是否有数据需要发送，如果有，那么发送数据给</span>
<span lang="EN"> client </span>
<span style="font-family: 宋体;">端。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">（所以，</span>
<span lang="EN">tracker</span>
<span style="font-family: 宋体;">是一个单进程的服务器，并没有用到线程。）</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">Bram Cohen </span>
<span style="font-family: 宋体;">认为软件的可维护性非常重要，使代码易于维护的重要一条就是设计可重用的类，</span>
<span lang="EN">RawServer </span>
<span style="font-family: 宋体;">在设计的时候，充分考虑到了可重用性，集中表现在两个地方：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 39pt; text-indent: -18pt;">
<span lang="EN">1、<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp; </span></span>
<span style="font-family: 宋体;">将网络</span>
<span lang="EN"> I/O </span>
<span style="font-family: 宋体;">和数据分析处理分离。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 39pt;">
<span style="font-family: 宋体;">网络服务器的事件多路复用、网络</span>
<span lang="EN">I/O </span>
<span style="font-family: 宋体;">部分通常是固定不变的，而数据在读取之后，进行分析处理的过程则是可变的。</span>
<span lang="EN">RawServer </span>
<span style="font-family: 宋体;">将可变的数据处理工作，交给另外一个抽象的类</span>
<span lang="EN"> Handler </span>
<span style="font-family: 宋体;">（实际上并没有这么一个类）来处理。比如，在</span>
<span lang="EN"> tracker </span>
<span style="font-family: 宋体;">服务器的实现中，具体使用的就是</span>
<span lang="EN"> HTTPHandler </span>
<span style="font-family: 宋体;">类，而在</span>
<span style="font-family: 宋体;">以后将要分析的</span>
<span lang="EN"> BT client </span>
<span style="font-family: 宋体;">实现代码中，用到的具体的</span>
<span lang="EN">Handler </span>
<span style="font-family: 宋体;">是</span>
<span lang="EN"> Encoder </span>
<span style="font-family: 宋体;">类。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 39pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 39pt; text-indent: -18pt;">
<span lang="EN">2、<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp; </span></span>
<span style="font-family: 宋体;">采用任务队列来抽象出任务处理的过程。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 39pt;">
<span lang="EN">RawServer</span>
<span style="font-family: 宋体;">维护了一个任务队列</span>
<span lang="EN">unscheduled_tasks</span>
<span style="font-family: 宋体;">（实际是一个二元组的</span>
<span lang="EN">list</span>
<span style="font-family: 宋体;">，二元组的第一项是一个函数，第二项是超时时间）。在初始化的时候，首先向这个队列中加入一个任务：</span>
<span lang="EN">scan_for_timeouts()</span>
<span style="font-family: 宋体;">，这样，每隔一段时间，服务器就会去检查一下是否有连接超时。如果有其它</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">RawServer</span>
<span style="font-family: 宋体;">的成员函数中，对外暴露的有：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: -21pt;">
<span style="font-family: wingdings;" lang="EN">u<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span lang="EN">__init__</span>
<span style="font-family: 宋体;">：（初始化函数）</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: -21pt;">
<span style="font-family: wingdings;" lang="EN">u<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span lang="EN">add_task()</span>
<span style="font-family: 宋体;">：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">在任务列表中增加一项任务（一个任务是一个函数以及一个指定的超时时间的组合）</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: -21pt;">
<span style="font-family: wingdings;" lang="EN">u<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span lang="EN">bind()</span>
<span style="font-family: 宋体;">：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">首先创建一个</span>
<span lang="EN">socket</span>
<span style="font-family: 宋体;">，然后设置</span>
<span lang="EN">socket</span>
<span style="font-family: 宋体;">的属性：</span>
<span lang="EN">SO_REUSEADDR</span>
<span style="font-family: 宋体;">和</span>
<span lang="EN">IP_TOS,</span>
<span style="font-family: 宋体;">，这两个属性的具体含义请参考《</span>
<span lang="EN">unix</span>
<span style="font-family: 宋体;">网络编程：卷</span>
<span lang="EN">1</span>
<span style="font-family: 宋体;">》，另外还将</span>
<span lang="EN"> socket </span>
<span style="font-family: 宋体;">设置为非阻塞的。相对于阻塞的</span>
<span lang="EN"> socket</span>
<span style="font-family: 宋体;">来说，非阻塞的</span>
<span lang="EN"> socket </span>
<span style="font-family: 宋体;">在网络</span>
<span lang="EN"> I/O </span>
<span style="font-family: 宋体;">性能上要提高许多，但是与此同时，编程的复杂度也要提高一些。象</span>
<span lang="EN"> tracker</span>
<span style="font-family: 宋体;">这种可能同时要处理成千上万个并发连接的服务器，只能采用非阻塞的</span>
<span lang="EN">socket</span>
<span style="font-family: 宋体;">。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">然后将该</span>
<span lang="EN"> socket</span>
<span style="font-family: 宋体;">和指定</span>
<span lang="EN">ip</span>
<span style="font-family: 宋体;">已经端口绑定；</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">最后把这个</span>
<span lang="EN">socket </span>
<span style="font-family: 宋体;">加入</span>
<span lang="EN"> poll</span>
<span style="font-family: 宋体;">的事件源。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: -21pt;">
<span style="font-family: wingdings;" lang="EN">u<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span lang="EN">start_connection()</span>
<span style="font-family: 宋体;">：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">对外主动建立一个连接，这个函数在处理</span>
<span lang="EN">NAT</span>
<span style="font-family: 宋体;">穿越的时候用到了，我们后面分析到</span>
<span lang="EN"> NAT</span>
<span style="font-family: 宋体;">穿越的时候，再具体讲解。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: -21pt;">
<span style="font-family: wingdings;" lang="EN">u<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span lang="EN">listen_forever()</span>
<span style="font-family: 宋体;">：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">这个函数的功能就是实现了我在前面描述的网络服务器的处理过程。我们看到，它唯一的参数是</span>
<span lang="EN">handler</span>
<span style="font-family: 宋体;">，</span>
<span lang="EN">handler</span>
<span style="font-family: 宋体;">的作用就是封装了对数据的具体处理。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">listen_forever()</span>
<span style="font-family: 宋体;">把对网络事件的处理过程，交给了</span>
<span lang="EN"> handle_events()</span>
<span style="font-family: 宋体;">。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">其它函数，包括</span>
<span lang="EN">handle_events()</span>
<span style="font-family: 宋体;">，都是内部函数（也就是外部不会直接来调用这些函数）。</span>
<span lang="EN">Python</span>
<span style="font-family: 宋体;">没有</span>
<span lang="EN">c++</span>
<span style="font-family: 宋体;">那样</span>
<span lang="EN"> public</span>
<span style="font-family: 宋体;">、</span>
<span lang="EN">protected</span>
<span style="font-family: 宋体;">、</span>
<span lang="EN">private </span>
<span style="font-family: 宋体;">这样的保护机制，</span>
<span lang="EN">python</span>
<span style="font-family: 宋体;">类的内部函数命名的惯例是以下划线开始，例如</span>
<span lang="EN"> RawServer </span>
<span style="font-family: 宋体;">中的</span>
<span lang="EN"> _close_dead()</span>
<span style="font-family: 宋体;">等。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: -21pt;">
<span style="font-family: wingdings;" lang="EN">u<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span lang="EN">handle_events()</span>
<span style="font-family: 宋体;">：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">事件处理过程，主要是根据三种不同的网络事件分别处理，一是连接事件，二是读事件、三是写事件。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span lang="EN">if sock == self.server.fileno()</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">这段代码判断发生事件的</span>
<span lang="EN">socket</span>
<span style="font-family: 宋体;">是否是监听</span>
<span lang="EN"> socket</span>
<span style="font-family: 宋体;">，如果是，那么说明是连接事件。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">连接事件的处理：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">通过</span>
<span lang="EN"> accept </span>
<span style="font-family: 宋体;">来接受连接，并将新建立的</span>
<span lang="EN"> socket </span>
<span style="font-family: 宋体;">设置为非阻塞。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">判断当前连接数是否已经达到了最大值（为了限制并发连接的数目，在初始化</span>
<span lang="EN"> RawServer</span>
<span style="font-family: 宋体;">的时候，需要指定最大连接数目），如果已经达到最大值，那么关闭这个新建的连接。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">否则，根据新的</span>
<span lang="EN"> socket </span>
<span style="font-family: 宋体;">创建一个</span>
<span lang="EN"> SingleSocket </span>
<span style="font-family: 宋体;">对象，（</span>
<span lang="EN">SingleSocket </span>
<span style="font-family: 宋体;">封装了对</span>
<span lang="EN"> socket</span>
<span style="font-family: 宋体;">的操作。）将这个对象加入内部的列表</span>
<span lang="EN">single_sockets</span>
<span style="font-family: 宋体;">中，以备后用。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">将这个新</span>
<span lang="EN"> socket</span>
<span style="font-family: 宋体;">加入</span>
<span lang="EN"> poll </span>
<span style="font-family: 宋体;">的事件源</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">最后，调用</span>
<span lang="EN"> Handler </span>
<span style="font-family: 宋体;">的</span>
<span lang="EN">external_connection_made() </span>
<span style="font-family: 宋体;">函数，关于这个函数，在后面分析</span>
<span lang="EN"> HTTPHandler </span>
<span style="font-family: 宋体;">时再讨论。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span lang="EN">if (event &amp; POLLIN) != 0:</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">这段代码判断是否是读事件</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">读事件的处理：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">首先刷新一下连接的最后更新时间</span>
<span style="font-family: 宋体;">（</span>
<span lang="EN">last_hit</span>
<span style="font-family: 宋体;">）。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">然后读取数据；</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">如果什么也没读到，那么说明连接被关闭了（在网络编程中，如果一个连接正常的被关闭，那么，也会触发读事件，只不过什么也读不到）</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">否则，调用</span>
<span lang="EN"> Handler</span>
<span style="font-family: 宋体;">的</span>
<span lang="EN"> data_came_in() </span>
<span style="font-family: 宋体;">函数来处理读到的数据。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span lang="EN">if (event &amp; POLLOUT) != 0 and s.socket is not None and not s.is_flushed():</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">这段代码判断是否是写事件，而且确实有数据需要发送。在一个连接可以写的时候，就会发生写事件。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">写事件的处理：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">实际代码是在</span>
<span lang="EN"> SingleSocket</span>
<span style="font-family: 宋体;">的</span>
<span lang="EN"> try_write()</span>
<span style="font-family: 宋体;">函数中。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">在一个非阻塞的连接上发送指定大小的数据，很可能在一次发送过程中，数据没有被完全发送出去（只发送了一部分）就返回了，所以，每次</span>
<span lang="EN"> write</span>
<span style="font-family: 宋体;">之后，必须判断是否完全发送了数据。如果没有发送完，那么下次有读事件的时候，还得回来继续发送未完得数据。这也是这个函数叫做</span>
<span lang="EN"> try_write </span>
<span style="font-family: 宋体;">的原因吧。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span lang="EN">try_write() </span>
<span style="font-family: 宋体;">在最后，要重新设置</span>
<span lang="EN"> poll </span>
<span style="font-family: 宋体;">的事件源。如果数据全部发送完毕了，那么只需要监听读事件（</span>
<span lang="EN">POLLIN</span>
<span style="font-family: 宋体;">）否则，既要监听读事件，也要监听写事件（</span>
<span lang="EN">POLLOUT</span>
<span style="font-family: 宋体;">），这样，一旦连接变的可写，可以继续将剩下的数据发送出去。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: -21pt;">
<span style="font-family: wingdings;" lang="EN">u<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span lang="EN">scan_for_timeouts()</span>
<span style="font-family: 宋体;">：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">任务处理函数，它首先把自身加入未处理任务队列中，这样，经过一段时间，可以保证这个函数再次被调用，从而达到周期性调用的效果。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">它检查每个连接是否超过指定时间没有被刷新，如果是，则该连接可能已经僵死，那么它关闭这个连接。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: -21pt;">
<span style="font-family: wingdings;" lang="EN">u<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span lang="EN">pop_unscheduled()</span>
<span style="font-family: 宋体;">：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;">
<span style="font-family: 宋体;">从任务列表中弹出一个未处理的任务。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">与</span>
<span lang="EN"> RawServer </span>
<span style="font-family: 宋体;">配合使用的是</span>
<span lang="EN"> SingleSocket </span>
<span style="font-family: 宋体;">类，这是一个辅助类，主要目的是封装对</span>
<span lang="EN"> socket</span>
<span style="font-family: 宋体;">的处理吧。包括数据的发送，都交给它来处理了。这个类比较简单，大家可以自己去看，我就不罗嗦了。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">以上是对</span>
<span lang="EN"> RasServer </span>
<span style="font-family: 宋体;">的具体实现的一个分析，可能读者看的还是晕晕糊糊，没办法，还是必须自己去看源代码，然后在遇到问题的时候，回头再来看这篇文章，才会有帮助。如果不亲自看源码，终究是纸上谈兵。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">我们再来小结一下。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">RawServer </span>
<span style="font-family: 宋体;">封装了网络服务器的实现细节，它实现了一种事件多路处理、非阻塞的网络模型。它主要负责建立新的连接，从网络读取和发送数据，而对读到的数据的具体处理工作，交给</span>
<span lang="EN"> Handler </span>
<span style="font-family: 宋体;">类来处理，从而把网络</span>
<span lang="EN">I/O</span>
<span style="font-family: 宋体;">和数据处理分离开来，使得</span>
<span lang="EN"> RawServer</span>
<span style="font-family: 宋体;">可以重用。</span>
<span lang="EN">Handler </span>
<span style="font-family: 宋体;">类是在调用</span>
<span lang="EN"> listen_forever() </span>
<span style="font-family: 宋体;">的时候，由调用者传递进来的，具体到</span>
<span lang="EN"> tracker</span>
<span style="font-family: 宋体;">服务器，就是</span>
<span lang="EN">HTTPHandler</span>
<span style="font-family: 宋体;">。有了</span>
<span lang="EN"> RawServer</span>
<span style="font-family: 宋体;">，</span>
<span lang="EN">tracker </span>
<span style="font-family: 宋体;">就可以作为一个网络服务器运行了。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">下一节，我们开始分析具体实现</span>
<span lang="EN"> tracker HTTP </span>
<span style="font-family: 宋体;">协议处理的</span>
<span lang="EN"> HTTPHandler</span>
<span style="font-family: 宋体;">类和</span>
<span lang="EN">Tracker</span>
<span style="font-family: 宋体;">类。</span>
</p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94812.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:18 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94812.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tracker 服务器源码分析之三：HTTPHandler 类</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94813.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:18:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94813.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94813.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94813.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94813.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94813.html</trackback:ping><description><![CDATA[<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">本篇文章分析</span>
<span lang="EN"> HTTPHandler</span>
<span style="font-family: 宋体;">类，它在</span>
<span lang="EN"> HTTPHandler.py </span>
<span style="font-family: 宋体;">文件中。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">上一篇我们讲到，</span>
<span lang="EN"> RawServer </span>
<span style="font-family: 宋体;">只负责网络</span>
<span lang="EN"> I/O</span>
<span style="font-family: 宋体;">，也就是从网络上读取和发送数据，至于读到的数据如何分析，以及应该发送什么样的数据，则交给</span>
<span lang="EN"> Handler </span>
<span style="font-family: 宋体;">类来处理。如果是用</span>
<span lang="EN"> c++ </span>
<span style="font-family: 宋体;">来实现的话，那么</span>
<span lang="EN"> Handler </span>
<span style="font-family: 宋体;">应该是一个接口类（提供几个虚函数作为接口），但是</span>
<span lang="EN"> python </span>
<span style="font-family: 宋体;">动态语言的特性，并不需要专门定义这么一个接口类，所以实际上并没有</span>
<span lang="EN"> Handler </span>
<span style="font-family: 宋体;">这么一个类。任何一个提供了以下成员函数的类，都可以作为一个</span>
<span lang="EN"> Handler </span>
<span style="font-family: 宋体;">类来与</span>
<span lang="EN"> RawServer </span>
<span style="font-family: 宋体;">配合，它们是：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">external_connection_made()</span>
<span style="font-family: 宋体;">：在建立新的连接的时候被调用</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">data_came_in()</span>
<span style="font-family: 宋体;">：连接上有数据可读的时候被调用</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">connection_flushed()</span>
<span style="font-family: 宋体;">：当在某个连接上发送完数据之后被调用</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>HTTPHandler </span>
<span style="font-family: 宋体;">就是这样一个</span>
<span lang="EN"> Handler </span>
<span style="font-family: 宋体;">类，它具备以上接口。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>HTTPHandler </span>
<span style="font-family: 宋体;">代码很少，因为它把主要工作又交给</span>
<span lang="EN"> HTTPConnection </span>
<span style="font-family: 宋体;">了。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">我们看</span>
<span lang="EN"> HTTPHandler </span>
<span style="font-family: 宋体;">类的这几个函数：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: -21pt;">
<span style="font-family: wingdings;" lang="EN">l<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span lang="EN">external_connection_made()</span>
<span style="font-family: 宋体;">：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">每当新来一个连接的时候，就创建一个</span>
<span lang="EN"> HTTPConnection </span>
<span style="font-family: 宋体;">类。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: -21pt;">
<span style="font-family: wingdings;" lang="EN">l<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span lang="EN">data_came_in()</span>
<span style="font-family: 宋体;">：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">当连接上有数据可读的时候，调用</span>
<span lang="EN"> HTTPConnection::data_came_in()</span>
<span style="font-family: 宋体;">。我们接下去看</span>
<span lang="EN">HTTPConnection::data_came_in()</span>
<span style="font-family: 宋体;">。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">我们知道，</span>
<span lang="EN">BT client</span>
<span style="font-family: 宋体;">端与</span>
<span lang="EN"> tracker</span>
<span style="font-family: 宋体;">服务器之间是通过</span>
<span lang="EN">tracke HTTP </span>
<span style="font-family: 宋体;">协议来进行通信的。</span>
<span lang="EN">HTTP</span>
<span style="font-family: 宋体;">协议分为请求（</span>
<span lang="EN">request</span>
<span style="font-family: 宋体;">）和响应（</span>
<span lang="EN">response</span>
<span style="font-family: 宋体;">），具体的协议请看相关的</span>
<span lang="EN"> RFC </span>
<span style="font-family: 宋体;">文档。我这里简单讲一下。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">对</span>
<span lang="EN"> tracke </span>
<span style="font-family: 宋体;">服务器来说，它读到的数据是</span>
<span lang="EN"> client </span>
<span style="font-family: 宋体;">端的</span>
<span lang="EN">HTTP </span>
<span style="font-family: 宋体;">请求。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">HTTP</span>
<span style="font-family: 宋体;">请求以行为单位，行的结束符是&#8220;回车换行&#8221;，也就是</span>
<span lang="EN"> ascii </span>
<span style="font-family: 宋体;">字符</span>
<span style="font-family: 宋体;">&#8220;</span>
<span lang="EN">\r</span>
<span style="font-family: 宋体;">&#8221;和&#8220;</span>
<span lang="EN">\n</span>
<span style="font-family: 宋体;">&#8221;。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">第一行是请求的</span>
<span lang="EN"> URL</span>
<span style="font-family: 宋体;">，例如：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">GET<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>/announce?ip=aaaaa;port=bbbbbbb<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>HTTP/1.0</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">这行数据被空格分为三部分，</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">第一部分</span>
<span lang="EN">GET</span>
<span style="font-family: 宋体;">表示命令，其它命令还有</span>
<span lang="EN">POST</span>
<span style="font-family: 宋体;">、</span>
<span lang="EN">HEAD</span>
<span style="font-family: 宋体;">等等，常用的就是</span>
<span lang="EN">GET</span>
<span style="font-family: 宋体;">了。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">第二部分是请求的</span>
<span lang="EN">URL</span>
<span style="font-family: 宋体;">，这里是</span>
<span lang="EN">/announce?ip=aaaaa;port=bbbbbbb</span>
<span style="font-family: 宋体;">。如果是普通的上网浏览网页，那么</span>
<span lang="EN">URL </span>
<span style="font-family: 宋体;">就是我们要看的网页在该</span>
<span lang="EN">web</span>
<span style="font-family: 宋体;">服务器上的相对路径。但是，这里的</span>
<span lang="EN">URL</span>
<span style="font-family: 宋体;">仅仅是交互信息的一种方式，</span>
<span lang="EN">client </span>
<span style="font-family: 宋体;">端把要报告给</span>
<span lang="EN"> tracker </span>
<span style="font-family: 宋体;">的信息，放在</span>
<span lang="EN">URL</span>
<span style="font-family: 宋体;">中，例子里面是</span>
<span lang="EN"> ip </span>
<span style="font-family: 宋体;">和</span>
<span lang="EN"> port</span>
<span style="font-family: 宋体;">，更详细的信息请看&#8220;</span>
<span lang="EN">BT</span>
<span style="font-family: 宋体;">协议规范&#8221;中</span>
<span lang="EN"> tracker </span>
<span style="font-family: 宋体;">协议部分。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">第三部分是</span>
<span lang="EN">HTTP</span>
<span style="font-family: 宋体;">协议的版本号，在程序中忽略。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">接下来的每一行，都是</span>
<span lang="EN">HTTP</span>
<span style="font-family: 宋体;">协议的消息头部分，例如：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">Host:www.sina.com.cn</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">Accept-encoding:gzip</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">通过消息头，</span>
<span lang="EN">tracker</span>
<span style="font-family: 宋体;">服务器可以知道</span>
<span lang="EN"> client</span>
<span style="font-family: 宋体;">端的一些信息，这其中比较重要的就是</span>
<span lang="EN"> Accept-encoding</span>
<span style="font-family: 宋体;">，如果是</span>
<span lang="EN"> gzip </span>
<span style="font-family: 宋体;">，那么说明</span>
<span lang="EN"> client </span>
<span style="font-family: 宋体;">可以对</span>
<span lang="EN"> gzip </span>
<span style="font-family: 宋体;">格式的数据进行解压，那么</span>
<span lang="EN">tracker</span>
<span style="font-family: 宋体;">服务器就可以考虑用</span>
<span lang="EN"> gzip </span>
<span style="font-family: 宋体;">把响应数据压缩之后再传回去，以减少网络流量。我们可以在代码中看到相应的处理。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">在消息头的最后，是一个空行，表示消息头结束了。对</span>
<span lang="EN">GET</span>
<span style="font-family: 宋体;">和</span>
<span lang="EN">HEAD</span>
<span style="font-family: 宋体;">命令来说，消息头的结束，也就意味着整个</span>
<span lang="EN">client</span>
<span style="font-family: 宋体;">端的请求结束了。而对</span>
<span lang="EN"> POST </span>
<span style="font-family: 宋体;">命令来说，可能后面还跟着其它数据。由于我们的</span>
<span lang="EN"> tracker</span>
<span style="font-family: 宋体;">服务器只接受</span>
<span lang="EN"> GET </span>
<span style="font-family: 宋体;">和</span>
<span lang="EN"> HEAD </span>
<span style="font-family: 宋体;">命令，所以在协议处理过程中，如果遇到空行，那么就表示处理结束。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">HTTPConnection::data_came_in() </span>
<span style="font-family: 宋体;">用一个循环来进行协议分析：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">首先是寻找行结束符号：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">i = self.buf.index('\n')</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">（我认为仅仅找</span>
<span style="font-family: 宋体;">&#8220;</span>
<span lang="EN">\n</span>
<span style="font-family: 宋体;">&#8221;并不严谨，应该找</span>
<span style="font-family: 宋体;">&#8220;</span>
<span lang="EN">\r\n</span>
<span style="font-family: 宋体;">&#8221;这个序列）。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">如果没有找到，那么</span>
<span lang="EN"> index() </span>
<span style="font-family: 宋体;">函数会抛出一个异常，而异常的处理是返回</span>
<span lang="EN"> True</span>
<span style="font-family: 宋体;">，表示数据不够，需要继续读数据。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">如果找到了，那么</span>
<span lang="EN">i <span>&nbsp;</span></span>
<span style="font-family: 宋体;">之前的字符串就是完整的一行。于是调用协议处理函数，代码是：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">self.next_func = self.next_func(val)</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">在</span>
<span lang="EN"> HTTPConnection </span>
<span style="font-family: 宋体;">的初始化的时候，有这么一行代码：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">self.next_func = self.read_type</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">next_func </span>
<span style="font-family: 宋体;">是用来保存协议处理函数的，所以，第一个被调用的协议处理函数就是</span>
<span lang="EN"> read_type()</span>
<span style="font-family: 宋体;">。它用来分析</span>
<span lang="EN">client</span>
<span style="font-family: 宋体;">端请求的第一行。在</span>
<span lang="EN"> read_type() </span>
<span style="font-family: 宋体;">的最后，我们看到：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">return self.read_header</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">这样，在下一次调用</span>
<span lang="EN"> next_func </span>
<span style="font-family: 宋体;">的时候，就是调用</span>
<span lang="EN"> read_header()</span>
<span style="font-family: 宋体;">了，也就是对</span>
<span lang="EN"> HTTP </span>
<span style="font-family: 宋体;">协议的消息头进行分析。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">下面先看</span>
<span lang="EN"> read_type()</span>
<span style="font-family: 宋体;">，</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">它首先把</span>
<span lang="EN"> GET </span>
<span style="font-family: 宋体;">命令中的</span>
<span lang="EN"> URL </span>
<span style="font-family: 宋体;">部分保存到</span>
<span lang="EN"> self.path</span>
<span style="font-family: 宋体;">中，因为这是</span>
<span lang="EN"> client</span>
<span style="font-family: 宋体;">端最关键的信息，后面要用到。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">然后检查一下是否是</span>
<span lang="EN">GET</span>
<span style="font-family: 宋体;">或者</span>
<span lang="EN">HEAD</span>
<span style="font-family: 宋体;">命令，如果不是，那么说明数据有错误。返回</span>
<span lang="EN">None</span>
<span style="font-family: 宋体;">，否则</span>
<span lang="EN">return self.read_header</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">接下来我们看</span>
<span lang="EN">read_header()</span>
<span style="font-family: 宋体;">，</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">这其中，最重要的就是对空行的处理，因为前面说了，空行表示协议分析结束。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">在检查完</span>
<span lang="EN"> client </span>
<span style="font-family: 宋体;">端是否支持</span>
<span lang="EN"> gzip </span>
<span style="font-family: 宋体;">编码之后，调用：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">r = self.handler.getfunc(self, self.path, self.headers)</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">通过一层层往后追查，发现</span>
<span lang="EN"> getfunc() </span>
<span style="font-family: 宋体;">实际是</span>
<span lang="EN"> Tracker::get()</span>
<span style="font-family: 宋体;">，也就是说，真正对</span>
<span lang="EN"> client </span>
<span style="font-family: 宋体;">端发来的请求进行分析，以及决定如何响应，是由</span>
<span lang="EN">Tracker </span>
<span style="font-family: 宋体;">来决定的。是的，这个</span>
<span lang="EN"> Tracker </span>
<span style="font-family: 宋体;">在我们</span>
<span lang="EN">tracker </span>
<span style="font-family: 宋体;">服务器源码分析系列的第一篇文章中就已经看到了。在创建</span>
<span lang="EN"> RawServer </span>
<span style="font-family: 宋体;">之后，马上就创建了一个</span>
<span lang="EN"> Tracker </span>
<span style="font-family: 宋体;">对象。所以，要了解</span>
<span lang="EN"> tracker </span>
<span style="font-family: 宋体;">服务器到底是如何工作的，需要我们深入进去分析</span>
<span lang="EN"> Tracker </span>
<span style="font-family: 宋体;">类，那就是我们下一篇文章的工作了。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">在调用完</span>
<span lang="EN"> Tracker::get() </span>
<span style="font-family: 宋体;">之后，返回的是决定响应给</span>
<span lang="EN"> client </span>
<span style="font-family: 宋体;">端的数据，</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">if r is not None:</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: 21pt;">
<span lang="EN">self.answer(r)</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">最后，调用</span>
<span lang="EN"> answer() </span>
<span style="font-family: 宋体;">来把这些数据发送给</span>
<span lang="EN"> client </span>
<span style="font-family: 宋体;">端。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">对</span>
<span lang="EN"> answer() </span>
<span style="font-family: 宋体;">的分析，我们在下一篇分析</span>
<span lang="EN"> Tracker</span>
<span style="font-family: 宋体;">类的文章中一并讲解。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt; text-indent: -21pt;">
<span style="font-family: wingdings;" lang="EN">l<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span>
<span lang="EN">connection_flushed()</span>
<span style="font-family: 宋体;">：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span lang="EN">tracker</span>
<span style="font-family: 宋体;">服务器用的是非阻塞的网络</span>
<span lang="EN"> I/O </span>
<span style="font-family: 宋体;">，所以不能保证在一次发送数据的操作中，把要发送的数据全部发送出去。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">这个函数，检查在某个连接上需要发送的数据，是否已经全部被发送出去了，如果是的话，那么关闭这个连接的发送端。（为什么仅仅关闭发送端，而不是完全关闭这个连接了？疑惑）。</span>
</p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94813.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:18 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94813.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tracker 服务器源码分析之四：Tracker 类</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94814.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:18:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94814.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94814.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94814.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94814.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94814.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 本篇文章分析 Tracker 类，它在 track.py 文件中。在分析之前，我们把前几篇文章的内容再回顾一下，以理清思路。&nbsp;BT的源码，主要可以分为两个部分，一部分用来实现 tracker 服务器，另一部分用来实现BT的客户端。我们这个系列的文章围绕 tracker 服务器的实现来展开。BT ...&nbsp;&nbsp;<a href='http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94814.html'>阅读全文</a><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94814.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:18 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94814.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tracker 服务器源码分析之一：总述</title><link>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94811.html</link><dc:creator>苦笑枯</dc:creator><author>苦笑枯</author><pubDate>Thu, 18 Jan 2007 16:17:00 GMT</pubDate><guid>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94811.html</guid><wfw:comment>http://www.blogjava.net/kuxiaoku/comments/94811.html</wfw:comment><comments>http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94811.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/kuxiaoku/comments/commentRss/94811.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/kuxiaoku/services/trackbacks/94811.html</trackback:ping><description><![CDATA[		<span lang="EN">tracker</span>
<span style="font-family: 宋体;">服务器是</span>
<span lang="EN">BT</span>
<span style="font-family: 宋体;">下载中必须的角色。一个</span>
<span lang="EN">BT client </span>
<span style="font-family: 宋体;">在下载开始以及下载进行的过程中，要不停的与</span>
<span lang="EN"> tracker </span>
<span style="font-family: 宋体;">服务器进行通信，以报告自己的信息，并获取其它下载</span>
<span lang="EN">client</span>
<span style="font-family: 宋体;">的信息。这种通信是通过</span>
<span lang="EN"> HTTP </span>
<span style="font-family: 宋体;">协议进行的，又被称为</span>
<span lang="EN"> tracker<span>&nbsp; </span>HTTP </span>
<span style="font-family: 宋体;">协议，它的过程是这样的：</span>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>client </span>
<span style="font-family: 宋体;">向</span>
<span lang="EN"> tracker </span>
<span style="font-family: 宋体;">发一个</span>
<span lang="EN">HTTP </span>
<span style="font-family: 宋体;">的</span>
<span lang="EN">GET</span>
<span style="font-family: 宋体;">请求，并把它自己的信息放在</span>
<span lang="EN">GET</span>
<span style="font-family: 宋体;">的参数中；这个请求的大致意思是：我是</span>
<span lang="EN">xxx</span>
<span style="font-family: 宋体;">（一个唯一的</span>
<span lang="EN">id</span>
<span style="font-family: 宋体;">），我想下载</span>
<span lang="EN">yyy</span>
<span style="font-family: 宋体;">文件，我的</span>
<span lang="EN">ip</span>
<span style="font-family: 宋体;">是</span>
<span lang="EN">aaa</span>
<span style="font-family: 宋体;">，我用的端口是</span>
<span lang="EN">bbb</span>
<span style="font-family: 宋体;">。。。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>tracker </span>
<span style="font-family: 宋体;">对所有下载者的信息进行维护，当它收到一个请求后，首先把对方的信息记录下来（如果已经记录在案，那么就检查是否需要更新），然后将一部分（并非全部，根据设置的参数已经下载者的请求）参与下载同一个文件（一个</span>
<span lang="EN">tracker</span>
<span style="font-family: 宋体;">服务器可能同时维护多个文件的下载）的下载者的信息返回给对方。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Client</span>
<span style="font-family: 宋体;">在收到</span>
<span lang="EN">tracker</span>
<span style="font-family: 宋体;">的响应后，就能获取其它下载者的信息，那么它就可以根据这些信息，与其它下载者建立连接，从它们那里下载文件片断。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-family: 宋体;">关于</span>
<span lang="EN">client</span>
<span style="font-family: 宋体;">和</span>
<span lang="EN">tracker</span>
<span style="font-family: 宋体;">之间通信协议的细节，在&#8220;</span>
<span lang="EN">BT</span>
<span style="font-family: 宋体;">协议规范&#8221;中已经给出，这里不再重复。下面我们具体分析</span>
<span lang="EN"> tracker</span>
<span style="font-family: 宋体;">服务器的实现细节。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-family: 宋体;">从哪里开始？</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">要建立一个</span>
<span lang="EN"> tracker</span>
<span style="font-family: 宋体;">服务器，只要运行</span>
<span lang="EN"> bttrack.py </span>
<span style="font-family: 宋体;">程序就行了，它最少需要一个参数，就是</span>
<span lang="EN">&#8211;dfile</span>
<span style="font-family: 宋体;">，这个参数指定了保存下载信息的文件。</span>
<span lang="EN">Bttrack.py </span>
<span style="font-family: 宋体;">调用</span>
<span lang="EN"> track.py </span>
<span style="font-family: 宋体;">中的</span>
<span lang="EN"> track()</span>
<span style="font-family: 宋体;">函数。因此，我们跟踪到</span>
<span lang="EN"> track.py </span>
<span style="font-family: 宋体;">中去看</span>
<span lang="EN">track() </span>
<span style="font-family: 宋体;">函数。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">Track.py</span>
<span style="font-family: 宋体;">：</span>
<span lang="EN">track()</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">这个函数首先对命令行的参数进行检查；然后将这些参数保存到</span>
<span lang="EN"> config </span>
<span style="font-family: 宋体;">字典中。在</span>
<span lang="EN">BT</span>
<span style="font-family: 宋体;">中所有的工具程序，都有类似的处理方式。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-family: 宋体;">接下来的代码：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>r = RawServer(Event(), config['timeout_check_interval'], config['socket_timeout'])</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp; </span>t = Tracker(config, r)</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp; </span>r.bind(config['port'], config['bind'], True)</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp; </span>r.listen_forever(HTTPHandler(t.get, config['min_time_between_log_flushes']))</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp; </span>t.save_dfile()</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">首先是创建一个</span>
<span lang="EN"> RawServer </span>
<span style="font-family: 宋体;">对象，这是一个服务器对象，它将实现一个网络服务器的一些细节封装起来。不仅</span>
<span lang="EN">tracker</span>
<span style="font-family: 宋体;">服务器用到了</span>
<span lang="EN"> RawServer</span>
<span style="font-family: 宋体;">，我们以后还可以看到，由于每个</span>
<span lang="EN"> client</span>
<span style="font-family: 宋体;">端也需要给其它</span>
<span lang="EN"> client </span>
<span style="font-family: 宋体;">提供下载服务，因此也同时是一个服务器，</span>
<span lang="EN">client</span>
<span style="font-family: 宋体;">的实现中，也用到了</span>
<span lang="EN">RawServer</span>
<span style="font-family: 宋体;">，这样，</span>
<span lang="EN">RawServer</span>
<span style="font-family: 宋体;">的代码得到了重用。关于</span>
<span lang="EN"> RawServer</span>
<span style="font-family: 宋体;">的详细实现，在后面的小节中进行分析。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">接着是创建一个</span>
<span lang="EN"> Tracker</span>
<span style="font-family: 宋体;">对象。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">然后让</span>
<span lang="EN">RawServer</span>
<span style="font-family: 宋体;">绑定在指定的端口上（通过命令行传递进来）。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">最后，调用</span>
<span lang="EN"> RawServer::listen_forever() </span>
<span style="font-family: 宋体;">函数，使得服务器投入运行。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">最后，在服务器因某些原因结束运行以后，调用</span>
<span lang="EN"> Tracker::save_dfile() </span>
<span style="font-family: 宋体;">保存下载信息。这样，一旦服务器再次投入运行，可以恢复当前的状态。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span style="font-family: 宋体;">其它信息：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
<span lang="EN">1、<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp; </span></span>
<span lang="EN">BT</span>
<span style="font-family: 宋体;">源码的分布：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt;">
<span style="font-family: 宋体;">把</span>
<span lang="EN">BT</span>
<span style="font-family: 宋体;">的源码展开之后，可以看到有一些</span>
<span lang="EN">python</span>
<span style="font-family: 宋体;">程序，还有一些说明文件等等，此外还有一个</span>
<span lang="EN">BitTorrent</span>
<span style="font-family: 宋体;">目录。这些</span>
<span lang="EN"> python</span>
<span style="font-family: 宋体;">程序，实际是一些小工具，比如制作</span>
<span lang="EN"> file</span>
<span style="font-family: 宋体;">的</span>
<span lang="EN">btmakefile.py</span>
<span style="font-family: 宋体;">、运行</span>
<span lang="EN">tracker</span>
<span style="font-family: 宋体;">服务器的</span>
<span lang="EN">bttrack.py</span>
<span style="font-family: 宋体;">、运行</span>
<span lang="EN">BT client</span>
<span style="font-family: 宋体;">端的</span>
<span lang="EN"> btdownloadheadless.py </span>
<span style="font-family: 宋体;">等等。而这些程序中，用到的一些</span>
<span lang="EN"> python </span>
<span style="font-family: 宋体;">类的实现，都放在子目录</span>
<span lang="EN"> BitTorrent </span>
<span style="font-family: 宋体;">下面。我们的分析工作，通常是从工具程序入手，比如</span>
<span lang="EN"> bttrack.py</span>
<span style="font-family: 宋体;">，而随着分析的展开，则重点是看</span>
<span lang="EN"> BitTorrenet</span>
<span style="font-family: 宋体;">子目录下的代码。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt;">
<span lang="EN">BT</span>
<span style="font-family: 宋体;">作者</span>
<span lang="EN"> Bram Cohen </span>
<span style="font-family: 宋体;">在谈到如何开发可维护的代码的一篇文章中（</span>
<span lang="EN">
<a href="http://www.advogato.org/article/258.html">http://www.advogato.org/article/258.html</a>
</span>
<span style="font-family: 宋体;">），其中提到的一条就是开发一些小工具以简化工作，我想</span>
<span lang="EN">BT</span>
<span style="font-family: 宋体;">的这种源码结构，也正是作者思想的一种体现吧。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt; text-indent: -18pt;">
<span lang="EN">2、<span style="font-family: 'Times New Roman'; font-style: normal; font-variant: normal; font-weight: normal; font-size: 7pt; line-height: normal; font-size-adjust: none; font-stretch: normal;">&nbsp; </span></span>
<span style="font-family: 宋体;">我们看到，</span>
<span lang="EN">python</span>
<span style="font-family: 宋体;">和我们以前接触的</span>
<span lang="EN"> c/c++ </span>
<span style="font-family: 宋体;">不一样的第一个地方就是它的函数在定义的时候，不用指定参数类型。既然这样，那么，在调用函数的时候，你可以传递任意类型的参数进来。例如这样的函数：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt;">
<span lang="EN">def foo(arg):</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>print type(arg)</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">你可以这样来调用：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>a = 100</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>b = &#8220;hello world&#8221;</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>foo(a)</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>foo(b)</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">输出结果是：</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&lt;type &#8216;int&#8217;&gt;</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&lt;type &#8216;str&#8217;&gt;</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt;">
<span lang="EN">
<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>
</span>
<span style="font-family: 宋体;">这是因为，第一次调用</span>
<span lang="EN"> foo()</span>
<span style="font-family: 宋体;">的时候，传递的是一个整数类型，而第二次调用的时候，传递的是一个字符串类型。</span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt 18pt;">
<span lang="EN">&nbsp;<o:p></o:p></span>
</p>
<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;">
<span style="font-family: 宋体;">这种参数具有动态类型的特性，是</span>
<span lang="EN"> c/c++</span>
<span style="font-family: 宋体;">等传统的语言是所不具备的。这也是</span>
<span lang="EN"> python </span>
<span style="font-family: 宋体;">被称为动态语言的一个原因吧。</span>
<span lang="EN">C++</span>
<span style="font-family: 宋体;">的高级特性模板，虽然也使得参数类型可以动态化，但使用起来，远没有</span>
<span lang="EN">python</span>
<span style="font-family: 宋体;">这么简单方便。</span>
</p><img src ="http://www.blogjava.net/kuxiaoku/aggbug/94811.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/kuxiaoku/" target="_blank">苦笑枯</a> 2007-01-19 00:17 <a href="http://www.blogjava.net/kuxiaoku/archive/2007/01/19/94811.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>