Iperf 版本建议采用linux版本,事实上,windows版也很好用。Iperf 是一个 TCP/IP 和 UDP/IP 的性能测量工具,通过调整各种参数可以测试TCP的最大带宽,并报告带宽、延迟、最大段和最大传输单元大小等统计信息。Iperf可以运行于Linux/BSD、Unix及Windows等操作系统。
带宽测试通常采用UDP 模式,因为能测出极限带宽、时延抖动、丢包率。在进行测试时,首先以链路理论带宽作为数据发送速率进行测试,例如,从客户端到服务器之间的链路的理论带宽为100Mbps,先用-b 100M进行测试,然后根据测试结果(包括实际带宽,时延抖动和丢包率),再以实际带宽作为数据发送速率进行测试,会发现时延抖动和丢包率比第一次好很多,重复测试几次,就能得出稳定的实际带宽。
服务器端 iperf -u -s
客户端
iperf -u -c 192.168.1.1 -b 100M -t 60
在udp模式下,以100Mbps为数据发送速率,客户端到服务器192.168.1.1上传带宽测试,测试时间为60秒。
iperf -u -c 192.168.1.1 -b 5M -P 30 -t 60
客户端同时向服务器端发起30个连接线程,以5Mbps为数据发送速率。
iperf -u -c 192.168.1.1 -b 100M -d -t 60
以100M为数据发送速率,进行上下行带宽测试。
服务器端 iperf -s
客户端
iperf -c 192.168.1.1 -t 60
在tcp模式下,客户端到服务器192.168.1.1上传带宽测试,测试时间为60秒。
iperf -c 192.168.1.1 -P 30 -t 60
客户端同时向服务器端发起30个连接线程。
iperf -c 192.168.1.1 -d -t 60
进行上下行带宽测试。
Iperf主要的TCP功能是测试基于特定路径的TCP连接的性能,我们知道TCP连接调整最基本的措施是调整TCP窗口的大小,窗口大小控制在任何节点网络中可以存在的数据大小。如果该值太小,发送者将会在某段时间处于空闲状态,从而影响发送的性能。TCP窗口大小的理论值为链路瓶颈带宽与往返时延的乘积:
TCP_Window = Bottleneck_Bandwidth * Round_Trip_Time
例如链路瓶颈带宽为45Mbps,往返时延为42ms(可以通过ping来测试),那么窗口的理论值为:
45Mbps*42ms=(45*e6)*(42*e-3)=1890000 bits=230KByte
调节窗口大小即可以此理论值为基准,在该值上慢慢增大或减少,即可获得最好的结果。
Iperf 测试TCP带宽的原理较简单,即在客户端和服务器端建立连接(三次握手)后,客户端发送一定大小的数据报,并记下发送的时间,或者客户端在一定的时间内发送数据,并记下发送的总数据。带宽的大小等于发送的总数据除以发送的总时间。对服务器端来说,就是在连接建立时间内,接收的总数据除以所花的时间即为服务器端所测得的带宽。MSS的大小通过TCP内核接口函数直接获得。
Iperf测试UDP的性能时,客户端可以指定UDP数据流的速率。客户端发送数据时,将根据客户提供的速率计算数据报发送之间的时延。另外客户还可以指定发送数据报的大小。每个发送的数据报包含一个ID号,用来唯一地标识该报文。服务器端则根据该ID号来确定数据报丢失和乱序。当把UDP报文大小设置可以将整个报文放入IP层的包(packet)内时,那么UDP所测得的报文丢失数据即为IP层包的丢失数据。这提供了一个有效的测试包丢失情况的方法。数据报传输延迟抖动 (Jitter)的测试由服务器端完成,客户发送的报文数据包含有发送时间戳,服务器端根据该时间信息和接收到报文的时间戳来计算传输延迟抖动。传输延迟抖动反映传输过程中是否平滑。由于它是一个相对值,所以并不需要客户端和服务器端时间同步。
Iperf源代码采用面向对象的C++语言实现,主要包括基本类和实现类两部分。基本类提供了实现中需要用到的一些基本的对象,包括队列、链表、时间管理、锁、条件、线程等,以及一些工具类程序,这些代码不是特定于Iperf应用的,可以移植到其他应用程序。实现类中主要包括针对Iperf应用的类,包括实现客户端/服务器发送和接收数据的类,以及用于用于管理client和server的类等。这里主要讨论一下与应用关系最紧密的几个类,其他的类不做详述。
Iperf主要类图结构包括9个类。Iperf 的核心部分均在PerfSocket类中实现,包括客户端和服务器端发送和接收数据、带宽报告、数据丢失及延迟抖动报告,以及窗口大小和MSS报告等功能。 其中Speaker和Client为客户端的对象,Listener、Audience和Server为服务器的对象。客户端和服务器的通信通过三个消息完成:connect、write以及shutdown。这里connect不同于TCP中的连接,它还包含一个数据报文,其信息为双向测试而传给服务器端的信息,主要用于双向测试时让服务器端启动客户端线程而所需要的信息。UDP 测试的过程基本上跟TCP类似。UDP报文包含了一个应用报文头,其主要字段为报文ID和时间信息,这个主要是为了测试UDP报文的丢失、乱序以及延迟抖动性能。UDP的第一个报文用来建立连接,不作为应用数据,其信息为双向测试而传给服务器端的信息,主要用于双向测试时让服务器端启动客户端线程而所需要的信息。UDP与TCP第一个报文内容的主要区别是UDP报文还包括一个应 用报文头。UDP传输结束通过客户端发送一个FIN的报文来实现,该报文的报文ID为负数,服务器端接收到FIN报文后即停止接收报文并回送一个AckFIN报文给客户,AckFIN报文包含了服务器端得到的测试数据。
操作举例:
1)TCP测试
服务器执行:iperf -s -i 1 -w 1M
客户端执行:iperf -c host -i 1 -w 1M
其中-w表示TCP window size,host需替换成服务器地址。
2)UDP测试
服务器执行:iperf -u -s
客户端执行:iperf -u -c 10.32.0.254 -b 900M -i 1 -w 1M -t 60
其中-b表示使用带宽数量,千兆链路使用90%容量进行测试就可以了。
在开发Iperf的过程中,开发者把Socket编程和多线程编程中经常用到的一些系统调用封装成对象,屏蔽了底层函数的复杂接口,提供了模块化和面向对象的机制,也为我们提供了一些非常实用的编程工具,我们可以在实现自己的程序时复用这些类。由于这些类实现的源代码都比较简单,也为我们修改前人的代码实现自己的功能提供了方便。
这些类的定义与实现都在源代码文件夹的lib子文件夹下。主要包括以下一些对象:
SocketAddr类:封装了Socket接口中的网络地址结构(sockaddr_in等)以及各种地址转换的系统调用(gethostbyname、gethostbyaddr、inet_ntop等);
Socket类:封装了socket文件描述符,以及socket、listen、connect等系统调用;
Mutex类以及Condition类:封装了POSIX标准中的mutex和condition(条件变量)线程同步机制;
Thread类:封装了POSIX标准中的多线程机制,提供了一种简单易用的线程模型;
Timestamp类:通过Unix系统调用gettimeofday实现了一个时间戳对象,提供了获得当前时间戳,计算两个时间戳之间的先后关系等方法。
此外,在lib文件夹中还包括一些Iperf的实现提供的实用工具函数,包括endian.c文件中的字节序转换函数、gnu_getopt文件中的命令行参数处理函数、snprintf文件中的字符串格式化函数、signal.c文件中的与信号处理有关的函数、 string.c文件中的字符处理函数、tcp_window_size.c文件中的TCP窗口大小处理函数等。
Iperf是基于Server-Client模式实现的。在测量网络参数时,Iperf区分Client对象和Server对象两种角色。Client对象向Server对象发送一定量的数据,由Server对象统计并记录带宽、时延抖动等参数。Client对象的数据全部发送完成后,Server对象通过向Client对象回送一个数据包,将测量数据反馈给Client对象。这样,在Client和Server两边都可以显示记录的数据。如果网络过于拥塞或者误码率较高,当Server回送的数据包无法被Client接收到时,Client无法显示完整的测量数据,而只报告本地记录的部分网络测试结果,发送的数据量、发送时间、发送带宽等,像时延抖动等参数在Client一侧则无法获得(因此,在报告时,服务器和客户端所得到的信息可能是不同的)。
Iperf提供了三种测试模式:Normal、Tradeoff、Dualtest。对于每一种模式,用户都可以通过-P选项指定同时测量的并行线程数。在以下的讨论中,设用户的并行线程数为P个。
在Normal模式下,客户端首先生成Speaker对象,Speaker对象根据参数设置生成P个Client对象,然后并行地向服务器发送数据。服务器通过Listener对象在套接字监听网络连接,当Listener接收到一个Client发送的数据时,首先判断是否有Audience处理该主机(仅指IP地址),如果有,便将Audience与此Client关联,如果没有,便生成一Audience对象,负责与Speaker对象间的通信。客户端有P个并行的Client对象(由Speaker管理),而服务器有P个并行的Server对象(由Audience管理),两者之间共有P个连接同时收发数据。测试结束后,服务器端的每个Server对象调用基类Report*()系列函数向自己对应的Client对象反馈测得的网络参数。
在Tradeoff模式下,首先进行Normal模式下的测试过程,然后客户端和服务器互换角色。原服务器生成P个Client对象,同时向原客户端发送数据。原客户端对应每个Client对象生成相应的Server对象,用于接收数据并测量参数。最后,由原客户端的Server对象向原服务器端的Client对象反馈测量结果。这样就可以测量两个方向上的网络参数了。
对于Dualtest模式,该模式同样用于测试两个方向上的网络参数,与Tradeoff模式的不同在于,在Dualtest模式下,由服务器到客户端方向上的网络测试与由客户端到服务器方向上的网络测试是同时进行的。客户端生成P个Client对象和P个Server对象,Client对象向服务器端发送数据,Server对象等待接收服务器端的Client对象发送的数据。服务器端也进行同样的操作。在客户端和服务器端之间同时存在 2P个网络连接,其中有P个连接的数据由客户端流向服务器,另外P个连接的数据由服务器流向客户端。因此,Dualtest的模式需要的测试时间是Tradeoff模式的一半。
在三种模式下,除了P个Client对象或Server对象,在服务器端和客户端两侧均存在一个监控进程实例(monitor thread),分别对应于Audience和Speaker。监控进程实例的作用包括:
*生成说者或听者线程;
*同步所有说者或听者的动作(开始发送、结束发送等);
*计算并报告所有说者或听者的累计测量数据。
在监控线程的控制下,所有P个Client或Server对象间就可以实现同步和信息共享。Client对象或Server、对象向一个公共的数据区写入测试结果数据(此数据区位于实现监控线程的对象中),由监控线程读取并处理。通过互斥锁(Mutex)实现对该数据区的同步访问。服务器端通过Listener类可以同时接收来自不同客户端的连接,这些连接是通过客户端的IP地址标识的。服务器将所有客户端的连接信息组织成一个单向链表,每个客户端连接对应链表中的一项,该项包括该客户端的地址结构(sockaddr)以及实现与该client对应server类的监控线程Audience的对象。
Iperf中的主要类:
PerfSocket类:
PerfSocket类以Socket为基类派生而来。该类实现了Iperf用于通信的大多数功能,包括发送UDP数据包(Send_UDP)、接收 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(Server监控线程,也可称为Server监控对象)对象来负责继续处理与该请求对应的后续操作,如果在请求中指明了要进行 tradeoff或dualtest模式的测量,Listener就生成一个Speaker(Client监控线程,也可称为Client监控对象)实例来负责发起到客户的反向连接;如果请求来自一个已经存在的客户,Listener通过该客户对应的Audience实例的方法使该Audience实例生成一个Server对象进行处理该连接(这发生在多线程并行测量的情况下)。完成这些工作后,Listener线程返回继续监听。
Audience类:
Audience类以Thread类和Notify为基类,属多重继承。一个Audience实例对应一个执行线程且具有监控其他线程的功能。Audience实例的主要功能是作为Server对象的监控线程。 它首先生成一个Server对象,之后负责维护并报告所用听者线程测量结果的累计值,在对听者线程的结束事件同步之后,Audience线程退出。Listener线程在接收到一个已有客户的连接请求时,也会通过Audience实例生成新的Server对象实例。在服务器端,一个客户对应一个Audience实例,所有的 Audience实例组织成一个链表。
Speaker类:
Speaker类同Audience类一样,也以Thread类和Notify类为基类。它实现了Client对象的监控线程。 它首先生成若干Client对象,对所有Client对象的开始事件进行同步,之后负责维护并报告所用Client对象测量结果的累计值,在对Client对象的结束事件同步之后,Speaker退出。此外,Speaker在运行时,还会根据是否进行双向测试来由Listener生成实例。在客户端程序开始运行后,Speaker线程就开始工作了。当服务器端收到双向测试的请求时,也会生成Speaker实例(线程)。
Server类:
Server类以PerfSocket类和Thread类为基类,实现了服务器端进程。它接收对端的Client对象发来的数据,记录数据量、延迟抖动等网络参数,打印测试结果, 修改Audience实例的累计数据区,并在收到Client对象的最后一个数据包时将测试结果回送给Client。
Client类:
Client类以PerfSocket类和Thread类为基类,实现了客户端进程。它建立于远端服务器的连接,执行发送TCP/UDP数据包的过程,判断发送结束标志,接收服务器端Server对象反馈信息,同时打印出相应的网络测试结果。
综上,Client类与Server类对应,分别实现了客户端进程和服务器进程;Speaker类和Audience类对应,分别实现了Client对象监控实例和Server对象监控实例。Listener类是监听对象;所有的Client实例由Speaker实例生成,所有的Server实例是由Listener实例触发Audience实例生成。