pieces -build your own BitTorrentClient

eliasson/pieces: An experimental BitTorrent client in Python 3.5 (github.com)

了解BitTorrent:BitTorrent 简介 - 知乎 (zhihu.com)

总结项目结构:

  • pieces.py:启动程序,调用main()
  • cli.py:main(),解析运行参数并生成TorrentClient并运行下载任务
  • client.py:核心,实现TorrentClient,完成整个下载过程,主要实现访问tracker获取peer列表的过程。实现BlockPiecePieceManager,用于管理传输来的响应数据
  • protocol.py:PeerConnection,用于对每个peer进行连接。PeerStreamIterator异步解析字节流中BitTorrent协议信息。PeerMessage,基类,派生类有HandshakeKeepAliveBitFieldInterestedNotInterestedChokeUnchokeHaveRequestPieceCancel
  • tracker.py:封装tracker的响应信息,并实现连接tracker的方法
  • torrent.py:使用BenCode处理种子文件
  • becoding.py:实现BenCode的编解码

程序运行逻辑:

解析运行参数获得种子文件路径,处理种子文件获得tracker地址等信息,创建TorrentClient,使用异步IO库asyncio,创建TorrentClient.start()为协程的任务,使用signal.signal()处理中断信号,异步执行任务

loop = asyncio.get_event_loop()
client = TorrentClient(Torrent(args.torrent))
task = loop.create_task(client.start())

def signal_handler(*_):
    logging.info('Exiting, please wait until everything is shutdown...')
    client.stop()
    task.cancel()

signal.signal(signal.SIGINT, signal_handler)

任务内容是构建容纳多个PeerConnectionTorrentClient.peers数组:这些连接(此时还不知道peer地址)会异步地监控available_peers队列。然后进入死循环,根据默认间隔和之后tracker发来的呼叫间隔对tracker进行不断连接,从而实时更新available_peers,直到检测到完成下载

async def start(self):
    self.peers = [PeerConnection('''连接信息''')
                  for _ in range(MAX_PEER_CONNECTIONS)]
    while True:
        # 终止条件

        current = time.time()
        if (not previous) or (previous + interval < current):
            # 与tacker连接
            if response:
                # 更新呼叫间隔(由tacker传回)
                self._empty_queue()
                for peer in response.peers:
                    self.available_peers.put_nowait(peer)
                else:
                    await asyncio.sleep(5)
		self.stop()

每个peer连接监控available_peers队列,安全地取出tracker发来的peer的ip和port,异步连接,完成握手(根据种子文件中的哈希值确定要传输的pieces)后发送开始信号(Interested)开始数据传输,传输来的数据先经过PeerStreamIterator解析后异步地对每段消息进行处理(如:传来的是BitField时就调用piece_manager的添加peer方法,从而确定这个peer有哪些piece;传来的是Piece就调用回调函数_on_block_retrieved()将传来的piece写入文件)

self.future = asyncio.ensure_future(self._start())
async def _start(self):
    while 'stopped' not in self.my_state:
        ip, port = await self.queue.get()
        try:
            
            self.reader, self.writer = await asyncio.open_connection(ip, port)
            
            buffer = await self._handshake()
            
            self.my_state.append('choked')

            # 让对方知道我们有兴趣下载片段
            await self._send_interested()
            self.my_state.append('interested')

            # 只要连接打开并传输数据,就开始作为消息流读取响应
            async for message in PeerStreamIterator(self.reader, buffer):
                # 根据message的类型进行处理
         except ...
         self.cancel()

PeerStreamIterator是一个异步迭代器,通过读取套接字数据到缓冲池,然后分析缓冲池的message_id确定消息类型并返回对应类型的对象。

async def __anext__(self):
    # 从套接字读取数据。当我们有足够的数据来解析时,解析它并返回消息。在那之前,继续从流中读取
    while True:
        try:
            data = await self.reader.read(PeerStreamIterator.CHUNK_SIZE)
            if data:
                self.buffer += data
                message = self.parse()
                if message:
                    return message
             else:
                logging.debug('No data read from stream')
                if self.buffer:
                    message = self.parse()
                    if message:
                        return message
                    raise StopAsyncIteration()
         except ...

def parse(self):
    header_length = 4
    if len(self.buffer) > 4:  # 4 bytes is needed to identify the message
        message_length = struct.unpack('>I', self.buffer[0:4])[0]

        if message_length == 0:
            return KeepAlive()

        if len(self.buffer) >= message_length:
            message_id = struct.unpack('>b', self.buffer[4:5])[0]

            def _consume():
                """Consume the current message from the read buffer"""
                self.buffer = self.buffer[header_length + message_length:]

            def _data():
                """"从读缓冲区中提取当前消息"""
                return self.buffer[:header_length + message_length]

            if message_id is PeerMessage.BitField:
                data = _data()
                _consume()
                return BitField.decode(data)
            elif message_id is PeerMessage.Interested:
                # 之后分别列举可能的情况然后获取数据返回对应类型的对象
            else:
                logging.info('Unsupported message!')
            else:
                logging.debug('Not enough in buffer in order to parse')
                return None

细枝末节:

Signal模块

signal.signal(signalnum, handler):

  • signalnum:信号量,具体参看python文档
  • handler:信号处理程序,可以是自定义的函数,也可以是特殊值 signal.SIG_IGN、 signal.SIG_DFL之一

信号量signal.SIGPIPE:忽略默认的SIGPIPE处理函数(SIGPIPE默认处理为管道/套接字出错时终止进程,这里将其忽略),因此管道和套接字上的写错误可以像普通的 Python 异常一样报告

信号量signal.SIGINT:转换为 KeyboardInterrupt 异常(一般为ctrl+c引发的中断)。

——https://blog.csdn.net/sunjintaoxxx/article/details/122195042

def signal_handler(*_): # 原本应该是 XXX(signum, frame) 使用带星号参数
    logging.info('Exiting, please wait until everything is shutdown...')
    client.stop()
    task.cancel()

signal.signal(signal.SIGINT, signal_handler)

create_task

loop = asyncio.get_event_loop()
task = loop.create_task(任务函数) # 任务函数内含循环,称为协程
try:
    loop.run_until_complete(task)
except CancelledError:
    logging.warning('Event loop was canceled')

事件循环的run_until_complete方法运行事件循环时,当其中的全部任务完成后,会自动停止循环;若想无限运行事件循环,可使用asyncio提供的run_forever方法

这样就会阻塞在这里,直到协程完成

ensure_future

也是asyncio的一个方法,同样可以创建任务,区别在于:

那么用 ensure_future 还是 create_task 呢?先对比一下函数声明:

asyncio.ensure_future(coro_or_future, *, loop=None)
BaseEventLoop.create_task(coro)

显然,ensure_future 除了接受 coroutine 作为参数,还接受 future 作为参数。看 ensure_future 的代码,会发现 ensure_future 内部在某些条件下会调用 create_task,综上所述:

encure_future: 最高层的函数,推荐使用!

create_task: 在确定参数是 coroutine 的情况下可以使用。

——https://www.jianshu.com/p/ff1747d736cb

self.future = asyncio.ensure_future(self._start())

PeerConnection__init__函数中创建任务,同时异步地开启任务的执行,通过future对任务进行控制,如if not self.future.done():self.future.cancel()

异步迭代器

异步迭代器在Python中的作用是允许以异步的方式逐个返回元素,而不是一次性返回所有元素。这对于处理大量数据或需要等待IO操作完成的情况非常有用。

class AsyncIterator:
    def __init__(self, ...):
        ...
	
    async def __aiter_(self):
        # 返回一个迭代器,通常是 return self
    
    async def __anext__(self):
        # 返回下一个元素

# 使用
async def main():
    async for item in AsyncIterator([1, 2, 3, 4, 5]):
        print(item)

struct.pack / unpack

Python中struct.pack()和struct.unpack() - 知乎 (zhihu.com)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值