Iperf 源代码解析.docx
- 文档编号:9557692
- 上传时间:2023-02-05
- 格式:DOCX
- 页数:43
- 大小:50.68KB
Iperf 源代码解析.docx
《Iperf 源代码解析.docx》由会员分享,可在线阅读,更多相关《Iperf 源代码解析.docx(43页珍藏版)》请在冰豆网上搜索。
Iperf源代码解析
Iperf源代码分析
(一)
概述
前段时间学习Linux网络编程的有关知识,希望看一看这些网络编程的技术在实际的代码中是如何运用的。
正巧实验室的项目中使用了开源网络性能测试软件Iperf,于是便初步分析了Iperf的源代码。
现将分析代码的点滴收获写在我的Blog上,希望各位高人多多指教。
Iperf是美国伊利诺斯大学(UniversityofIllinois)开发的一种网络性能测试工具。
可以用来测试网络节点间TCP或UDP连接的性能,包括带宽、延时抖动(jitter,适用于UDP)以及误码率(适用于UDP)等。
关于Iperf的下载、安装以及详细的使用方法,可以参照:
Iperf是按照Server-Client范型工作的。
在连接的一端使用以下命令启动Server:
iperf-s
在连接的另一端启动Client:
iperf-c1.1.1.1
此处假设Server端的IP地址为1.1.1.1。
经过一段测试时间(默认为10秒),在Server端和Client端就会打印出网络连接的各种性能参数。
Iperf作为一种功能完备的测试工具,还提供了各种选项,例如是建立TCP连接还是UDP连接、测试时间、测试应传输的字节总数、测试模式等。
而测试模式又分为单项测试(NormalTest)、同时双向测试(DualTest)和交替双向测试(TradeoffTest)。
此外,用户可以指定测试的线程数。
这些线程各自独立的完成测试,并可报告各自的以及汇总的统计数据。
对于Iperf的详细使用方法以及命令行参数的意义,请参照上面的网页。
Iperf是用C++语言实现的,对设计中的各种结构和功能单元都按照面向对象的思想进行建模。
它主要用到了Unix系统编程中两个主要的部分:
Socket网络编程和多线程编程。
因此,通过分析Iperf的源代码,我们就可以在实际的例子中学习面向对象编程,Socket网络编程以及多线程编程的技术。
同时,Iperf实现的功能比较简单,代码并不复杂,而且功能比较单一。
因此,Iperf是我们研究Unix系统编程技术的一个很好的学习对象。
我所分析的是Iperf1.7.0版的源代码。
需要说明的是,Iperf的源代码中既包含了对应于Unix的部分,也包含了对应于Windows的部分。
这两部分是通过条件编译的预处理语句分别编译的。
我仅对Unix部分的代码进行分析。
Iperf提供的库
在开发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窗口大小处理函数等。
接下来对lib文件夹中的一些比较重要的类和函数进行说明。
Iperf源代码分析
(二)
Thread类
Thread类封装了POSIX标准中的多线程机制,提供了一种简单易用的线程模型。
Thread类是Iperf的实现中比较重要的类,使Iperf实现多线程并行操作的核心。
Thread类的定义在文件lib/Thread.hpp中,其实现位于lib/Thread.cpp中。
/*-------------------------------------------------------------------*/
classThread{
public:
Thread(void);
virtual~Thread();
//startorstopathreadexecuting
voidStart(void);
voidStop(void);
//runisthemainloopforthisthread
//usuallythisiscalledbyStart(),butmaybecalled
//directlyforsingle-threadedapplications.
virtualvoidRun(void)=0;
//waitforthisorallthreadstocomplete
voidJoin(void);
staticvoidJoinall(void);
voidDeleteSelfAfterRun(void){
mDeleteSelf=true;
}
//setathreadtobedaemon,sojoinallwon'twaitonit
voidSetDaemon(void);
//returnsthenumberofuser(i.e.notdaemon)threads
staticintNumUserThreads(void){
returnsNum;
}
staticnthread_tGetID(void);
staticboolEqualID(nthread_tinLeft,nthread_tinRight);
staticnthread_tZeroID(void);
protected:
nthread_tmTID;
boolmDeleteSelf;
//countofthreads;usedinjoinall
staticintsNum;
staticConditionsNum_cond;
private:
//lowlevelfunctionwhichcallsRun()fortheobject
//thismustbestaticinordertoworkwithpthread_create
staticvoid* Run_Wrapper(void*paramPtr);
};//endclassThread
数据成员说明:
mTID纪录本线程的线程ID;
mDeleteSelf通过方法DeleteSelfAfterRun设置,用来说明是否在线程结束后释放属于该现程的变量;
sNum是一个静态变量,即为所有的Thread实例所共有的。
该变量纪录所生成的线程的总数。
Thread对象的Joinall方法通过该变量判断所有的Thread实例是否执行结束;
sNum_cond是用来同步对sNum的操作的条件变量,也是一个静态变量。
主要函数成员说明:
Start方法:
/*-------------------------------------------------------------------
*Starttheobject'sthreadexecution.Incrementsthread
*count,spawnsnewthread,andstoresthreadID.
*-------------------------------------------------------------------*/
voidThread:
:
Start(void){
if(EqualID(mTID,ZeroID())){
//incrementthreadcount
sNum_cond.Lock();
sNum++;
sNum_cond.Unlock();
Thread*ptr=this;
//pthreads--spawnnewthread
interr=pthread_create(&mTID,NULL,Run_Wrapper,ptr);
FAIL(err!
=0,"pthread_create");
}
}//endStart
首先通过Num++纪录一个新的线程的产生,之后通过pthread_create系统调用产生一个新的线程。
新线程执行Run_Wrapper函数,以至向该Thread实例的ptr指针作为参数。
原线程在判断pthread_create是否成功后退出Start函数。
Stop方法:
/*-------------------------------------------------------------------
*Stopthethreadimmediately.Decrementsthreadcountand
*resetsthethreadID.
*-------------------------------------------------------------------*/
voidThread:
:
Stop(void){
if(!
EqualID(mTID,ZeroID())){
//decrementthreadcount
sNum_cond.Lock();
sNum--;
sNum_cond.Signal();
sNum_cond.Unlock();
nthread_toldTID=mTID;
mTID=ZeroID();
//exitthread
//useexit() ifcalledfromwithinthisthread
//usecancel()ifcalledfromadifferentthread
if(EqualID(pthread_self(),oldTID)){
pthread_exit(NULL);
}else{
//CrayJ90doesn'thavepthread_cancel;Iperfworksokaywithout
pthread_cancel(oldTID);
}
}
}//endStop
首先通过sNum--纪录一个线程执行结束,并通过sNum_cond的Signal方法激活此时wait在sNum_cond的线程(某个主线程会调用调用Joinall方法,等待全部线程的结束,在Joinall方法中通过sNum_cond.Wait()等待在sNum_cond条件变量上)。
若结束的线程是自身,则调用pthread_exit函数结束,否则调用pthread_cancel函数。
注意:
传统的exit函数会结束整个进程(即该进程的全部线程)的运行,而pthread_exit函数仅结束该线程的运行。
Run_Wrapper方法:
/*-------------------------------------------------------------------
*Lowlevelfunctionwhichstartsanewthread,calledby
*Start().TheargumentshouldbeapointertoaThreadobject.
*CallsthevirtualRun()functionforthatobject.
*Uponcompleting,decrementsthreadcountandresetsthreadID.
*IftheobjectisdeallocatedimmediatelyaftercallingStart(),
*suchasanobjectcreatedonthestackthathassincegone
*out-of-scope,thiswillobviouslyfail.
*[static]
*-------------------------------------------------------------------*/
void*
Thread:
:
Run_Wrapper(void*paramPtr){
assert(paramPtr!
=NULL);
Thread*objectPtr=(Thread*)paramPtr;
//run(purevirtualfunction)
objectPtr->Run();
#ifdefHAVE_POSIX_THREAD
//detachThread.Ifsomeonealreadyjoineditwillnotdoanything
//Ifnoonehasthenitwillfreeresourcesuponreturnfromthis
//function(Run_Wrapper)
pthread_detach(objectPtr->mTID);
#endif
//setTIDtozero,thendeleteit
//thezeroTIDcausesStop()inthedestructornottodoanything
objectPtr->mTID=ZeroID();
if(objectPtr->mDeleteSelf){
DELETE_PTR(objectPtr);
}
//decrementthreadcountandsendconditionsignal
//dothisaftertheobjectisdestroyed,otherwiseNTcomplains
sNum_cond.Lock();
sNum--;
sNum_cond.Signal();
sNum_cond.Unlock();
returnNULL;
}//endrun_wrapper
该方法是一个外包函数(wrapper),其主要功能是调用本实例的Run方法。
实际上,Run_Wrapper是一个静态成员函数,是为所有的Thread实例所共有的,因此无法使用this指针。
调用Run_Wrapper的Thread是通过参数paramPtr指明具体的Thread实例的。
在Run返回之后,通过pthread_detach使该线程在运行结束以后可以释放资源。
Joinall函数是通过监视sNum的数值等待所有线程运行结束的,而并非通过pthread_join函数。
在完成清理工作后,Run_Wrapper减少sNum的值,并通过sNum_cond.Signal函数通知在Joinall中等待的线程。
Run方法:
从Run方法的声明中知道,该方法是一个纯虚函数,因此Thread是一个抽象基类,主要作用是为其派生类提供统一的对外接口。
在Thread的派生类中,像Iperf中的Server,Client,Speader,Audience,Listener等类,都会为Run提供特定的实现,完成不同的功能,这是对面向对象设计多态特性的运用。
Thread函数通过Run方法提供了一个通用的线程接口。
讨论:
为什么要通过Run_Wrapper函数间接的调用Run函数?
首先,Thread的各派生类的完成的功能不同,但它们都是Thread的实例,都有一些相同的工作要做,如初始化和清理等。
在Run_Wrapper中实现这些作为Thread实例所应有的相同功能,在Run函数中实现派生类各自不同的功能,是比较合理的设计。
更重要的是,由于要通过Pthread_create函数调用Run_Wrapper函数,因此Run_Wrapper函数必须是一个静态成员,无法使用this指针区分运行Run_Wrapper函数的具体实例,也就无法利用多态的特性。
而这个问题可以通过把this指针作为Run_Wrapper函数的参数,并在Run_Wrapper中显示调用具有多态特性的Run函数来解决。
这种使用一个wrapper函数的技术为我们提供了一种将C++面向对象编程和传统的Unix系统调用相结合的思路。
Joinall方法和SetDaemon方法:
/*-------------------------------------------------------------------
*Waitforallthreadobject'sexecutiontocomplete.Dependsonthe
*threadcountbeingaccurateandthethreadssendingacondition
*signalwhentheyterminate.
*[static]
*-------------------------------------------------------------------*/
voidThread:
:
Joinall(void){
sNum_cond.Lock();
while(sNum>0){
sNum_cond.Wait();
}
sNum_cond.Unlock();
}//endJoinall
/*-------------------------------------------------------------------
*setathreadtobedaemon,sojoinallwon'twaitonit
*thissimplydecrementsthethreadcountthatjoinalluses,
*whichisnotathoroughsolution,butworksforthemoment
*-------------------------------------------------------------------*/
voidThread:
:
SetDaemon(void){
sNum_cond.Lock();
sNum--;
sNum_cond.Signal();
sNum_cond.Unlock();
}
由这两个方法的实现可见,Thread类是通过计数器sNum监视运行的线程数的。
线程开始前(Start方法中的pthread_create)sNum加一,线程结束后(Stop方法和Run_Wrapper方法末尾)sNum减一。
Joinall通过条件变量类的实例sNum_cond的Wait方法等待sNum的值改变。
而SetDaemon的目的是使调用线程不再受主线程Joinall的约束,只是简单的把sNum减一就可以了。
Iperf源代码分析(三)
SocketAddr类
SocketAddr类定义在lib/SocketAddr.hpp中,实现在lib/SocketAddr.cpp中。
SocketAddr类封装了网络通信中经常用到的地址结构以及在这些结构上进行的操作。
地址解析也是在SocketAddr的成员函数中完成的。
首先讨论一下Socket编程中用于表示网络地址的数据结构。
网络通信中的端点地址可以一般化的表示为(地址族,该族中的端点地址)。
Socket接口系统中用来表示通用的网络地址的数据结构是sockaddr:
structsockaddr{ /*structtoholdanaddress*/
u_char sa_len /*totallength */
u_short sa_family; /*typeofaddress */
char sa_data[14]; /*valueofaddress */
};
其中sa_family表示地址所属的地址族,TCP/IP协议的地址族用常量AF_INET表示,而UNIX命名管道的地址族用常量AF_UNIX表示。
使用Socket的每个协议族都精确定义了自己的网络端点地址,并在头文件中提供了相应的结构声明。
用来表示TCP/IP地址的数据结构如下:
structsockaddr_in{
u_char sin_len; /*totallength */
u_short sin_family; /*typeofaddress */
u_short sin_port; /*protocolportnumber */
struct in_addr sin_addr; /*IPaddress */
char
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Iperf 源代码解析 源代码 解析