iperf-1.7.0 源代码分析.doc_第1页
iperf-1.7.0 源代码分析.doc_第2页
iperf-1.7.0 源代码分析.doc_第3页
iperf-1.7.0 源代码分析.doc_第4页
iperf-1.7.0 源代码分析.doc_第5页
已阅读5页,还剩31页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Iperf 源代码分析(一)概述 前段时间学习Linux网络编程的有关知识,希望看一看这些网络编程的技术在实际的代码中是如何运用的。正巧实验室的项目中使用了开源网络性能测试软件Iperf,于是便初步分析了Iperf的源代码。现将分析代码的点滴收获写在我的Blog上,希望各位高人多多指教。 Iperf 是美国伊利诺斯大学(University of Illinois)开发的一种网络性能测试工具。可以用来测试网络节点间TCP或UDP连接的性能,包括带宽、延时抖动(jitter,适用于UDP)以及误码率(适用于UDP)等。关于Iperf的下载、安装以及详细的使用方法,可以参照/Projects/Iperf/Iperf是按照Server-Client范型工作的。在连接的一端使用以下命令启动Server:iperf -s在连接的另一端启动Client:iperf -c 此处假设Server端的IP地址为。经过一段测试时间(默认为10秒),在Server端和Client端就会打印出网络连接的各种性能参数。Iperf作为一种功能完备的测试工具,还提供了各种选项,例如是建立TCP连接还是UDP连接、测试时间、测试应传输的字节总数、测试模式等。而测试模式又分为单向测试(Normal Test)、同时双向测试(Dual Test)和交替双向测试(Tradeoff Test)。此外,用户可以指定测试的线程数。这些线程各自独立的完成测试,并可报告各自的以及汇总的统计数据。对于Iperf的详细使用方法以及命令行参数的意义,请参照上面的网页。 Iperf是用C+语言实现的,对设计中的各种结构和功能单元都按照面向对象的思想进行建模。它主要用到了 Unix系统编程中两个主要的部分:Socket网络编程和多线程编程。因此,通过分析Iperf的源代码,我们就可以在实际的例子中学习面向对象编程, Socket网络编程以及多线程编程的技术。同时,Iperf实现的功能比较简单,代码并不复杂,而且功能比较单一。因此,Iperf是我们研究Unix 系统编程技术的一个很好的学习对象。我所分析的是Iperf 1.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中。cpp view plaincopyprint?1 /* - */ 2 class Thread 3 public: 4 Thread( void ); 5 virtual Thread(); 6 7 / start or stop a thread executing 8 void Start( void ); 9 void Stop( void ); 10 11 / run is the main loop for this thread 12 / usually this is called by Start(), but may be called 13 / directly for single-threaded applications. 14 virtual void Run( void ) = 0; 15 16 / wait for this or all threads to complete 17 void Join( void ); 18 static void Joinall( void ); 19 20 void DeleteSelfAfterRun( void ) 21 mDeleteSelf = true; 22 23 24 / set a thread to be daemon, so joinall wont wait on it 25 void SetDaemon( void ); 26 27 / returns the number of user (i.e. not daemon) threads 28 static int NumUserThreads( void ) 29 return sNum; 30 31 32 static nthread_t GetID( void ); 33 34 static bool EqualID( nthread_t inLeft, nthread_t inRight ); 35 36 static nthread_t ZeroID( void ); 37 38 protected: 39 nthread_t mTID; 40 bool mDeleteSelf; 41 42 / count of threads; used in joinall 43 static int sNum; 44 static Condition sNum_cond; 45 46 private: 47 / low level function which calls Run() for the object 48 / this must be static in order to work with pthread_create 49 50 static void* Run_Wrapper( void* paramPtr ); 51 52 ; / end class Thread 数据成员说明:mTID纪录本线程的线程ID;mDeleteSelf通过方法DeleteSelfAfterRun设置,用来说明是否在线程结束后释放属于该现程的变量;sNum是一个静态变量,即为所有的Thread实例所共有的。该变量纪录所生成的线程的总数。Thread对象的Joinall方法通过该变量判断所有的Thread实例是否执行结束;sNum_cond是用来同步对sNum的操作的条件变量,也是一个静态变量。主要函数成员说明:Start方法:cpp view plaincopyprint?53 /* - 54 * Start the objects thread execution. Increments thread 55 * count, spawns new thread, and stores thread ID. 56 * - */ 57 58 void Thread:Start( void ) 59 if ( EqualID( mTID, ZeroID() ) ) 60 61 / increment thread count 62 sNum_cond.Lock(); 63 sNum+; 64 sNum_cond.Unlock(); 65 66 Thread* ptr = this; 67 / pthreads - spawn new thread 68 int err = pthread_create( &mTID, NULL, Run_Wrapper, ptr ); 69 FAIL( err != 0, pthread_create ); 70 71 / end Start 首先通过Num+纪录一个新的线程的产生,之后通过pthread_create系统调用产生一个新的线程。新线程执行Run_Wrapper函数,以至向该Thread实例的ptr指针作为参数。原线程在判断pthread_create是否成功后退出Start函数。Stop方法:cpp view plaincopyprint?72 /* - 73 * Stop the thread immediately. Decrements thread count and 74 * resets the thread ID. 75 * - */ 76 77 void Thread:Stop( void ) 78 if ( ! EqualID( mTID, ZeroID() ) ) 79 / decrement thread count 80 sNum_cond.Lock(); 81 sNum-; 82 sNum_cond.Signal(); 83 sNum_cond.Unlock(); 84 85 nthread_t oldTID = mTID; 86 mTID = ZeroID(); 87 88 / exit thread 89 / use exit() if called from within this thread 90 / use cancel() if called from a different thread 91 if ( EqualID( pthread_self(), oldTID ) ) 92 pthread_exit( NULL ); 93 else 94 / Cray J90 doesnt have pthread_cancel; Iperf works okay without 95 pthread_cancel( oldTID ); 96 97 98 / end Stop 首先通过sNum-纪录一个线程执行结束,并通过sNum_cond的Signal方法激活此时wait在 sNum_cond的线程(某个主线程会调用调用Joinall方法,等待全部线程的结束,在Joinall方法中通过sNum_cond.Wait() 等待在sNum_cond条件变量上)。若结束的线程是自身,则调用pthread_exit函数结束,否则调用pthread_cancel函数。注意:传统的exit函数会结束整个进程(即该进程的全部线程)的运行,而pthread_exit函数仅结束该线程的运行。Run_Wrapper方法:cpp view plaincopyprint?99 /* - 100 * Low level function which starts a new thread, called by 101 * Start(). The argument should be a pointer to a Thread object. 102 * Calls the virtual Run() function for that object. 103 * Upon completing, decrements thread count and resets thread ID. 104 * If the object is deallocated immediately after calling Start(), 105 * such as an object created on the stack that has since gone 106 * out-of-scope, this will obviously fail. 107 * static 108 * - */ 109 110 void* 111 Thread:Run_Wrapper( void* paramPtr ) 112 assert( paramPtr != NULL ); 113 114 Thread* objectPtr = (Thread*) paramPtr; 115 116 / run (pure virtual function) 117 objectPtr-Run(); 118 119 #ifdef HAVE_POSIX_THREAD 120 / detach Thread. If someone already joined it will not do anything 121 / If noone has then it will free resources upon return from this 122 / function (Run_Wrapper) 123 pthread_detach(objectPtr-mTID); 124 #endif 125 126 / set TID to zero, then delete it 127 / the zero TID causes Stop() in the destructor not to do anything 128 objectPtr-mTID = ZeroID(); 129 130 if ( objectPtr-mDeleteSelf ) 131 DELETE_PTR( objectPtr ); 132 133 134 / decrement thread count and send condition signal 135 / do this after the object is destroyed, otherwise NT complains 136 sNum_cond.Lock(); 137 sNum-; 138 sNum_cond.Signal(); 139 sNum_cond.Unlock(); 140 141 return NULL; 142 / end run_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方法:cpp view plaincopyprint?143 /* - 144 * Wait for all thread objects execution to complete. Depends on the 145 * thread count being accurate and the threads sending a condition 146 * signal when they terminate. 147 * static 148 * - */ 149 150 void Thread:Joinall( void ) 151 sNum_cond.Lock(); 152 while ( sNum 0 ) 153 sNum_cond.Wait(); 154 155 sNum_cond.Unlock(); 156 / end Joinall 157 158 /* - 159 * set a thread to be daemon, so joinall wont wait on it 160 * this simply decrements the thread count that joinall uses, 161 * which is not a thorough solution, but works for the moment 162 * - */ 163 164 void Thread:SetDaemon( void ) 165 sNum_cond.Lock(); 166 sNum-; 167 sNum_cond.Signal(); 168 sNum_cond.Unlock(); 169 由这两个方法的实现可见,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:cppview plaincopyprint?1. structsockaddr/*structtoholdanaddress*/2. u_charsa_len/*totallength*/3. u_shortsa_family;/*typeofaddress*/4. charsa_data14;/*valueofaddress*/5. ;其中sa_family表示地址所属的地址族,TCP/IP协议的地址族用常量AF_INET表示,而UNIX命名管道的地址族用常量AF_UNIX表示。 使用Socket的每个协议族都精确定义了自己的网络端点地址,并在头文件中提供了相应的结构声明。用来表示TCP/IP地址的数据结构如下:cppview plaincopyprint?1. structsockaddr_in2. u_charsin_len;/*totallength*/3. u_shortsin_family;/*typeofaddress*/4. u_shortsin_port;/*protocolportnumber*/5. structin_addrsin_addr;/*IPaddress*/6. charsin_zero8;/*unused(settozero)*/7. 其中,sin_len和sin_family和sockaddr机构中的sa_len以及sa_family表示相同的数据。结构sockaddr_in将sockaddr中通用的端点地址sa_data(14字节长)针对TCP/IP的地址结构作了细化,分为8bit的端口地址sin_port和32bit的IP地址。在Linux系统中,结构in_addr如下定义:htmlview plaincopyprint?1. structin_addr2. unsignedlongs_addr;/*IPaddress*/3. 可见,结构in_addr仅有一个成员,表示一个32bit的数据,即IP地址。对于通用地址结构中的其余bit,填充0。 Socket接口中的很多函数都是为通用的网络地址结构设计的,例如,我们既可以用bind函数将一个socket绑定到一个TCP/IP的端口上,也可以用bind函数将一个socket绑定到一个UNIX命名管道上。因此,像bind,connect,recvfrom,sendto等函数都要求一个sockaddr结构作为指名地址的参数。这时,我们就要使用强制类型转换把表示IP地址的sockaddr_in结构转换为sockaddr结构进行函数调用。但实际上,sockaddr和sockaddr_in结构表示的均是同一地址。它们在内存中对应的区域是重合的。SockedAddr类的功能比较单一,成员变量mAddress就是SocketAddr的实例所表示的TCP/IP端口地址(包括IP地址和TCP/UDP端口号)。类声明mAddress为iperf_sockaddr类型的变量,而在文件/lib/headers.h中,有htmlview plaincopyprint?1. typedefsockaddr_iniperf_sockaddr 因此,iperf_sockaddr实际上就是sockaddr_in类型的变量。SockedAddr的成员函数都是对mAddress进行读取或修改的操作的。比较复杂的成员函数是setHostname,它完成了地址解析的过程,源代码如下(已将不相关部分删除)。cppview plaincopyprint?1. /*-2. *Resolvethehostnameaddressandfillitin.3. *-*/4. 5. voidSocketAddr:setHostname(constchar*inHostname)6. 7. assert(inHostname!=NULL);8. 9. mIsIPv6=false;10. mAddress.sin_family=AF_INET;11. /firsttryjustconvertingdotteddecimal12. /onWindowsgethostbynamedoesntunderstanddotteddecimal13. intrc=inet_pton(AF_INET,inHostname,(unsignedchar*)&(mAddress.sin_addr);14. if(rc=0)15. structhostent*hostP=gethostbyname(inHostname);16. if(hostP=NULL)17. /*thisisthesameasherror()butworksonmoresystems*/18. constchar*format;19. switch(h_errno)20. caseHOST_NOT_FOUND:21. format=%s:Unknownhost/n;22. break;23. caseNO_ADDRESS:24. format=%s:Noaddressassociatedwithname/n;25. break;26. caseNO_RECOVERY:27. format=%s:Unknownservererror/n;28. break;29. caseTRY_AGAIN:30. format=%s:Hostnamelookupfailure/n;31. break;32. 33. default:34. format=%s:Unknownresolvererror/n;35. break;36. 37. fprintf(stderr,format,inHostname); Iperf 源代码分析(四)Socket类 Socket的定义和实现分别在文件Socket.hpp和Socket.cpp中。它的主要功能是封装了socket文件描述符、此socket对应的端口号,以及socket接口中的listen,accept,connect和close等函数,为用户提供了一个简单易用而又统一的接口。同时作为其他派生类的基类。 Socket类的定义如下:cppview plaincopyprint?1. *-2. *Aparentclasstoholdsocketinformation.Haswrappersaround3. *thecommonlisten,accept,connect,andclosefunctions.4. *-*/5. 6. #ifndefSOCKET_H7. #defineSOCKET_H8. 9. #includeheaders.h10. #includeSocketAddr.hpp11. 12. /*-*/13. classSocket14. public:15. /storesserverportandTCP/UDPmode16. Socket(unsignedshortinPort,boolinUDP=false);17. 18. /destructor19. virtualSocket();20. 21. protected:22. /getlocaladdress23. SocketAddrgetLocalAddress(void);24. 25. /getremoteaddress26. SocketAddrgetRemoteAddress(void);27. 28. /serverbindandlisten29. voidListen(constchar*inLocalhost=NULL,boolisIPv6=false);30. 31. /serveraccept32. intAccept(void);33. 34. /clientconnect35. voidConnect(constchar*inHostname,constchar*inLocalhost=NULL);36. 37. /closethesocket38. voidClose(void);39. 40. /toputsetsockoptcallsbeforethelisten()andconnect()calls41. virtualvoidSetSocketOptions(void)42. 43. 44. /jointhemulticastgroup45. voidMcastJoin(SocketAddr&inAddr);46. 47. /setthemulticastttl48. voidMcastSetTTL(intval,SocketAddr&inAddr);49. 50. intmSock;/socketfiledescriptor(sockfd)51. unsignedshortmPort;/porttolistento52. boolmUDP;/trueforUDP,falseforTCP53. 54. ;/endclassSocket55. 56. #endif/SOCKET_H Socket类主要提供了四个函数:Listen,Accept,Connect和Close。getLocalAddress和GetREmoteAddress的作用分别是获得socket本端的地址和对端的地址,两个函数均返回一个SocketAddr实例。SetSocketOptions的作用是设置socket的属性,它是一个虚函数,因此不同的socket的派生类在实现此函数时会执行不同的操作。下面重点看一下Socket类的几个函数的实现。Listen函数cppview plaincopyprint?1. /*-2. *Setupasocketlisteningonaport.3. *ForTCP,thiscallsbind()andlisten().4. *ForUDP,thisjustcallsbind().5. *IfinLocalhostisnotnull,bindtothataddressratherthanthe6. *wildcardserveraddress,specifyingwhatincominginterfaceto7. *acceptconnectionson.8. *-*/9. 10. voidSocket:Listen(constchar*inLocalhost,boolisIPv6)11. intrc;12. 13. SocketAddrserverAddr(inLocalhost,mPort,isIPv6);14. 15. /createaninternetTCPsocket16. inttype=(mUDP?SOCK_DGRAM:SOCK_STREAM);17. intdomain=(serverAddr.isIPv6()?18. #ifdefIPV619. AF_INET620. #else21. AF_INET22. #endif23. :AF_INET);24. 25. mSock=socket(domain,type,0);26. FAIL_errno(mSock=INVALID_SOCKET,socket);27. 28. SetSocketOptions();29. 30. /reusetheaddress,sowecanrunifaformerserverwaskilledoff31. intboolean=1;32. Socklen_tlen=sizeof(boolean);33. /this(char*)castisforoldheadersthatdontuse(void*)34. setsockopt(mSock,SOL_SOCKET,SO_REUSEADDR,(char*)&boolean,len);35. 36. /bindsockettoserveraddress37. rc=bind(mSock,serverAddr.get_sockaddr(),serverAddr.get_sizeof_sockadd

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论