??xml version="1.0" encoding="utf-8" standalone="yes"?>久久久亚洲AV波多野结衣,亚洲爆乳大丰满无码专区,五月天婷亚洲天综合网精品偷http://www.tkk7.com/kuxiaoku/category/21189.html收藏zh-cnMon, 02 Apr 2007 00:08:28 GMTMon, 02 Apr 2007 00:08:28 GMT60BT客户端源码分析之八:BT对等q接的徏立过E?/title><link>http://www.tkk7.com/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.tkk7.com/kuxiaoku/archive/2007/01/19/94822.html</guid><wfw:comment>http://www.tkk7.com/kuxiaoku/comments/94822.html</wfw:comment><comments>http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94822.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/kuxiaoku/comments/commentRss/94822.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/kuxiaoku/services/trackbacks/94822.html</trackback:ping><description><![CDATA[<p>概要<br>  上一节我们分析了BT客户端与tracker之间的通信q程。通过?tracker 的通信Q客L(fng)获得了参与下载的其它peers 的列表。有了这?peers 的信息,客户端就可以d向它们发赯接,一步从它们那里获取所需要的文g片断做好准备。这些连接称?#8220;dq接”。因接的发vҎ(gu)客户端自己?br>  同时Q每个客L(fng)在启动以后,都会监听某个端口Q用于接受其?peers 的连接请求。P2P的核心理念就?#8220;公^、对{?#8221;Q你向别人有所求(下蝲Q,必L付出Q上传)。客L(fng)在接受外来的q接h之后Q就会生一个新的连 接,UC?#8220;被动q接”Q因接的发vҎ(gu)其它 peer?br> 无论是被动连接,q是dq接Q一旦连接徏立之后,双方pq行“BT对等协议”的握手。握手成功之后,双方才可以通过q个q接互相传递消息了。ؓ什么要q行握手了?主要目的是ؓ了防止一些错误的q接。这好比地下党接头Q暗号对上了Q彼此才建立信Q?/p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> 在这个示意图中,客户?A ?其它 peers B、C、D、E 都徏立了q接。通过头来表C接徏立的方向。Ad?B、D、E 建立q接QA被动接收C的连接。同时C与D、E与D、B与D之间也都有连接。这P所有下载者之间就形成了一个网状的l构?br> 同时Q这些下载者都要与 tracker 之间不断通信?br> 无论是被动连接,q是dq接Q一旦在“BT对等握手”成功之后Q它们就没有M区别了。下载通过q个q接q行Q上传也是通过q个q接q行?br> <br> 本文重点分析BT客户端如何主动向其它 peers 发vq接QBT客户端如何被动接收其?peers 的连接请求;以及在连接徏立成功之后,如何q行BT对等协议握手的过E?/p> <p>客户端主动发赯?br>【Encrypter.py?br> 上一节的最后,我们看到调用 Encoder::start_connection() 来向其它 peer 发vq接。所以,从这里开始看赗?/p> <p>class Encoder:<br># start_connection() 需要两个参敎ͼ<br>dnsQ对方的ip、port 的组合?br>idQ?Ҏ(gu)?id。每个BT客户端都要创Z个唯一?id Pq且把这?id h告给 tracker。Id号唯一标识了一个客L(fng)。Id L(fng)计算方式?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>        if id:<br>            # 如果 id 是自己,不连?br>            if id == self.my_id:<br>                return<br>            # 如果已经和这个peer建立了连接,也不再徏立连接?br>            for v in self.connections.s():<br>                if v.id == id:<br>return<br>  # 如果当前q接数过多,暂时不发赯?br>        if len(self.connections) >= self.max_initiate: <br># self.spares 起到~存的作用。在当前q接数过多的情况下,把本ơ要q接?peer ?ip、port ~存h。一旦当前连接数于讑֮?max_initiateQ?则可以从 spares 中取出备用的 peers?br>            if len(self.spares) < self.max_initiate and dns not in self.spares:<br>                self.spares.append(dns)<br>            return<br>        try:<br># 调用 RawServer::start_connection()Q发?TCP 的连接。RawServer的代码分析请参看“服务器源码分?#8221;pd文章Q不再赘q?br># q回?c 是一?SingleSocket 对象Q它?yu)装?socket 句柄<br>            # 如果出错Q抛?socketerror 异常<br>            c = self.raw_server.start_connection(dns)<br># 成功建立 TCP q接。构造一?Connection对象Q加?connections 字典中。注意,最后一个参数是 TrueQ它表面q条q接是由客户端主动发起徏立的。我们立d?Connection cȝ构?br>            self.connections[c] = Connection(self, c, id, True)<br>        except socketerror:<br>            pass<br> <br>【Encrypter.py?br>class Connection:<br>  ?nbsp; def __init__(self, Encoder, connection, id, is_local):<br>        self.encoder = Encoder<br>        self.connection = connection #q个 connection ?SingleSocket 对象Q就是上? RawServer::start_connection() q回的|它封装了对socket句柄的操作。名字v的不好,Ҏ(gu)弄؜淆?br>        self.id = id<br>        self.locally_initiated = is_local #q个q接是否由本地发P<br>        self.complete = False<br>        self.closed = False<br>        self.buffer = StringIO()<br>        self.next_len = 1<br>        ⑦self.next_func = self.read_header_len<br>  <br># 如果由本地发P那么l对方发送BT对等q接的握手消息?br>        ④if self.locally_initiated:<br>            connection.write(chr(len(protocol_name)) + protocol_name + <br>                (chr(0) * 8) + self.encoder.download_id)<br>            if self.id is not None:<br>                connection.write(self.encoder.my_id)</p> <p>客户端被动接受外来连?br>? L(fng)在与 tracker 通信的时候,已经把自q ip 和监听的 port 报告l了 tracker。这P其它 peers 可以通过q个 ip ?port 来连接它了。例如上图中的CQ就dl?A 发一个连接,从C的角度来_它是“dq接”Q但从A的角度,它是“被动q接”?br>一旦有外来q接hQ就会调?RawServer::handle_events()Q下面是摘录?#8220;Tracker 服务器源码分析之二:RawServerc?#8221;中的一Dc?/p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p> </p> <p>最 后调用的?Handle ?external_connection_made()Q对客户端来_q个Handle ?Encoder cd象。所以,外来q接建立成功后,最后调用的?Encoder:: external_connection_made()Q?/p> <p>③def external_connection_made(self, connection):<br> # 同样是创Z个新?Connection c,q加?connections 字典中。但不同之处在于最后一个参数是 FalseQ表明这个连接是由外部发L(fng)?br>self.connections[connection] = Connection(self, connection, None, False)</p> <p>BT对等q接握手Q第一?/p> <p>如果是主动连接,那么一旦连接徏立成功之后,qҎ(gu)发送一个握手消息,我们折回ȝ序号?4 的代码:<br>if self.locally_initiated:<br>     connection.write(chr(len(protocol_name)) + protocol_name + <br>                (chr(0) * 8) + self.encoder.download_id)<br>        if self.id is not None:<br>     connection.write(self.encoder.my_id)</p> <p>在《BT协议规范》中Q如此描q握手消息:<br>对等协议׃个握手开始,后面是@环的消息,每个消息的前面,都有一个数字来表示消息的长度。握手的q程首先是先发?9Q然后发送协议名U?#8220;BitTorrent protocol”?9是“BitTorrent protocol”的长度?br>后箋的所有的整数Q都采用big-endian 来编码ؓ4个字节?br>在协议名UC后,?个保留的字节Q这些字节当前都讄??br>接下来对元文件中?info 信息Q通过 sha1 计算后得到的 hash|20个字节长。接收消息方Q也会对 info q行一?hash q算Q如果这两个l果不一P那么说明双方要下载的文g不一_会切断连接?br>接下来是20个字节的 peer id?br>? 以看刎ͼ最后两就?Encoder::download_id ? Encoder::my_id。download_id是如何得来的Q它是首先对 torrent 文g?info 关键字所包含的信息进? Bencoding 方式的编码(L(fng)《BT协议规范》关?Bencoding的介l)Q然后再通过 sha 函数计算出的 hashQ摘要)倹{(相关代码都在 download.py 中)。在一ơ下载过E中Q所有的下蝲者根?torrent 文g计算出的q个 download_id应该都是一L(fng)Q否则就说明不处于同一个下载过E中?br>至于 peer_idQ可以看出是可选的。它的计方式也?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>握手q程已经完成了第一步,q需要第二步Q接收到Ҏ(gu)的握手消息,握手q程才算完成。所以接下去看在有数据到来的时候,是如何处理的?/p> <p>BT对等q接握手Q第二步<br>当TCPq接上有数据到来的时候, RawServer 会调用到 Encoder:: data_came_in()</p> <p>【Encoder?br>⑤def data_came_in(self, connection, data):<br>        self.connections[connection].data_came_in(data)</p> <p>q一步调?Connection::data_came_in()</p> <p>【Connection?br>⑥def data_came_in(self, s):<br> #q个循环处理用来对BT对等q接中的消息q行分析<br>        while True:<br>            if self.closed:<br>                return<br>            i = self.next_len - self.buffer.tell()<br>            if i > len(s):<br>                self.buffer.write(s)<br>                return<br>            self.buffer.write(s[:i])<br>            s = s[i:]<br>            m = self.buffer.get()<br>            self.buffer.reset()<br>            self.buffer.truncate()<br>            try:<br>                x = self.next_func(m) #调用消息分析函数Q第一个被调用的是read_header_len<br>            except:<br>                self.next_len, self.next_func = 1, self.read_dead<br>                raise<br>            if x is None:<br>                self.close()<br>                return<br>            self.next_len, self.next_func = x</p> <p>⑧def read_header_len(self, s):<br> # 协议的长度是否ؓ 19Q?br>        if ord(s) != len(protocol_name):<br>            return None<br>        return len(protocol_name), self.read_header # 下一个处理函?/p> <p>def read_header(self, s):<br> # 协议名称是否?#8220;BitTorrent protocol”Q?br>        if s != protocol_name:<br>            return None<br>        return 8, self.read_reserved # 下一个处理函?/p> <p>def read_reserved(self, s):<br> #8个保留字?br>        return 20, self.read_download_id # 下一个处理函?/p> <p>def read_download_id(self, s):<br> Ҏ(gu) download_id 和自p出的是否一_<br>        if s != self.encoder.download_id:<br>return None<br>  查完 download_idQ就认ؓҎ(gu)已经通过查了?br>q里很关键!Q!Q需要仔l体会。这实际上就是在被动q接情况下,完成握手q程的处理。如果连接不是由本地发v的(被动接收C个握手消息)Q那么给Ҏ(gu)回一个握手消息。这里的握手消息发送处理和W一步是一L(fng)<br>        if not self.locally_initiated:<br>            self.connection.write(chr(len(protocol_name)) + protocol_name + <br>                (chr(0) * 8) + self.encoder.download_id + self.encoder.my_id)<br>        return 20, self.read_peer_id #下一个处理函?/p> <p>def read_peer_id(self, s):<br># Connection cȝ来管理一?BT 对等q接。在握手完成之后Q就用对方的 peer_id 来唯一标识q个 Connection。这个D保存?self.id 中。显Ӟ在握手完成之前,q个 id q是I倹{?br>        if not self.id:<br>  <br>   # Ҏ(gu)的peer_id 可千万别跟自q一?br>            if s == self.encoder.my_id:<br>                return None<br>   唔,如果 peer_id 已经收到q,也不l箋下去?br>            for v in self.encoder.connections.s():<br>                if s and v.id == s:<br>return None<br>   用对方的 peer_id ?self.id 赋|唯一标识q个 Connection<br>            self.id = s<br>            if self.locally_initiated:<br>                self.connection.write(self.encoder.my_id)<br>            else:<br>                self.encoder.everinc = True<br>        else:<br>   <br>   if s != self.id:<br>                return None<br>  # OKQ握手完成!Q!<br>        self.complete = True<br>        self.encoder.connecter.connection_made(self)<br>        return 4, self.read_len #下一个处理函数。从此以后,是对其它BT对等消息的处理过E了。这是我们下一节分析的问题?/p> 结Q?br> q篇文章重点分析了BT客户端主动发赯接和被动接收q接的过E,以及在这两种情况下,如何q行BT对等握手的处理?br> 在BT对等握手完成之后Q连接的双方可以互相发送BT对等消息了,q是下一节的内容? <img src ="http://www.tkk7.com/kuxiaoku/aggbug/94822.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/kuxiaoku/" target="_blank">苦笑?/a> 2007-01-19 00:24 <a href="http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94822.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之七:客户端与tracker通信q程http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94821.html苦笑?/dc:creator>苦笑?/author>Thu, 18 Jan 2007 16:23:00 GMThttp://www.tkk7.com/kuxiaoku/archive/2007/01/19/94821.htmlhttp://www.tkk7.com/kuxiaoku/comments/94821.htmlhttp://www.tkk7.com/kuxiaoku/archive/2007/01/19/94821.html#Feedback0http://www.tkk7.com/kuxiaoku/comments/commentRss/94821.htmlhttp://www.tkk7.com/kuxiaoku/services/trackbacks/94821.html概要
 上一节我们分析了BT客户端主E序的框Ӟ一个@环的服务器程序。在q个循环q程中,客户端要完成如下dQ?br>? ?tracker ? 务器通信Q汇报自q状态,? 时获取其?peers 的信息;
? 接受其它 peers 的连接请求;
? d向某?peers 发vq接hQ?br>? 对BT对等协议的分析处理;
? 启用片断选择法Q? 通过q些d或被动的q接M载所需要的片断Q?br>? 为其?peers 提供片断上传Q启用阻塞算法,? d某些 peers 的上传请求;
? 下载的片断写入盘Q?br>? 其它dQ?/p>

  q一节我们分析BT客户端与 tracker 服务器的通信q程。在整个下蝲q程中,客户端必M断的?tracker 通信Q报告自q状态,同时也从 tracker 那里获取当前参与下蝲的其?peers 的信息(ip地址、端口等Q。一旦不能与 tracker 通信Q则下蝲q程无法l箋下去?br> 客户端与 tracker 之间采用 HTTP 协议通信Q客L(fng)把状态信息放? HTTP协议的GET命o的参C传递给 trackerQtracker 则把 peers列表{信息放?中传递给 客户端。这U设计非常简捷y妙。采用HTTP协议也更使得I越防火墙的L更容易(不过实际应用中,tracker通常不会使用 80 端口Q?br> 请务必参考服务器端源码分析的几篇相关文章Q论坛中有)?br> Rerequester cd权负责与 tracker 的通信dQ我们首先看客户端主E序 download.py中相应的代码Q?/p>

【download.py?br>①rerequest = Rerequester(response['announce'], config['rerequest_interval'],
        rawserver.add_task, connecter.how_many_connections,
        config['min_peers'], encoder.start_connection,
        rawserver.add_task, storagewrapper.get_amount_left,
        upmeasure.get_total, downmeasure.get_total, listen_port,
        config['ip'], myid, infohash, config['http_timeout'], errorfunc,
        config['max_initiate'], doneflag, upmeasure.get_rate,
downmeasure.get_rate,
        encoder.ever_got_incoming
②rerequest.begin()

Rerequester cȝ构造函C传递了一大堆参数Q重Ҏ(gu) rawserver.add_task? encoder.start_connectionQrawserver和encoder q两个对象,在上一节中Q我们已l看C它们的构造过E)Q分别对应RawServer::add_task() ? Encoder::start_connection()Q稍后会看到它们的作用?br>一切从 Rerequest::begin() 开始?/p>

【rerequester.py?/p>

class Rerequester:
 # q个构造函C递了一大堆参数Q现在没必要搞的那么清楚Q关键是 sched ?connect q两个参敎ͼ对照前面的代码,很容易知道它们是什么?br>    def __init__(self, url, interval, sched, howmany, minpeers,
            connect, externalsched, amount_left, up, down,
            port, ip, myid, infohash, timeout, errorfunc, maxpeers, doneflag,
            upratefunc, downratefunc, ever_got_incoming):
        self.url = ('%s?info_hash=%s&peer_id=%s&port=%s&key=%s' %
            (url, quote(infohash), quote(myid), str(port),
            b2a_hex(''.join([chr(randrange(256)) for i in xrange(4)]))))
        if ip != '':
            self.url += '&ip=' + quote(ip)
        self.interval = interval
        self.last = None
        self.trackerid = None
        self.announce_interval = 30 * 60
        self.sched = sched # Q》RawServer::add_task()
        self.howmany = howmany # 当前有多个q接 peer
        self.minpeers = minpeers
        self.connect = connect #Q?Encoder::start_connection()
        self.externalsched = externalsched
        self.amount_left = amount_left
        self.up = up
        self.down = down
        self.timeout = timeout
        self.errorfunc = errorfunc
        self.maxpeers = maxpeers
        self.doneflag = doneflag
        self.upratefunc = upratefunc
        self.downratefunc = downratefunc
        self.ever_got_incoming = ever_got_incoming
        self.last_failed = True
        self.last_time = 0

?nbsp;def c(self):
  # 再调用一?add_task()Q把自n加入到客L(fng)的Q务队列中Q这样就形成了Q务@环,每隔一D|_׃执行q个函数。从而,客户端与 tracker 的通信能够持箋的进行下厅R?br>        self.sched(self.c, self.interval)
       
        if self.ever_got_incoming():
            # 如果曄接受到过外来的连接,那么说明q个客户端是可以接受外来q接的,也就是说很可能处?#8220;公网”之中Q所以ƈ非很q切的需要从 tracker 那里获取 peers 列表。(既可以主动连接别人,也可以接受别人的q接Q所以有点不紧不慢的味道Q?br>            getmore = self.howmany() <= self.minpeers / 3
        else:
            # 如果从来没有接受到过外来的连接,那么很有可能处于内网之中Q根本无法接受外来的q接。这U情况下Q只有主动的去连接外部的 peersQ所以要可能的?tracker h更多?peers。(只能去连接别人,所以更q切一些)?br>            getmore = self.howmany() < self.minpeers
        if getmore or time() - self.last_time > self.announce_interval:
            # 如果有必要从 tracker 那里获取 peers 列表Q且d执行旉已到Q则调用 announce() 开始与 tracker 通信
            self.announce()

?nbsp;   def begin(self):
        # 调用前面提到?RawServer::add_task()Q把 Rerequester::c() 加入?rawserver对象的Q务队列中
        self.sched(self.c, self.interval)
  # 宣布?tracker 的通信开始。。。?br>        self.announce(0)

?nbsp;   def announce(self, event = None):
        self.last_time = time()
  # 传递给 tracker 的信息,攑֜ HTTP GET 命o的参C。所以,首先是构造这个参数?br>  # 传递给 tracker 的信息,必须包括 uploaded、downloaded、left 信息。其它入 last、trackerid、numwant、compact、event 是可选的。这些参数的解释L(fng) BT协议规范?br>        s = ('%s&uploaded=%s&downloaded=%s&left=%s' %
            (self.url, str(self.up()), str(self.down()),
            str(self.amount_left())))
        if self.last is not None:
            s += '&last=' + quote(str(self.last))
        if self.trackerid is not None:
            s += '&trackerid=' + quote(str(self.trackerid))
        if self.howmany() >= self.maxpeers:
            s += '&numwant=0'
        else:
            s += '&compact=1'
        if event != None:
            s += '&event=' + ['started', 'completed', 'stopped'][event]
        set = SetOnce().set

  # 函数中可以嵌套定义函敎ͼq是 python 语法特别的地斏V?br>  # 调用 RawServer::add_task() ?checkfail() 加入CQ务队列中。在 timeout 旉之后Q检查向 tracker 发的GET命o是否p|?br>        def checkfail(self = self, set = set):
            if set():
                if self.last_failed and self.upratefunc() < 100 and self.downratefunc() < 100:
                    self.errorfunc('Problem connecting to tracker - timeout exceeded')
                self.last_failed = True
        self.sched(checkfail, self.timeout)

        # 创徏一个线E,执行 Rerequester::rerequest()
  # 可见Q客L(fng)?tracker之间的通信是由单独的线E完成的
        Thread(target = self.rerequest, args = [s, set]).start()

?nbsp;   def rerequest(self, url, set):
        try:
   # 调用 urlopen() Q向 tracker 发送命?br>h = urlopen(url)
# read() q回 tracker 的响应数据?br>            r = h.read()
            h.close()
            if set():
                def add(self = self, r = r):
self.last_failed = False
# ?tracker 响应回来的数据,?postrequest() 处理?br>self.postrequest(r)
# externalsched() 同样是调?RawServer::add_task()Q这P׃U程调用 postrequest() 函数Q处?tracker 响应的数据。(不要忘记Q我们现在是处在一个单独的子线E中。)
                self.externalsched(add, 0)
        except (IOError, error), e:
            if set():
                def fail(self = self, r = 'Problem connecting to tracker - ' + str(e)):
                    if self.last_failed:
                        self.errorfunc(r)
                    self.last_failed = True
                self.externalsched(fail, 0)

?nbsp;   def postrequest(self, data):
        try:
   # Ҏ(gu)务器响应的数据解码。请参看BT协议规范?br>r = bdecode(data)
# ?peers 有效性?
            check_peers(r)
            if r.has_key('failure reason'):
                self.errorfunc('rejected by tracker - ' + r['failure reason'])
            else:
                if r.has_key('warning message'):
self.errorfunc('warning from tracker - ' + r['warning message'])

 # Ҏ(gu) tracker 的响应,调整BT客户端的参数?br>                self.announce_interval = r.get('interval', self.announce_interval)
                self.interval = r.get('min interval', self.interval)
                self.trackerid = r.get('tracker id', self.trackerid)
                self.last = r.get('last')

    # 其它下载者的信息保存?peers 数组中?br>                p = r['peers']
                peers = []
                if type(p) == type(''):
                    for x in xrange(0, len(p), 6):
                        ip = '.'.join([str(ord(i)) for i in p[x:x+4]])
                        port = (ord(p[x+4]) << 8) | ord(p[x+5])
                        peers.append((ip, port, None))
                else:
                    for x in p:
                        peers.append((x['ip'], x['port'], x.get('peer id')))
                ps = len(peers) + self.howmany()
                if ps < self.maxpeers:
                    if self.doneflag.isSet():
                        if r.get('num peers', 1000) - r.get('done peers', 0) > ps * 1.2:
                            self.last = None
                    else:
                        if r.get('num peers', 1000) > ps * 1.2:
                            self.last = None
                # q里?connectQ即前面的Encoder::start_connection()Q?br>    # xQ已l成功的完成了一ơ与 tracker 服务器的通信Qƈ且从它那里获取了 peers 列表Q下面就与这?peers 挨个建立q接Q至于徏立连接的q程Q就要追t到 Encoder cM了,且听下回分解?br>                for x in peers:
                    # x[0]:ip, x[1]:port, x[2]:id
                    self.connect((x[0], x[1]), x[2])
        except Error, e:
            if data != '':
                self.errorfunc('bad data from tracker - ' + str(e))


结Q?br>  q篇文章Q我们重点分析了BT客户端与 tracker 服务器通信的过E。我们知道了Q客L(fng)要每隔一D|_去q接 tracker 一ơ,它是以一个单独的U程执行的。这个线E在接受?tracker 的响应数据后Q交l主U程Q既ȝ序)来进行分析。主E序从响应数据中Q获? peers 列表。然后调?Encoder::start_connection() 挨个与这?peers 试建立q接?/p>

]]>
BT客户端源码分析之四:PiecePicker c?/title><link>http://www.tkk7.com/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.tkk7.com/kuxiaoku/archive/2007/01/19/94818.html</guid><wfw:comment>http://www.tkk7.com/kuxiaoku/comments/94818.html</wfw:comment><comments>http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94818.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/kuxiaoku/comments/commentRss/94818.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/kuxiaoku/services/trackbacks/94818.html</trackback:ping><description><![CDATA[     摘要: PiecePicker 用于实现“片断选择法”Q片断选择法在《Incentives Build Robustness in BitTorrent》一文中有介l,我把相关内容列出来?   BT的片断选择法Q综合下面几U策略?   l      &nbs...  <a href='http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94818.html'>阅读全文</a><img src ="http://www.tkk7.com/kuxiaoku/aggbug/94818.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/kuxiaoku/" target="_blank">苦笑?/a> 2007-01-19 00:22 <a href="http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94818.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之五:Encoder cd Connection c?/title><link>http://www.tkk7.com/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.tkk7.com/kuxiaoku/archive/2007/01/19/94819.html</guid><wfw:comment>http://www.tkk7.com/kuxiaoku/comments/94819.html</wfw:comment><comments>http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94819.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/kuxiaoku/comments/commentRss/94819.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/kuxiaoku/services/trackbacks/94819.html</trackback:ping><description><![CDATA[<p>Encoder 是一U?Handler c(关于 Handlerc,请参看前面的分析文章Q。它?download.py 中被初始化。它?ConnectioncMP完成“BT对等q接”的徏立,以及“BT对等协议”的分析?br>Z有助于理解,我添加了一些用圆圈括v来的序号Q徏议你按照q个序去阅诅R?/p> <p>class Connection:<br>    ②def __init__(self, Encoder, connection, id, is_local):<br>        self.encoder = Encoder<br>        self.connection = connection<br>如果是本地发赯接,那么 id 是对方的 idQ否?id ?None<br>        self.id = id<br>如果q接是由本地发v的,那么 is_local ?TrueQ否则ؓ False<br>        self.locally_initiated = is_local<br>        self.complete = False<br>        self.closed = False<br>        self.buffer = StringIO()<br>        self.next_len = 1<br>        self.next_func = self.read_header_len<br>如果q接是由本地d发v建立的,那么需要向Ҏ(gu)发送一个握手消息。(如果不是由本C动发L(fng)Q那么就是被动徏立的Q那么不能在q里发送握手消息,而必d分析完对方的握手消息之后Q再d应一个握手西消息Q请看read_download_id() 中的处理?br>        if self.locally_initiated:<br>            connection.write(chr(len(protocol_name)) + protocol_name + <br>                (chr(0) * 8) + self.encoder.download_id)<br>            if self.id is not None:<br>                connection.write(self.encoder.my_id)</p> <p>    def get_ip(self):<br>        return self.connection.get_ip()</p> <p>    def get_id(self):<br>        return self.id</p> <p>    def is_locally_initiated(self):<br>        return self.locally_initiated</p> <p>    def is_flushed(self):<br>        return self.connection.is_flushed()</p> <p>    ⑦def read_header_len(self, s):<br>        if ord(s) != len(protocol_name):<br>            return None<br>        return len(protocol_name), self.read_header</p> <p>    def read_header(self, s):<br>        if s != protocol_name:<br>            return None<br>        return 8, self.read_reserved</p> <p>    def read_reserved(self, s):<br>        return 20, self.read_download_id</p> <p>    def read_download_id(self, s):<br>        if s != self.encoder.download_id:<br>            return None<br>q一步很重要Q?如果q接是由Ҏ(gu)发v的,那么Q给Ҏ(gu)发送一个握手消息。ؓ什么不在读完了 peer id 之后才发送这个消息了Q这是因?peer id 是可选的Q所以只要分析完 download id 之后Q就要立卛_送握手消息?br>        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>        return 20, self.read_peer_id</p> <p>    def read_peer_id(self, s):<br>        if not self.id:<br>            如果 peer id 是自己,那么出错?br>            if s == self.encoder.my_id:<br>                return None<br>            for v in self.encoder.connections.s():<br>                如果已经跟该 peer 建立了连接了Q那么也出错?br>                if s and v.id == s:<br>                    return None<br>            self.id = s<br>            if self.locally_initiated:<br>                self.connection.write(self.encoder.my_id)<br>            else:<br>                self.encoder.everinc = True<br>        else:<br>            如果 peer id ?xxx 不符Q那么出错了?br>            if s != self.id:<br>                return None</p> <p>“BT对等q接”的握手过E正式宣告完成,此后Q双方就可以通过q个q接互相发送消息了?br>        self.complete = True</p> <p>调用Connecter::connection_made()Q这个函数的意义Q我们到分析 Connecter cȝ时候,再记得分析?br>        self.encoder.connecter.connection_made(self)</p> <p>下面q入 BT 消息的处理过E?br>        return 4, self.read_len</p> <p>    def read_len(self, s):<br>        l = toint(s)<br>        if l > self.encoder.max_len:<br>            return None<br>        return l, self.read_message</p> <p>消息处理Q交l了 Connecter::got_message()Q所以下一我们要分析 Connecter cR?br>    def read_message(self, s):<br>        if s != '':<br>            self.encoder.connecter.got_message(self, s)<br>        return 4, self.read_len</p> <p>    def read_dead(self, s):<br>        return None</p> <p>    def close(self):<br>        if not self.closed:<br>            self.connection.close()<br>            self.sever()</p> <p>    def sever(self):<br>        self.closed = True<br>        del self.encoder.connections[self.connection]<br>        if self.complete:<br>            self.encoder.connecter.connection_lost(self)</p> <p>    def send_message(self, message):<br>        self.connection.write(tobinary(len(message)) + message)</p> <p> <br>⑤在 Encoder::data_came_in() 中调用下面这个函敎ͼ表示某个q接上有数据可读。如果有数据可读Q那么我们就按照 BT 对等协议的规范来q行分析。。?br>def data_came_in(self, s):</p> <p>q入协议分析循环。。?br>        while True:<br>            if self.closed:<br>                return</p> <p>self.next_len表示按照BT对等协议规范Q下一D要分析的数据的长度<br>self.buffer.tell() 表示~冲Z剩下数据的长?br>那么 i pC:Z完成接下来的协议分析Q还需要多数据?<br>i = self.next_len - self.buffer.tell()<br>如果 i 大于所d的数据的长度Q那表示数据q没有读够,无法l箋协议分析Q需要等d_多的数据才能l箋Q所以只能退出?br>            if i > len(s):<br>                self.buffer.write(s)<br>                return<br>否则表示q次d的数据已l够完成一步协议分析?br>只取满q一步协议分析的数据攑օ buffer 中(因ؓ buffer中可能还有上一步协议分析后留下的一些数据,要加在一PQ剩下的数据保留?s 中?br>            self.buffer.write(s[:i])<br>s = s[i:]</p> <p>?buffer 中取出数据,q些数据是q一步协议分析所需要的数据。然后把 buffer 清空?br>            m = self.buffer.get()<br>            self.buffer.reset()<br>self.buffer.truncate()</p> <p>next_func 是用于q一步协议分析的函数?br>q回?x 是一个二元组Q包含了下一步协议分析的数据长度和协议分析函数。这PŞ成了一个协议分析@环?br>            try:<br>                x = self.next_func(m)<br>            except:<br>                self.next_len, self.next_func = 1, self.read_dead<br>                raise<br>            if x is None:<br>                self.close()<br>                return<br>?x 中分解出 next_len?next_func?br>self.next_len, self.next_func = x<br>⑥那么BT对等协议分析的第一步是什么了Q?br>L(fng)初始化函敎ͼ<br>self.next_len = 1<br>self.next_func = self.read_header_len<br>昄Q第一步协议分析是?read_header_len() 来完成的?br>在BT源码中,有多处采用了q种协议分析的处理方式?/p> <p>class Encoder:<br>    def __init__(self, connecter, raw_server, my_id, max_len,<br>            schedulefunc, keepalive_delay, download_id, <br>            max_initiate = 40):<br>        self.raw_server = raw_server<br>        self.connecter = connecter<br>        self.my_id = my_id<br>        self.max_len = max_len<br>        self.schedulefunc = schedulefunc<br>        self.keepalive_delay = keepalive_delay<br>        self.download_id = download_id<br>        最大发L(fng)q接?br>        self.max_initiate = max_initiate<br>        self.everinc = False<br>        <br>        self.connections = {}<br>        self.spares = []<br>        <br>        schedulefunc(self.send_keepalives, keepalive_delay)</p> <p>Z保持q接不因时而被关闭Q所以可能需要随机的发送一些空消息Q它的目的纯_Ҏ(gu)Z保证q接?#8220;zd”<br>    def send_keepalives(self):<br>        self.schedulefunc(self.send_keepalives, self.keepalive_delay)<br>        for c in self.connections.s():<br>            if c.complete:<br>                c.send_message('')</p> <p>③主动向Ҏ(gu)发v一个连接,q个函数什么时候调用?<br>L(fng) download.py ?Rerequester cȝ初始化函敎ͼ其中传递的一个参数是 encoder.start_connection?br>再看 Rerequester.py 中,Rerequester::postrequest() 的最后,<br>for x in peers:<br>self.connect((x[0], x[1]), x[2])<br>q里调用?connect() 是初始化的时候传递进来的 encoder.start_connectionQ也是下面q个函数了?br>也就是说Q当客户端从 tracker 服务器那里获取了 peers 列表之后Q就逐一向这?peers d发vq接?br>    def start_connection(self, dns, id):<br>        if id:<br>            跟自׃用徏立连接?br>            if id == self.my_id:<br>                return<br>            如果已经与对方徏立vq接Q也不再建立q接<br>            for v in self.connections.s():<br>                if v.id == id:<br>                    return</p> <p>        如果当前q接敎ͼ已经过讑֮?#8220;最大发赯接数”Q那么就暂时不徏立连接?br>  <br>        if len(self.connections) >= self.max_initiate:<br>如果I闲q接数还于 “最大发赯接数”Q那么把Ҏ(gu)?ip 先放到spares中,{以后网l稍微空闲一点的时候,再从 spares 中取出来Q实际去建立q接?br>            if len(self.spares) < self.max_initiate and dns not in self.spares:<br>                self.spares.append(dns)<br>            return<br>        try:<br>调用 RawServer::start_connection 与对方徏立TCPq接<br>            c = self.raw_server.start_connection(dns)<br>            <br>创徏 Connection 对象Q加入到 connections 字典中,注意Q最后一个参数是 TrueQ表C是q个q接是由本地d发v的。这P?Connection 的初始化函数中,会与Ҏ(gu)q行 BT 对等协议的握手?/p> <p>            self.connections[c] = Connection(self, c, id, True)<br>        except socketerror:<br>            pass<br>    <br>q个内部函数好像没有用到<br>    def _start_connection(self, dns, id):<br>        def foo(self=self, dns=dns, id=id):<br>            self.start_connection(dns, id)<br>        <br>        self.schedulefunc(foo, 0)<br>        <br>    def got_id(self, connection):<br>        for v in self.connections.s():<br>            if connection is not v and connection.id == v.id:<br>                connection.close()<br>                return<br>        self.connecter.connection_made(connection)</p> <p>    def ever_got_incoming(self):<br>        return self.everinc</p> <p>①在 RawServer 中,当从外部发v的一个TCP成功建立后,调用此函数?br>q里传递进来的参数 connection ?SingleSocket cd<br>def external_connection_made(self, connection):<br> 创徏一?Connection 对象Q加入到 connections 字典中?br>        self.connections[connection] = Connection(self, connection, None, False)</p> <p>    def connection_flushed(self, connection):<br>        c = self.connections[connection]<br>        if c.complete:<br>            self.connecter.connection_flushed(c)</p> <p>关闭q接的时候调用此函数<br>    def connection_lost(self, connection):<br>        self.connections[connection].sever()<br>关闭一个连接之后,q接数量可能没有达?#8220;最大连接数”Q所以如?spares 中有一些等待徏立的 ip Q现在可以取出来Q主动向Ҏ(gu)发vq接?br>        while len(self.connections) < self.max_initiate and self.spares:<br>            self.start_connection(self.spares.pop(), None)</p> ④某个连接上Q无q接上主动徏立还是被动徏立的Q有数据可读的时候,调用此函数。在 RawServer 中被调用。{而去?Connection::data_came_in()?br>    def data_came_in(self, connection, data):<br>        self.connections[connection].data_came_in(data) <img src ="http://www.tkk7.com/kuxiaoku/aggbug/94819.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/kuxiaoku/" target="_blank">苦笑?/a> 2007-01-19 00:22 <a href="http://www.tkk7.com/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.tkk7.com/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.tkk7.com/kuxiaoku/archive/2007/01/19/94820.html</guid><wfw:comment>http://www.tkk7.com/kuxiaoku/comments/94820.html</wfw:comment><comments>http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94820.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/kuxiaoku/comments/commentRss/94820.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/kuxiaoku/services/trackbacks/94820.html</trackback:ping><description><![CDATA[<p>前言Q?br> 自从7月䆾写完“客户端源码分析之五:Encoder ?Connection c?#8221;后,我就停止了l对BT源码的分析。原因很多,最主要的还是懒惰吧。(f)到岁末,l于下定军_Q无论如何,要完成这一pd的文章,对自׃有个交待?br>  前面的几文章,都是深入到源码的某一部分l节之中Q虽然很清晰Q但无助于读者对整体构架的把握(其实我自己当时也比较p涂Q。这一ơ重新开始读源码Q重 炚w着几条U烦往下读Q感觉原来杂乱无序的代码H然变得清晰明了hQ呵呵,其实不是代码杂ؕQ只是我原来的阅L\比较混ؕ的缘故?br> 读者可以抛开前面几篇文章Q从q一开始往下读Q希望能有所收获?br> 源码分析cȝ文章Q比较难写,马哥毕竟没有候捷的春U笔法(甚至q作文都写不好)Q能把一个深奥复杂的STL源码分析的如此透彻Q所以只好尝试一些傻办法Q这些傻办法包括Q?br>在源码上直接加注释。但我只寚w点的部分增加一些注释,l节的东西就不再qQ这h助于写作的进度?br>用①、②、③q样的序h指引代码的阅ȝ?br>用不同颜色标注出代码中值得注意的地斏V?br>你有什么好的徏议,Ƣ迎提出来?br>好,我们q入正题?/p> <p>BT客户端的 main() 函数Q?br> C 和c++的可执行E序Q通常都有一?main() 函数Q一切从q里开始。?python q种解释性语aQƈ没有 main() 函数的概c你传递给解释器一?.py 扩展名的python源码文gQ解释器׃序去解释执行这个文件中的代码。所以,BT客户端的执行Q是从最先被 python 解释器解释执行的那个文g开始的。这个文件应该是 btdownloadheadless.pyQ至于谁又来通知?python 解释器执行这个文件的Q我们以后再讨论?/p> <p>【btdownloadheadless.py】(在BT源码的根目录下)<br>②def run(params):<br>    try:<br>        import curses<br>        curses.initscr()<br>        cols = curses.COLS<br>        # endwin() De-initialize the library, and return terminal to normal status<br>        curses.endwin()<br>     <br>    except:<br>        cols = 80<br>h = HeadlessDisplayer()<br># 调用 download.py 中的 download 函数Q我们的重点{Ud download.py 文gQ注意,该文件及以后要分析的代码都在 BT源码?BitTorrent 子目录下?br>?nbsp;   download(params, h.chooseFile, h.display, h.finished, h.error, Event(), cols, h.newpath)<br>    if not h.done:<br>        h.failed()</p> <p> # 所有的 python的入门书c中Q都会告诉你下面q段代码的含义。这是最先被解释执行的代码所在?br>①if __name__ == '__main__':<br>run(argv[1:])</p> <p> <br>【download.py?br>q个文g中,最主要的就?download() 函数Q客L(fng)所有的一切都是从q里开始。这个函C码比较多Q我必须挑出其中最重要的代码,其它的部分,后箋再分析?/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>     rawserver.bind(listen_port, config['bind'])<br>        break<br>except socketerror, e:<br>     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>? L(fng)的核心类是 RawServerQ这个类我在“服务器端源码分析”Q可在论坛中扑ֈQ文章中分析q它的作用,q里不再赘述。通过调用它的 listen_forever() 函数QBT 客户端就q入了一个@环之中。此后,所有的下蝲、上传、文件存储以及其它工作都在这一ơ次的@环之中完成?br>注意刎ͼ在进入@环之前,调用? RawServer::bind() 函数QBT客户端会选择一个可用的端口Q然后通过监听q个端口Q从而可以接受其?peer 的连接请求(也就是BT对等q接Q。这个端口可以称?#8220;监听端口”。如果你有网l服务器的编E经验,从它的@环处理逻辑、监听端口的处理可以知道Q这昄 是一个典型的|络服务器的表现。所以,我在以前的文章中曄说过QBT客户端同时也是一个服务器?br>一旦在监听端口上有某个peer发来q接? 求,BT客户端会创徏一个新的端口,q个端口用来与请求者徏立连接;有几个peerhq接Q就会创建几个端口。我们可以说q是一?#8220;被动端口”。在循环 的处理过E中QBT客户端还会根据情况,向其? peerd发出q接hQ每个连接也需要一个端口,q些端口可以UCؓ“d端口”。其实,q些都是|络~程中最基本的概念,不清楚的朋友q是首先要去? 看这斚w的书c?br>q样Q就有了一?#8220;监听端口”Q几?#8220;被动端口”和几?#8220;d端口”QBT客户端通过 select() 函数来监视这些端口,一?#8220;监听端口”或?#8220;被动端口”上有数据到来Q或者有数据需要从“d端口”上发送出去,那么select() 都可以及时感知ƈq行处理Q其实是一个轮询的q程Q,q就UCؓ“I/O多\复用”。如?#8220;被动端口”上有数据到来Q那么这是BT对等q接的协议数据,需? 按照BT对等协议q行分析QƈҎ(gu)分析l果q行相应处理Q这个分析处理的工作是?Encoder cL完成的。所以在 listen_forever() 函数中传递的参数是一?Encoder cd象。关于对 Encoder cȝ分析Q以后分析到BT对等协议的处理的时候再说?/p> <p>结Q?br> 通过q篇文章Q我们了解了BT客户端是从哪里执行的Q相应的源码在哪 里。同时我们知道了BT客户端是以一个服务器循环的Ş式运行的Q它需要监听一个端口,用于接受其它peers的连接请求;在@环过E中Q它通过 select 来实?#8220;I/O多\复用”Qƈ?Encoder cL完成BT对等协议的分析处理?/p><img src ="http://www.tkk7.com/kuxiaoku/aggbug/94820.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/kuxiaoku/" target="_blank">苦笑?/a> 2007-01-19 00:22 <a href="http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94820.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之三:StorageWrapperc?/title><link>http://www.tkk7.com/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.tkk7.com/kuxiaoku/archive/2007/01/19/94817.html</guid><wfw:comment>http://www.tkk7.com/kuxiaoku/comments/94817.html</wfw:comment><comments>http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94817.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/kuxiaoku/comments/commentRss/94817.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/kuxiaoku/services/trackbacks/94817.html</trackback:ping><description><![CDATA[     摘要: StorageWrapper 的作用:把文件片断进一步切割ؓ子片断,q且些子片断发? request 消息。在获得子片断后Q将数据写入盘? L(fng)? Storage cȝ分析来看?   几点说明Q? 1?nbsp; Z获取传输性能Q?BT 把文件片断切割ؓ多个子片断? 2?nbsp; BT 取一...  <a href='http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94817.html'>阅读全文</a><img src ="http://www.tkk7.com/kuxiaoku/aggbug/94817.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/kuxiaoku/" target="_blank">苦笑?/a> 2007-01-19 00:21 <a href="http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94817.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之二: Storage c?/title><link>http://www.tkk7.com/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.tkk7.com/kuxiaoku/archive/2007/01/19/94816.html</guid><wfw:comment>http://www.tkk7.com/kuxiaoku/comments/94816.html</wfw:comment><comments>http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94816.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/kuxiaoku/comments/commentRss/94816.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/kuxiaoku/services/trackbacks/94816.html</trackback:ping><description><![CDATA[     摘要: 作者:马? 日期Q?2004-6-28   ׃ Storage cL较简单,我直接在源码基础上进行注释。掌?Storage Qؓq一步分? StorageWrapper cL下基?   几点说明Q? 1?nbsp; Storage c?..  <a href='http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94816.html'>阅读全文</a><img src ="http://www.tkk7.com/kuxiaoku/aggbug/94816.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/kuxiaoku/" target="_blank">苦笑?/a> 2007-01-19 00:20 <a href="http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94816.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT客户端源码分析之一Q总述http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94815.html苦笑?/dc:creator>苦笑?/author>Thu, 18 Jan 2007 16:19:00 GMThttp://www.tkk7.com/kuxiaoku/archive/2007/01/19/94815.htmlhttp://www.tkk7.com/kuxiaoku/comments/94815.htmlhttp://www.tkk7.com/kuxiaoku/archive/2007/01/19/94815.html#Feedback0http://www.tkk7.com/kuxiaoku/comments/commentRss/94815.htmlhttp://www.tkk7.com/kuxiaoku/services/trackbacks/94815.htmlBT客户端源码分析之一Q总述
作者:马?br>日期Q?004-6-24

概述Q?br>相对?tracker 服务器来_BT客户端要复杂的多QBram Cohen ׃一q?full time 的时间来完成 BTQ我估计其中大部分时间是用在 BT 客户端的实现和调试上了?br>? ?BT 客户端涉及的代码比较多,我不能再象分?tracker 服务器那PC来就深入到细节之中去Q那L(fng)话,我写的晕晕糊p,大家看v来也不知所云。所以第一文章先来谈谈客L(fng)的功能、相兛_议,以及客户端的 M架构和相关的cȝ层次l构。这P从整体上把握之后Q大家在自己分析代码的过E中Q就能做到胸有成竏V?/p>

客户端的功能Q?/p>

不看代码Q只Ҏ(gu) BT 的相兛_理,大致可以推测Q客L(fng)需要完成以下功能:
1、解?torrent 文gQ获取要下蝲的文件的详细信息Qƈ在磁盘上创徏I文件?br>2、与 tracker服务?建立q接Qƈ交互消息?br>3、根据从 tracker 得到的信息,跟其?peers 建立q接Qƈ下蝲需要的文g片断
4、监听某端口Q等待其它peers 的连接,q提供文件片断的上传?/p>

相关协议Q?br>对客L(fng)来说Q它需要处理两U协议:
1、与 tracker 服务器交互的 track HTTP协议?br>2、与其它 peers 交互?BT 对等协议?/p>

M架构Q?br>从M上来看,BT客户端实际是以一个服务器的Ş式在q行。这一点似乎有些难以理解,但确实是q样?br>Z么是一个服务器了?
? L(fng)的主要功能是下蝲文gQ但作ؓ一UP2P软gQ同时它必须提供上传服务Q也是它必d候在某一个端口上Q等待其它peers 的连接请求。从q一点上来说Q它必须以一个服务器的Ş式运行。我们在后面实际分析代码的时候,可以看到Q客L(fng)复用?RawServer cȝ来实现网l服务器?br>客户端的代码Q是?download.py 开始的Q首先完成功?Q之后就q入服务器@环,在每一ơ@环过E中Q完成功?2??。其中,Rerequester c负责完成功?Q它通过 RawServer::add_task()Q向 RawServer d自己的Q务函敎ͼq个d函数Q每隔一D|间与 tracker 服务器进行通信。而Encoder、Connecter {多个类l合在一P完成功能3??/p>

cdơ结构:

BT 客户端涉及的cL较多Q我首先大致描述一下这些类的功能,然后l出它们的一个层ơ结构?/p>

1、RawServerQ负责实现网l服务器

2、RerequesterQ负责和 tracker 通信。它调用 RawServer::add_task() Q向 RawServer d自己的Q务函?Rerequester::c()?/p>

3、EncoderQ一U?Handlerc(在分?tracker 服务器时候提刎ͼQ负责处理与其它peers建立q接和以及对d的数据按照BT对等协议q行分析?/p>

Encoder cdEncrypter.py中,该文件中Q还有一?Connection c,而在 Connecter.py 文g中,也有一? Connection c,q两个同名的 Connection cL些蹊PZ区分Q我把它们重新命名ؓ E-Connection ? C-Connection?/p>


3.1、E-ConnectionQ负?TCP 层次上的q接工作
q两? Connection 是有区别的,q是因ؓBT对等协议需要在两个层次上徏立连接。首先是 TCP 层次上的q接Q也是l过 TCP 的三ơ握手之后,建立q接Q这个连接由 E-Connection 来管理。在 Encoder:: external_connection_made() 函数中可以看刎ͼ一旦有外部q接到来Q则创徏一?E-Connection cR?/p>

3.2、C-ConnectionQ管理对{协议层ơ上的连接?br>?TCP q接之上Q是 BT对等协议的连接,它需要经qBT对等协议的两?#8220;握手”Q握手的l节大家ȝBT对等协议。过E是q样的:
Z便于q说Q我们假设一个BT客户端ؓ AQ另一个客L(fng)?X?br>? 果是Xd向A发vq接Q那么在TCPq接建立之后QA立刻利用q个q接向X发送BT对等协议?#8220;握手”消息。同PX在连接一旦徏立之后,? A发送BT对等协议?#8220;握手”消息。A一旦接收到X?#8220;握手”消息Q那么它?yu)p?#8220;握手”成功Q徏立了BT对等协议层次上的q接。我把它叫做“对等q? ?#8221;。A 发送了一个消息,同时接收了一个消息,所以这个握手过E是两次“握手”?br>同样Q对X 来说Q因接是它主动发L(fng)Q所以它在发送完“握手”消息之后Q就{待A?#8220;握手”消息Q如果收刎ͼ那么它也认ؓ“对等q接”建立了?br>一?#8220;对等q接”建立之后Q双方就可以通过q个q接传递消息了?/p>

q样Q原来我所疑惑的一个问题也有了答案。就是:如果 X 需要从 A q里下蝲数据Q那么它会同 A 建立一个连接。假?A 又希望从 X 那里下蝲数据Q它需不需要重新向 X 发v另外一个连接了Q答案显然是不用Q它会利用已有的一条连接?/p>

也就是说Q不是Xd向A发v的连接,q是 A d?X发v的连接,一旦徏立之后,它们的效果是一L(fng)。这个同我们qx?C/Sl构的网l开发是有区别的?/p>

我们可以看到?E-Connection的初始化函数中,会主动连接的另一方发?#8220;握手”消息Q在 E-Connection::data_came_in() 中,会首先对Ҏ(gu)?#8220;握手”消息q行处理。这正是我上面所描述的情形?br>?E-Connection::read_peer_id() 中,是对“握手”消息的最后一?peer idq行处理Q一旦正无误,那么p?#8220;对等q接”完成Q?/p>

self.encoder.connecter.connection_made(self)

?Connecter::connection_made() 函数中,创Z理“对等q接”?C-ConnectinoncR所以,更高一层的“对等q接”是由 C-Connection 来管理的?/p>

3.3、ConnecterQ连接器Q管理下载、上传、阻塞、片断选择、读写磁盘等{?/p>

下蝲和上传不是孤立的Q它们之间相互媄响。下载需要有片断选择法Q上传的时候要考虑dQ片断下载之后,要写到磁盘上。上传的时候,也需要从盘d?br>q些dQ是?Connecter 来统一调度的?/p>


cdơ结构,我用~进来表CZU包含关pR?/p>

Encoder:
    E-Connection
        C-Connection
            Upload
            SingleDownloader
        Connecter
            ChokerQ负责阻塞的理
            DownloaderQ?br>                SingleDownloader
                PickerQ片断选择{略
                StorageWrapperQ?/p>

 

先写q些吧,有什么我再补充进来?/p>

]]>
Tracker 服务器源码分析之二:RawServerc?/title><link>http://www.tkk7.com/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.tkk7.com/kuxiaoku/archive/2007/01/19/94812.html</guid><wfw:comment>http://www.tkk7.com/kuxiaoku/comments/94812.html</wfw:comment><comments>http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94812.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/kuxiaoku/comments/commentRss/94812.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/kuxiaoku/services/trackbacks/94812.html</trackback:ping><description><![CDATA[<p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">q篇文章Q我们来分析</span> <span lang="EN"> RawServer </span> <span style="font-family: 宋体;">以及一些相关的cR?/span> <span lang="EN">RawServer </span> <span style="font-family: 宋体;">cȝ实现代码Q在</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"> <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: 宋体;">q个cȝ作用是实C个网l服务器。关于网l编E的知识Q?/span> <span lang="EN">unix</span> <span style="font-family: 宋体;">|络~程Q卷</span> <span lang="EN">1</span> <span style="font-family: 宋体;">》是最l典的书c,你如果对q块不了解,抽时间看看这本书?/span> <span lang="EN">RawServer </span> <span style="font-family: 宋体;">实现的是一U事件多路复用、非d的网l模型。它使用的是</span> <span lang="EN"> poll() </span> <span style="font-family: 宋体;">Q而不是我们常见的</span> <span lang="EN">select()</span> <span style="font-family: 宋体;">Q关?/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: 宋体;">|络~程Q卷</span> <span lang="EN">1</span> <span style="font-family: 宋体;">》中有介l)函数Q处理过E大致是q样的:</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: 宋体;">Q然后将q个</span> <span lang="EN"> socket </span> <span style="font-family: 宋体;">加入</span> <span lang="EN"> poll </span> <span style="font-family: 宋体;">的事件源Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">随后q入服务处理循环Q即Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">函数Q这个函CdQ直到网l上有某些事件发生或者超时才q回l调用者;</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: 宋体;">q回之后Q先查一下是否有没有处理的Q务,如果有,那么先完成这些Q务。然后根据事件类型进行处理?/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: 宋体;">事gQ到来,?/span> <span lang="EN"> accept</span> <span style="font-family: 宋体;">q个hQ如?/span> <span lang="EN"> accept </span> <span style="font-family: 宋体;">成功Q那么就和一?/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: 宋体;">的事件源Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">如果在已l徏立的q接上(q接</span> <span lang="EN">socket</span> <span style="font-family: 宋体;">上的</span> <span lang="EN">POLLIN</span> <span style="font-family: 宋体;">事gQ,有数据可读,那么数据从</span> <span lang="EN"> client </span> <span style="font-family: 宋体;">端读q来Q做q一步处理;</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">如果已经建立的连接已l准备好Q连?/span> <span lang="EN">socket</span> <span style="font-family: 宋体;">上的</span> <span lang="EN">POLLOUT</span> <span style="font-family: 宋体;">事gQ,可以发送数据,则检查是否有数据需要发送,如果有,那么发送数据给</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"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">Q所以,</span> <span lang="EN">tracker</span> <span style="font-family: 宋体;">是一个单q程的服务器Qƈ没有用到U程。)</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">认ؓ软g的可l护性非帔R要,使代码易于维护的重要一条就是设计可重用的类Q?/span> <span lang="EN">RawServer </span> <span style="font-family: 宋体;">在设计的时候,充分考虑C可重用性,集中表现在两个地方:</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;">  </span></span> <span style="font-family: 宋体;">网l?/span> <span lang="EN"> I/O </span> <span style="font-family: 宋体;">和数据分析处理分R?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 39pt;"> <span style="font-family: 宋体;">|络服务器的事g多\复用、网l?/span> <span lang="EN">I/O </span> <span style="font-family: 宋体;">部分通常是固定不变的Q而数据在d之后Q进行分析处理的q程则是可变的?/span> <span lang="EN">RawServer </span> <span style="font-family: 宋体;">可变的数据处理工作Q交l另外一个抽象的c?/span> <span lang="EN"> Handler </span> <span style="font-family: 宋体;">Q实际上q没有这么一个类Q来处理。比如,?/span> <span lang="EN"> tracker </span> <span style="font-family: 宋体;">服务器的实现中,具体使用的就?/span> <span lang="EN"> HTTPHandler </span> <span style="font-family: 宋体;">c,而在</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: 宋体;">cR?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 39pt;"> <span lang="EN"> <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;">  </span></span> <span style="font-family: 宋体;">采用d队列来抽象出d处理的过E?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 39pt;"> <span lang="EN">RawServer</span> <span style="font-family: 宋体;">l护了一个Q务队?/span> <span lang="EN">unscheduled_tasks</span> <span style="font-family: 宋体;">Q实际是一个二元组?/span> <span lang="EN">list</span> <span style="font-family: 宋体;">Q二元组的第一Ҏ(gu)一个函敎ͼW二Ҏ(gu)时旉Q。在初始化的时候,首先向这个队列中加入一个Q务:</span> <span lang="EN">scan_for_timeouts()</span> <span style="font-family: 宋体;">Q这P每隔一D|_服务器就会去查一下是否有q接时。如果有其它</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">的成员函CQ对外暴露的有:</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <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;">       </span></span> <span lang="EN">__init__</span> <span style="font-family: 宋体;">Q(初始化函敎ͼ</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <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;">       </span></span> <span lang="EN">add_task()</span> <span style="font-family: 宋体;">Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <span>       </span> </span> <span style="font-family: 宋体;">在Q务列表中增加一Q务(一个Q务是一个函C及一个指定的时旉的组合)</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <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;">       </span></span> <span lang="EN">bind()</span> <span style="font-family: 宋体;">Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <span>       </span> </span> <span style="font-family: 宋体;">首先创徏一?/span> <span lang="EN">socket</span> <span style="font-family: 宋体;">Q然后设|?/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: 宋体;">Q这两个属性的具体含义请参考?/span> <span lang="EN">unix</span> <span style="font-family: 宋体;">|络~程Q卷</span> <span lang="EN">1</span> <span style="font-family: 宋体;">》,另外q将</span> <span lang="EN"> socket </span> <span style="font-family: 宋体;">讄为非d的。相对于d?/span> <span lang="EN"> socket</span> <span style="font-family: 宋体;">来说Q非d?/span> <span lang="EN"> socket </span> <span style="font-family: 宋体;">在网l?/span> <span lang="EN"> I/O </span> <span style="font-family: 宋体;">性能上要提高许多Q但是与此同Ӟ~程的复杂度也要提高一些。象</span> <span lang="EN"> tracker</span> <span style="font-family: 宋体;">q种可能同时要处理成千上万个q发q接的服务器Q只能采用非d?/span> <span lang="EN">socket</span> <span style="font-family: 宋体;">?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <span>       </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: 宋体;">已经端口l定Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <span>       </span> </span> <span style="font-family: 宋体;">最后把q个</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"> <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;">       </span></span> <span lang="EN">start_connection()</span> <span style="font-family: 宋体;">Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <span>       </span> </span> <span style="font-family: 宋体;">对外d建立一个连接,q个函数在处?/span> <span lang="EN">NAT</span> <span style="font-family: 宋体;">I越的时候用CQ我们后面分析到</span> <span lang="EN"> NAT</span> <span style="font-family: 宋体;">I越的时候,再具体讲解?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <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;">       </span></span> <span lang="EN">listen_forever()</span> <span style="font-family: 宋体;">Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <span>       </span> </span> <span style="font-family: 宋体;">q个函数的功能就是实C我在前面描述的网l服务器的处理过E。我们看刎ͼ它唯一的参数是</span> <span lang="EN">handler</span> <span style="font-family: 宋体;">Q?/span> <span lang="EN">handler</span> <span style="font-family: 宋体;">的作用就是封装了Ҏ(gu)据的具体处理?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN">listen_forever()</span> <span style="font-family: 宋体;">把对|络事g的处理过E,交给?/span> <span lang="EN"> handle_events()</span> <span style="font-family: 宋体;">?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">其它函数Q包?/span> <span lang="EN">handle_events()</span> <span style="font-family: 宋体;">Q都是内部函敎ͼ也就是外部不会直接来调用q些函数Q?/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: 宋体;">q样的保护机Ӟ</span> <span lang="EN">python</span> <span style="font-family: 宋体;">cȝ内部函数命名的惯例是以下划线开始,例如</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"> <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;">       </span></span> <span lang="EN">handle_events()</span> <span style="font-family: 宋体;">Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">事g处理q程Q主要是Ҏ(gu)三种不同的网l事件分别处理,一是连接事Ӟ二是M件、三是写事g?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span lang="EN"> <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"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">q段代码判断发生事g?/span> <span lang="EN">socket</span> <span style="font-family: 宋体;">是否是监?/span> <span lang="EN"> socket</span> <span style="font-family: 宋体;">Q如果是Q那么说明是q接事g?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">q接事g的处理:</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: 宋体;">来接受连接,q将新徏立的</span> <span lang="EN"> socket </span> <span style="font-family: 宋体;">讄为非d?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">判断当前q接数是否已l达C最大|Z限制q发q接的数目,在初始化</span> <span lang="EN"> RawServer</span> <span style="font-family: 宋体;">的时候,需要指定最大连接数目)Q如果已l达到最大|那么关闭q个新徏的连接?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">否则Q根据新?/span> <span lang="EN"> socket </span> <span style="font-family: 宋体;">创徏一?/span> <span lang="EN"> SingleSocket </span> <span style="font-family: 宋体;">对象Q(</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: 宋体;">函数Q关于这个函敎ͼ在后面分?/span> <span lang="EN"> HTTPHandler </span> <span style="font-family: 宋体;">时再讨论?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span lang="EN"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span lang="EN">if (event & POLLIN) != 0:</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">q段代码判断是否是读事g</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">M件的处理Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">首先h一下连接的最后更新时?/span> <span style="font-family: 宋体;">Q?/span> <span lang="EN">last_hit</span> <span style="font-family: 宋体;">Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">然后d数据Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">如果什么也没读刎ͼ那么说明q接被关闭了Q在|络~程中,如果一个连接正常的被关闭,那么Q也会触发读事gQ只不过什么也M刎ͼ</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">否则Q调?/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"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span lang="EN">if (event & 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: 宋体;">q段代码判断是否是写事gQ而且实有数据需要发送。在一个连接可以写的时候,׃发生写事件?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">写事件的处理Q?/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: 宋体;">在一个非d的连接上发送指定大的数据Q很可能在一ơ发送过E中Q数据没有被完全发送出去(只发送了一部分Q就q回了,所以,每次</span> <span lang="EN"> write</span> <span style="font-family: 宋体;">之后Q必d断是否完全发送了数据。如果没有发送完Q那么下ơ有M件的时候,q得回来l箋发送未完得数据。这也是q个函数叫做</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: 宋体;">的事件源。如果数据全部发送完毕了Q那么只需要监听读事gQ?/span> <span lang="EN">POLLIN</span> <span style="font-family: 宋体;">Q否则,既要监听MӞ也要监听写事Ӟ</span> <span lang="EN">POLLOUT</span> <span style="font-family: 宋体;">Q,q样Q一旦连接变的可写,可以l箋剩下的数据发送出厅R?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span lang="EN"> <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;">       </span></span> <span lang="EN">scan_for_timeouts()</span> <span style="font-family: 宋体;">Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">d处理函数Q它首先把自w加入未处理d队列中,q样Q经q一D|_可以保证q个函数再次被调用,从而达到周期性调用的效果?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">它检查每个连接是否超q指定时间没有被hQ如果是Q则该连接可能已l僵死,那么它关闭这个连接?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span lang="EN"> <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;">       </span></span> <span lang="EN">pop_unscheduled()</span> <span style="font-family: 宋体;">Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt 21pt;"> <span style="font-family: 宋体;">从Q务列表中弹出一个未处理的Q务?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <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: 宋体;">c,q是一个辅助类Q主要目的是装?/span> <span lang="EN"> socket</span> <span style="font-family: 宋体;">的处理吧。包括数据的发送,都交l它来处理了。这个类比较单,大家可以自己ȝQ我׃|嗦了?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">的具体实现的一个分析,可能读者看的还是晕晕糊p,没办法,q是必须自己ȝ源代码,然后在遇到问题的时候,回头再来看这文章,才会有帮助。如果不亲自看源码,l究是纸上谈c?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">装了网l服务器的实现细节,它实C一U事件多路处理、非d的网l模型。它主要负责建立新的q接Q从|络d和发送数据,而对d的数据的具体处理工作Q交l?/span> <span lang="EN"> Handler </span> <span style="font-family: 宋体;">cL处理Q从而把|络</span> <span lang="EN">I/O</span> <span style="font-family: 宋体;">和数据处理分d来,使得</span> <span lang="EN"> RawServer</span> <span style="font-family: 宋体;">可以重用?/span> <span lang="EN">Handler </span> <span style="font-family: 宋体;">cL在调?/span> <span lang="EN"> listen_forever() </span> <span style="font-family: 宋体;">的时候,p用者传递进来的Q具体到</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: 宋体;">Q?/span> <span lang="EN">tracker </span> <span style="font-family: 宋体;">可以作Z个网l服务器q行了?/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: 宋体;">cd</span> <span lang="EN">Tracker</span> <span style="font-family: 宋体;">cR?/span> </p><img src ="http://www.tkk7.com/kuxiaoku/aggbug/94812.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/kuxiaoku/" target="_blank">苦笑?/a> 2007-01-19 00:18 <a href="http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94812.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tracker 服务器源码分析之三:HTTPHandler c?/title><link>http://www.tkk7.com/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.tkk7.com/kuxiaoku/archive/2007/01/19/94813.html</guid><wfw:comment>http://www.tkk7.com/kuxiaoku/comments/94813.html</wfw:comment><comments>http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94813.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/kuxiaoku/comments/commentRss/94813.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/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: 宋体;">c,它在</span> <span lang="EN"> HTTPHandler.py </span> <span style="font-family: 宋体;">文g中?/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: 宋体;">只负责网l?/span> <span lang="EN"> I/O</span> <span style="font-family: 宋体;">Q也是从网l上d和发送数据,至于d的数据如何分析,以及应该发送什么样的数据,则交l?/span> <span lang="EN"> Handler </span> <span style="font-family: 宋体;">cL处理。如果是?/span> <span lang="EN"> c++ </span> <span style="font-family: 宋体;">来实现的话,那么</span> <span lang="EN"> Handler </span> <span style="font-family: 宋体;">应该是一个接口类Q提供几个虚函数作ؓ接口Q,但是</span> <span lang="EN"> python </span> <span style="font-family: 宋体;">动态语a的特性,q不需要专门定义这么一个接口类Q所以实际上q没?/span> <span lang="EN"> Handler </span> <span style="font-family: 宋体;">q么一个类。Q何一个提供了以下成员函数的类Q都可以作ؓ一?/span> <span lang="EN"> Handler </span> <span style="font-family: 宋体;">cL?/span> <span lang="EN"> RawServer </span> <span style="font-family: 宋体;">配合Q它们是Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">Q在建立新的q接的时候被调用</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN">data_came_in()</span> <span style="font-family: 宋体;">Q连接上有数据可ȝ时候被调用</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN">connection_flushed()</span> <span style="font-family: 宋体;">Q当在某个连接上发送完数据之后被调?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <span>       </span>HTTPHandler </span> <span style="font-family: 宋体;">是q样一?/span> <span lang="EN"> Handler </span> <span style="font-family: 宋体;">c,它具备以上接口?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <span>       </span>HTTPHandler </span> <span style="font-family: 宋体;">代码很少Q因为它把主要工作又交给</span> <span lang="EN"> HTTPConnection </span> <span style="font-family: 宋体;">了?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <span>       </span> </span> <span style="font-family: 宋体;">我们?/span> <span lang="EN"> HTTPHandler </span> <span style="font-family: 宋体;">cȝq几个函敎ͼ</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <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;">         </span></span> <span lang="EN">external_connection_made()</span> <span style="font-family: 宋体;">Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">每当新来一个连接的时候,创Z?/span> <span lang="EN"> HTTPConnection </span> <span style="font-family: 宋体;">cR?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt;"> <span lang="EN"> <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;">         </span></span> <span lang="EN">data_came_in()</span> <span style="font-family: 宋体;">Q?/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"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">我们知道Q?/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: 宋体;">协议分ؓhQ?/span> <span lang="EN">request</span> <span style="font-family: 宋体;">Q和响应Q?/span> <span lang="EN">response</span> <span style="font-family: 宋体;">Q,具体的协议请看相关的</span> <span lang="EN"> RFC </span> <span style="font-family: 宋体;">文档。我q里单讲一下?/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: 宋体;">h?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">h以行为单位,行的l束W是“回R换行”Q也是</span> <span lang="EN"> ascii </span> <span style="font-family: 宋体;">字符</span> <span style="font-family: 宋体;">“</span> <span lang="EN">\r</span> <span style="font-family: 宋体;">”?#8220;</span> <span lang="EN">\n</span> <span style="font-family: 宋体;">”?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">W一行是h?/span> <span lang="EN"> URL</span> <span style="font-family: 宋体;">Q例如:</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN">GET<span>       </span><span>       </span>/announce?ip=aaaaa;port=bbbbbbb<span>       </span>HTTP/1.0</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">q行数据被空格分Z部分Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">W一部分</span> <span lang="EN">GET</span> <span style="font-family: 宋体;">表示命oQ其它命令还?/span> <span lang="EN">POST</span> <span style="font-family: 宋体;">?/span> <span lang="EN">HEAD</span> <span style="font-family: 宋体;">{等Q常用的是</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: 宋体;">W二部分是请求的</span> <span lang="EN">URL</span> <span style="font-family: 宋体;">Q这里是</span> <span lang="EN">/announce?ip=aaaaa;port=bbbbbbb</span> <span style="font-family: 宋体;">。如果是普通的上网览|页Q那?/span> <span lang="EN">URL </span> <span style="font-family: 宋体;">是我们要看的网在?/span> <span lang="EN">web</span> <span style="font-family: 宋体;">服务器上的相对\径。但是,q里?/span> <span lang="EN">URL</span> <span style="font-family: 宋体;">仅仅是交互信息的一U方式,</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: 宋体;">Q更详细的信息请?#8220;</span> <span lang="EN">BT</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: 宋体;">W三部分?/span> <span lang="EN">HTTP</span> <span style="font-family: 宋体;">协议的版本号Q在E序中忽略?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">协议的消息头部分Q例如:</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"> <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: 宋体;">端的一些信息,q其中比较重要的是</span> <span lang="EN"> Accept-encoding</span> <span style="font-family: 宋体;">Q如果是</span> <span lang="EN"> gzip </span> <span style="font-family: 宋体;">Q那么说?/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: 宋体;">把响应数据压~之后再传回去,以减网l流量。我们可以在代码中看到相应的处理?/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: 宋体;">命o来说Q消息头的结束,也就意味着整个</span> <span lang="EN">client</span> <span style="font-family: 宋体;">端的hl束了。而对</span> <span lang="EN"> POST </span> <span style="font-family: 宋体;">命o来说Q可能后面还跟着其它数据。由于我们的</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: 宋体;">命oQ所以在协议处理q程中,如果遇到IQ那么就表示处理l束?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">用一个@环来q行协议分析Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">首先是寻找行l束W号Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">Q我认ؓ仅仅?/span> <span style="font-family: 宋体;">“</span> <span lang="EN">\n</span> <span style="font-family: 宋体;">”q不严}Q应该找</span> <span style="font-family: 宋体;">“</span> <span lang="EN">\r\n</span> <span style="font-family: 宋体;">”q个序列Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">如果没有扑ֈQ那?/span> <span lang="EN"> index() </span> <span style="font-family: 宋体;">函数会抛Z个异常,而异常的处理是返?/span> <span lang="EN"> True</span> <span style="font-family: 宋体;">Q表C数据不够,需要l读数据?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">如果扑ֈ了,那么</span> <span lang="EN">i <span> </span></span> <span style="font-family: 宋体;">之前的字W串是完整的一行。于是调用协议处理函敎ͼ代码是:</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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"> <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"> <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"> <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: 宋体;">是用来保存协议处理函数的Q所以,W一个被调用的协议处理函数就?/span> <span lang="EN"> read_type()</span> <span style="font-family: 宋体;">。它用来分析</span> <span lang="EN">client</span> <span style="font-family: 宋体;">端请求的W一行。在</span> <span lang="EN"> read_type() </span> <span style="font-family: 宋体;">的最后,我们看到Q?/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"> <o:p></o:p></span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">q样Q在下一ơ调?/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: 宋体;">协议的消息头q行分析?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">Q?/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: 宋体;">命o中的</span> <span lang="EN"> URL </span> <span style="font-family: 宋体;">部分保存?/span> <span lang="EN"> self.path</span> <span style="font-family: 宋体;">中,因ؓq是</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: 宋体;">命oQ如果不是,那么说明数据有错误。返?/span> <span lang="EN">None</span> <span style="font-family: 宋体;">Q否?/span> <span lang="EN">return self.read_header</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">Q?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">q其中,最重要的就是对I的处理,因ؓ前面说了Q空行表C协议分析结束?/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: 宋体;">~码之后Q调用:</span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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"> <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: 宋体;">Q也是_真正?/span> <span lang="EN"> client </span> <span style="font-family: 宋体;">端发来的hq行分析Q以及决定如何响应,是由</span> <span lang="EN">Tracker </span> <span style="font-family: 宋体;">来决定的。是的,q个</span> <span lang="EN"> Tracker </span> <span style="font-family: 宋体;">在我?/span> <span lang="EN">tracker </span> <span style="font-family: 宋体;">服务器源码分析系列的W一文章中已l看C。在创徏</span> <span lang="EN"> RawServer </span> <span style="font-family: 宋体;">之后Q马上就创徏了一?/span> <span lang="EN"> Tracker </span> <span style="font-family: 宋体;">对象。所以,要了?/span> <span lang="EN"> tracker </span> <span style="font-family: 宋体;">服务器到底是如何工作的,需要我们深入进d?/span> <span lang="EN"> Tracker </span> <span style="font-family: 宋体;">c,那就是我们下一文章的工作了?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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: 宋体;">之后Q返回的是决定响应给</span> <span lang="EN"> client </span> <span style="font-family: 宋体;">端的数据Q?/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: 宋体;">来把q些数据发送给</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"> <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: 宋体;">cȝ文章中一q讲解?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span lang="EN"> <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;">         </span></span> <span lang="EN">connection_flushed()</span> <span style="font-family: 宋体;">Q?/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: 宋体;">Q所以不能保证在一ơ发送数据的操作中,把要发送的数据全部发送出厅R?/span> </p> <p class="MsoNormal" style="margin: 0cm 0cm 0pt; text-indent: 21pt;"> <span style="font-family: 宋体;">q个函数Q检查在某个q接上需要发送的数据Q是否已l全部被发送出MQ如果是的话Q那么关闭这个连接的发送端。(Z么仅仅关闭发送端Q而不是完全关闭这个连接了Q疑惑)?/span> </p><img src ="http://www.tkk7.com/kuxiaoku/aggbug/94813.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/kuxiaoku/" target="_blank">苦笑?/a> 2007-01-19 00:18 <a href="http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94813.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tracker 服务器源码分析之四:Tracker c?/title><link>http://www.tkk7.com/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.tkk7.com/kuxiaoku/archive/2007/01/19/94814.html</guid><wfw:comment>http://www.tkk7.com/kuxiaoku/comments/94814.html</wfw:comment><comments>http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94814.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.tkk7.com/kuxiaoku/comments/commentRss/94814.html</wfw:commentRss><trackback:ping>http://www.tkk7.com/kuxiaoku/services/trackbacks/94814.html</trackback:ping><description><![CDATA[     摘要: 本篇文章分析 Tracker c,它在 track.py 文g中? 在分析之前,我们把前几篇文章的内容再回顾一下,以理清思\?   BT 的源码,主要可以分ؓ两个部分Q一部分用来实现 tracker 服务器,另一部分用来实现 BT 的客L(fng)。我们这个系列的文章围绕 tracker 服务器的实现来展开? BT ...  <a href='http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94814.html'>阅读全文</a><img src ="http://www.tkk7.com/kuxiaoku/aggbug/94814.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.tkk7.com/kuxiaoku/" target="_blank">苦笑?/a> 2007-01-19 00:18 <a href="http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94814.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Tracker 服务器源码分析之一Q总述http://www.tkk7.com/kuxiaoku/archive/2007/01/19/94811.html苦笑?/dc:creator>苦笑?/author>Thu, 18 Jan 2007 16:17:00 GMThttp://www.tkk7.com/kuxiaoku/archive/2007/01/19/94811.htmlhttp://www.tkk7.com/kuxiaoku/comments/94811.htmlhttp://www.tkk7.com/kuxiaoku/archive/2007/01/19/94811.html#Feedback0http://www.tkk7.com/kuxiaoku/comments/commentRss/94811.htmlhttp://www.tkk7.com/kuxiaoku/services/trackbacks/94811.htmltracker 服务器是 BT 下蝲中必ȝ角色。一?/span> BT client 在下载开始以及下载进行的q程中,要不停的?/span> tracker 服务器进行通信Q以报告自己的信息,q获取其它下?/span> client 的信息。这U通信是通过 HTTP 协议q行的,又被UCؓ tracker  HTTP 协议Q它的过E是q样的:

       client ?/span> tracker 发一?/span> HTTP ?/span> GET hQƈ把它自己的信息放?/span> GET 的参CQ这个请求的大致意思是Q我?/span> xxx Q一个唯一?/span> id Q,我想下蝲 yyy 文gQ我?/span> ip ?/span> aaa Q我用的端口?/span> bbb 。。?/span>

       tracker Ҏ(gu)有下载者的信息q行l护Q当它收C个请求后Q首先把Ҏ(gu)的信息记录下来(如果已经记录在案Q那么就查是否需要更斎ͼQ然后将一部分Qƈ非全部,Ҏ(gu)讄的参数已l下载者的hQ参与下载同一个文Ӟ一?/span> tracker 服务器可能同时维护多个文件的下蝲Q的下蝲者的信息q回l对斏V?/span>

       Client 在收?/span> tracker 的响应后Q就能获取其它下载者的信息Q那么它?yu)可以根据这些信息,与其它下载者徏立连接,从它们那里下载文件片断?/span>

 

关于 client ?/span> tracker 之间通信协议的细节,?#8220; BT 协议规范”中已l给出,q里不再重复。下面我们具体分?/span> tracker 服务器的实现l节?/span>

 

从哪里开始?

       要徏立一?/span> tracker 服务器,只要q行 bttrack.py E序p了,它最需要一个参敎ͼ是 –dfile Q这个参数指定了保存下蝲信息的文件?/span> Bttrack.py 调用 track.py 中的 track() 函数。因此,我们跟踪?/span> track.py 中去?/span> track() 函数?/span>

 

Track.py Q?/span> track()

       q个函数首先对命令行的参数进行检查;然后这些参C存到 config 字典中。在 BT 中所有的工具E序Q都有类似的处理方式?/span>

 

接下来的代码Q?/span>

       r = RawServer(Event(), config['timeout_check_interval'], config['socket_timeout'])

    t = Tracker(config, r)

    r.bind(config['port'], config['bind'], True)

    r.listen_forever(HTTPHandler(t.get, config['min_time_between_log_flushes']))

    t.save_dfile()

 

首先是创Z?/span> RawServer 对象Q这是一个服务器对象Q它?yu)实C个网l服务器的一些细节封装v来。不?/span> tracker 服务器用C RawServer Q我们以后还可以看到Q由于每?/span> client 端也需要给其它 client 提供下蝲服务Q因此也同时是一个服务器Q?/span> client 的实CQ也用到?/span> RawServer Q这P RawServer 的代码得C重用。关?/span> RawServer 的详l实玎ͼ在后面的节中进行分析?/span>

接着是创Z?/span> Tracker 对象?/span>

然后?/span> RawServer l定在指定的端口上(通过命o行传递进来)?/span>

最后,调用 RawServer::listen_forever() 函数Q得服务器投入q行?/span>

最后,在服务器因某些原因结束运行以后,调用 Tracker::save_dfile() 保存下蝲信息。这P一旦服务器再次投入q行Q可以恢复当前的状态?/span>

 

 

其它信息Q?/span>

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;">  BT 源码的分布:

?/span> BT 的源码展开之后Q可以看到有一?/span> python E序Q还有一些说明文件等{,此外q有一?/span> BitTorrent 目录。这?/span> python E序Q实际是一些小工具Q比如制?/span> file ?/span> btmakefile.py 、运?/span> tracker 服务器的 bttrack.py 、运?/span> BT client 端的 btdownloadheadless.py {等。而这些程序中Q用到的一?/span> python cȝ实现Q都攑֜子目?/span> BitTorrent 下面。我们的分析工作Q通常是从工具E序入手Q比?/span> bttrack.py Q而随着分析的展开Q则重点是看 BitTorrenet 子目录下的代码?/span>

BT 作?/span> Bram Cohen 在谈到如何开发可l护的代码的一文章中Q?/span> http://www.advogato.org/article/258.html Q,其中提到的一条就是开发一些小工具以简化工作,我想 BT 的这U源码结构,也正是作者思想的一U体现吧?/span>

 

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;">  我们看到Q?/span> python 和我们以前接触的 c/c++ 不一L(fng)W一个地方就是它的函数在定义的时候,不用指定参数cd。既然这P那么Q在调用函数的时候,你可以传递Q意类型的参数q来。例如这L(fng)函数Q?/span>

def foo(arg):

        print type(arg)

      

       你可以这h调用Q?/span>

       a = 100

       b = “hello world”

       foo(a)

       foo(b)

 

       输出l果是:

       <type ‘int’>

       <type ‘str’>

 

       q是因ؓQ第一ơ调?/span> foo() 的时候,传递的是一个整数类型,而第二次调用的时候,传递的是一个字W串cd?/span>

 

q种参数h动态类型的Ҏ(gu),?/span> c/c++ {传l的语言是所不具备的。这也是 python 被称为动态语a的一个原因吧?/span> C++ 的高U特性模板,虽然也得参数类型可以动态化Q但使用hQ远没有 python q么单方ѝ?/span>



]]>
վ֩ģ壺 avһ㽶| ޾ƷۺϾþ| ޾ƷƵ| ձһҹ| LƷþ| ޳߲va| ձѲ| ޾Һվ| ѧһëƬѿ| ޳_վͼƬ| ˬˬˬƵ| ޾Ʒ˳߲| AVӰ߹ۿ| ˳ɫ4444߹ۿ| ӰԺ߹ۿ| ޶ۺϾþ| Ʒ˸| ҹƵ| Ʒ߳| ߹ۿƵ| 91޾ƷƵ| ŷߴSUV| av뾫Ʒۺ| Ƶ| ƷҳѸ߹ۿ | þøԴվѿ| ŷ޹˾Ʒ| ߲ŸԲ| Ʒۿ˳| ˾þþƷҹ| ˳߹ۿվƷ| 91ҹƷһ| պĻ| վѴȫպ| ӰԺ߹ۿ| ѹۿ| 91ѹƵ| Avһ| Ʒҹѹۿ| վɫƵ߹ۿaվ| һëƬ߲|