接下来将开始研究Iperf本身的实现细节。
Iperf工作机制
Iperf是基于Server-Client模式实现的。在测量网络参数时,Iperf区分听者(Audience)和说者(Speaker)两种角色。说者向听着发送一定量的数据,由听者统计并记录带宽、时延抖动等参数。说者的数据全部发送完成后,听者通过向说者回送一个数据包,将测量数据告知说者。这样,在听者和说者两边都可以显示记录的数据。如果网络过于拥塞或误码率较高,当听者回送的数据包无法被说者收到时,说者就无法显示完整的测量数据,而只能报告本地记录的部分网络参数,发送的数据量、发送时间、发送带宽等,像延时抖动等参数在说者一侧则无法获得(因此在报告时,服务器和客户端所得到的信息是不同的)。
Iperf提供了三种测量模式:normal, tradeoff, dualtest。对于每一种模式,用户都可以通过-P选项指定同时测量的并行线程数。以下的讨论假设用户设定的并行线程数为P个。
在normal模式下,Client生成P个说者线程,并行向Server发送数据。Server每接收到一个说者的数据,就生成一个听者线程,负责与该说者间的通信。Client有P个并行的说者线程,而Server端有P个并行的听者线程(针对这一Client),两者之间共有P个连接同时收发数据。测量结束后,Server端的每个听者向自己对应的说者回送测得的网络参数。
在tradeoff模式下,首先进行normal模式下的测量过程,然后Server和Client互换角色。Server生成P个说者,同时向Client发送数据。Client对应每个说者生成一个听者接收数据并测量参数。最后有Client端的听者向Server端的说者回馈测量结果。这样就可以测量两个方向上的网络参数了。
对于dualtest模式同样可以测量两个方向上的网络参数,与tradeoff模式的不同在于,在dualtest模式下,由Server到Client方向上的测量与由Client到Server方向上的测量是同时进行的。Client生成P个说者和P个听者,说者向Server端发送数据,听者等待接收Server端的说者发来的数据。Server端也进行相同的操作。在Server端和Client端之间同时存在2P个网络连接,其中有P个连接的数据由Client流向Server,另外P个连接的数据由Server流向Client。因此,dualtest模式需要的测量时间是tradeoff模式的一半。
在三种模式下,除了P个听者或说者进程,在Server和Client两侧均存在一个监控线程(monitor thread)。 监控线程的作用包括:
*生成说者或听者线程;
*同步所有说者或听者的动作(开始发送、结束发送等);
*计算并报告说有说者或听者的累计测量数据。
在监控线程的控制下,所有P个线程间就可以实现同步和信息共享。说者线程或听者线程向一个公共的数据区写入测量数据(此数据区位于实现监控线程的对象中),由监控线程读取并处理。通过互斥锁(mutex)实现对该数据区的同步访问。 Server可以同时接收来自不同Client的连接,这些连接是通过Client的IP地址标识的。Server将所有Client的连接信息组织成一个单向链表,每个Client对应链表中的一项,该项包含该Client的地址结构(sockaddr)以及实现与该Client对应的监控线程的对象(我们称它为监控对象),所有与此Client相关的听者对象和说者对象都是由该监控线程生成的。
perf中主要的类
Iperf的实现中主要的类及其相互间的派生关系如下图所示。
PerfSocket类
PerfSocket类以Socket为基类派生而来。该类实现了Iperf用于通信的大多数功能,像发送UDP包 (SendUDP),接收UDP包(Recv_UDP),发送TCP数据(Send_TCP),接收TCP数据(Recv_TCP)以及传输初始化、报告网络参数、发送/回复结束包等。
Notify类
Notify类是Iperf中的另一个主要基类。它实现了对多个并行线程的监控机制。Notify类提供了对所监控线程的开始动作、结束动作进行同步的方法。这些方法主要是通过条件变量(condition)机制实现的。同时,Notify类的实例还含有供被监控的线 程进行排他(exclusively)访问的数据区。一般是被监控的线程向该数据区中写入数据,由Notify类(或其派生类)的实例读取。
Listener类
Listener类以PerfSocket类和Thread类为基类,属多重继承。因为是Thread类的派生类,因此 一个Listener实例就是一个执行线程,又因为Listener类是PerfSocket类的派生类,因此它也具有收发socket数据的能力。在服务器端,在服务器程序启动后Listener就存在并开始工作。在客户端,如果用户指定进行双向测试(tradeoff或dualtest模式),也会生成Listener对象。Listener实现的主要功能是在设定的端口上监听连接请求,接收到请求后,若请求来自一个新的客户,Listener生成 一个Audience(听者监控线程,也可称为听者监控对象,见下文)实例来负责继续处理与该请求对应的后续操作,如果在请求中指明了要进行 tradeoff或dualtest模式的测量,Listener就生成一个Speaker(说者监控线程,也可称为说者监控对象,见下文)实例来负责发起到客户的反向连接;如果请求来自一个已经存在的客户,Listener通过该客户对应的Audience实例的方法使该Audience实例生成一个听者线程进行处理该连接(这发生在多线程并行测量的情况下)。完成这些工作后,Listener线程返回继续监听。
Audience类
Audience类以Thread类和Notify为基类,属多重继承。一个Audience实例对应一个执行线程且具有监控其他线程的功能。Audience实例(线程)的主要功能是作为听者线程的监控线程。它首先生成一个听者线程(听者线程通过Server类实现,见下文),之后负责维护并报告所用听者线程测量结果的累计值,在对听者线程的结束事件同步之后,Audience线程退出。Listener线程在接收到一 个已有客户的连接请求时,也会通过Audience实例生成新的Server实例。在服务器端,一个客户对应一个Audience实例,所有的Audience实例组织成一个链表。
Speaker类
Speaker类同Audience类一样,也以Thread类和Notify类为基类。它实现了说者线程的监控线程。它首先生成若干说者线程(听者线程通过Client类实现,见下文),对所有说者线程的开始事件进行同步,之后负责维护并报告所用说者线程测量结果的累计值,在对说者线程的结束事件同步之后,Speaker线程退出。此外,在Speaker线程看是运行时,还会根据是否进行双向测试生成Listener生成实例,具体情况会在下文中介绍。在客户端程序开始运行后,Speaker线程就开始工作了。当服务器端收到双向测试的请求时,也会生成Speaker实例(线程)。
Server类
Server类以PerfSocket类和Thread类为基类,实现了听者进程。它接收对端的说者对象发来的数据,记录数据量、延迟抖动等网络参数,打印测试结果,修改听者监控进程(Audience实例)的累计数据区,并在收到说者的最后一个数据包时将测试结果回送给说者进程。
Client类
Client类以PerfSocket类和Thread类为基类,实现了说者进程。它不断的向对端的听者进程发送数据,直到发送了指定的数据量或达到了指定的发送时间。此后Server实例发送最后一个标记了测量结束标志的数据包,并等待来自对端听者的测量结果。在发送数据的同时,Server进程周期性的更新说者监控线程(Speaker实例)的累计数据区,并打印测试结果。
综上,Client类与Server类对应,分别实现了说者进程和听者进程;Speaker类和Audience类对应,分别实现了说者监控线程和 听者监控线程。Listener类是监听线程;所有的Client实例是由Speaker实例生成的;而Server实例是由Listener实例触发 Audience生成的。