BitTorrent 协议规范(BT协议集合)十

Tracker服务器源码分析之二:RawServer类
作者:小马哥

这篇文章,我们来分析 RawServer 以及一些相关的类。RawServer 类的实现代码,在 BitTorrent 子目录的RawServer.py 中

RawServer 这个类的作用是实现一个网络服务器。关于网络编程的知识,《unix网络编程:卷1》是最经典的书籍,你如果对这块不了解,建议抽时间看看这本书。RawServer 实现的是一种事件多路复用、非阻塞的网络模型。它使用的是 poll() (而不是我们常见的select(),关于 poll和select的比较,也在《unix网络编程:卷1》中有介绍)函数,处理过程大致是这样的:

首先创建一个监听 socket,然后将这个 socket 加入 poll 的事件源;

随后进入服务处理循环,即:

调用 poll() 函数,这个函数会阻塞,直到网络上有某些事件发生或者超时才返回给调用者;

在 poll()返回之后,先检查一下是否有没有处理的任务,如果有,那么先完成这些任务。然后根据事件类型进行处理。

如果是连接请求(监听 socket上的POLLIN事件)到来,它 accept这个请求,如果 accept 成功,那么就和一个 client建立了连接,于是将 accept() 新创建的 socket 加入 poll 的事件源;

如果在已经建立的连接上(连接socket上的POLLIN事件),有数据可读,那么将数据从 client 端读过来,做进一步处理;

如果已经建立的连接已经准备好(连接socket上的POLLOUT事件),可以发送数据,则检查是否有数据需要发送,如果有,那么发送数据给 client 端。

(所以,tracker是一个单进程的服务器,并没有用到线程。)

Bram Cohen 认为软件的可维护性非常重要,使代码易于维护的重要一条就是设计可重用的类,RawServer 在设计的时候,充分考虑到了可重用性,集中表现在两个地方:

1、 将网络 I/O 和数据分析处理分离。

网络服务器的事件多路复用、网络I/O 部分通常是固定不变的,而数据在读取之后,进行分析处理的过程则是可变的。RawServer 将可变的数据处理工作,交给另外一个抽象的类 Handler (实际上并没有这么一个类)来处理。比如,在 tracker 服务器的实现中,具体使用的就是 HTTPHandler 类,而在 以后将要分析的 BT client 实现代码中,用到的具体的Handler 是 Encoder 类。

2、 采用任务队列来抽象出任务处理的过程。

RawServer维护了一个任务队列 unscheduled_tasks(实际是一个二元组的list,二元组的第一项是一个函数,第二项是超时时间)。在初始化的时候,首先向这个队列中加入一个任务:scan_for_timeouts(),这样,每隔一段时间,服务器就会去检查一下是否有连接超时。如果有其它


RawServer的成员函数中,对外暴露的有:

u     __init__:(初始化函数)

u     add_task():

  在任务列表中增加一项任务(一个任务是一个函数以及一个指定的超时时间的组合)

u     bind():

  首先创建一个socket,然后设置socket的属性: SO_REUSEADDR和IP_TOS,,这两个属性的具体含义请参考《unix网络编程:卷1》,另外还将 socket 设置为非阻塞的。相对于阻塞的 socket来说,非阻塞的 socket 在网络 I/O 性能上要提高许多,但是与此同时,编程的复杂度也要提高一些。象 tracker这种可能同时要处理成千上万个并发连接的服务器,只能采用非阻塞的socket。

  然后将该 socket和指定ip已经端口绑定;

  最后把这个socket 加入 poll的事件源。

u     start_connection():

  对外主动建立一个连接,这个函数在处理NAT穿越的时候用到了,我们后面分析到 NAT穿越的时候,再具体讲解。

u     listen_forever():

  这个函数的功能就是实现了我在前面描述的网络服务器的处理过程。我们看到,它唯一的参数是handler,handler的作用就是封装了对数据的具体处理。

listen_forever()把对网络事件的处理过程,交给了 handle_events()。

其它函数,包括handle_events(),都是内部函数(也就是外部不会直接来调用这些函数)。Python没有c++那样 public、protected、private 这样的保护机制,python类的内部函数命名的惯例是以下划线开始,例如 RawServer 中的 _close_dead()等。

u     handle_events():

事件处理过程,主要是根据三种不同的网络事件分别处理,一是连接事件,二是读事件、三是写事件。

if sock == self.server.fileno()

这段代码判断发生事件的socket是否是监听 socket,如果是,那么说明是连接事件。

连接事件的处理:

通过 accept 来接受连接,并将新建立的 socket 设置为非阻塞。

判断当前连接数是否已经达到了最大值(为了限制并发连接的数目,在初始化 RawServer的时候,需要指定最大连接数目),如果已经达到最大值,那么关闭这个新建的连接。

否则,根据新的 socket 创建一个 SingleSocket 对象,(SingleSocket 封装了对 socket的操作。)将这个对象加入内部的列表single_sockets中,以备后用。

将这个新 socket加入 poll 的事件源

最后,调用 Handler 的external_connection_made() 函数,关于这个函数,在后面分析 HTTPHandler 时再讨论。

if (event & POLLIN) != 0:

这段代码判断是否是读事件

读事件的处理:

首先刷新一下连接的最后更新时间 (last_hit)。

然后读取数据;

如果什么也没读到,那么说明连接被关闭了(在网络编程中,如果一个连接正常的被关闭,那么,也会触发读事件,只不过什么也读不到)

否则,调用 Handler的 data_came_in() 函数来处理读到的数据。

if (event & POLLOUT) != 0 and s.socket is not None and not s.is_flushed():

这段代码判断是否是写事件,而且确实有数据需要发送。在一个连接可以写的时候,就会发生写事件。

写事件的处理:

实际代码是在 SingleSocket的 try_write()函数中。

在一个非阻塞的连接上发送指定大小的数据,很可能在一次发送过程中,数据没有被完全发送出去(只发送了一部分)就返回了,所以,每次 write之后,必须判断是否完全发送了数据。如果没有发送完,那么下次有读事件的时候,还得回来继续发送未完得数据。这也是这个函数叫做 try_write 的原因吧。

try_write() 在最后,要重新设置 poll 的事件源。如果数据全部发送完毕了,那么只需要监听读事件(POLLIN)否则,既要监听读事件,也要监听写事件(POLLOUT),这样,一旦连接变的可写,可以继续将剩下的数据发送出去。

u     scan_for_timeouts():

任务处理函数,它首先把自身加入未处理任务队列中,这样,经过一段时间,可以保证这个函数再次被调用,从而达到周期性调用的效果。

它检查每个连接是否超过指定时间没有被刷新,如果是,则该连接可能已经僵死,那么它关闭这个连接。

u     pop_unscheduled():

从任务列表中弹出一个未处理的任务。

与 RawServer 配合使用的是 SingleSocket 类,这是一个辅助类,主要目的是封装对 socket的处理吧。包括数据的发送,都交给它来处理了。这个类比较简单,大家可以自己去看,我就不罗嗦了。

以上是对 RasServer 的具体实现的一个分析,可能读者看的还是晕晕糊糊,没办法,还是必须自己去看源代码,然后在遇到问题的时候,回头再来看这篇文章,才会有帮助。如果不亲自看源码,终究是纸上谈兵。

我们再来小结一下。

RawServer 封装了网络服务器的实现细节,它实现了一种事件多路处理、非阻塞的网络模型。它主要负责建立新的连接,从网络读取和发送数据,而对读到的数据的具体处理工作,交给 Handler 类来处理,从而把网络I/O和数据处理分离开来,使得 RawServer可以重用。Handler 类是在调用 listen_forever() 的时候,由调用者传递进来的,具体到 tracker服务器,就是HTTPHandler。有了 RawServer,tracker 就可以作为一个网络服务器运行了。 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值