IPerf——网络测试工具介绍与源码解析(4)
上篇随笔讲到了TCP模式下的客户端,接下来会讲一下TCP模式普通场景下的服务端,说普通场景则是暂时不考虑双向测试的可能,毕竟了解一项东西还是先从简单的情况下入手会快些。
对于服务端,并不是我们认为的直接创建服务端线程,而是先创建一个监听者线程,在本地绑定套接字后进行蹲点监听。
在Listener类中,Run成员函数执行一个do-while循环接收等待来自对端的连接,循环中调用Accept函数,该函数会阻塞,直至接收到对端的连接并通过thread_Settings*类型的指针参数返回客户端的信息,此时该thread_Settings*类型的值已经可以看作是一个用来生成服务端线程的服务端配置信息,接着创建一个IPerf_ListEntry*类型的节点,此前已经定义一个该类型的全局变量clients链表,作为存储已连接的客户端的信息,对新创建的IPerf_ListEntry*类型的变量listtemp,用新连接产生的套接字地址进行初始化,然后在clients链表中查找是否已存在同一对端连接过来的客户端信息节点,如果有,则与已存在的客户端节点共用MultiHeader*类型的多播报首部,该结构是用来打印并发统计信息的,对应在IPerf——网络测试工具介绍与源码解析(3)中讲述报告者线程时提到的多播类型的报告内容;如果在clients中没找到则说明该连接是一个新的连接,这时需要通过调用InitMulti函数给其创建并分配一个新的MultiHeader结构。
在InitMulti函数中真正执行分配MultiHeader空间的条件有两个:
if ( agent->mThreads > 1 || agent->mThreadMode == kMode_Server )
第一个条件对应进行并发测试的客户端,也就是在选项参数中使用了-c和-P,参数选项输入时,-P后面跟着的值说明有几个客户端同时尝试连接并发送数据到服务端;第二个条件对应着传送进来的thread_Settings*类型参数所代表的线程模式是服务端线程模式,因为服务端得考虑到同一个IP地址的客户端发起并发连接或者说通过不同的端口开启多个客户端程序尝试连接到同一个服务端的情况。对于同一个地方来的客户端都得进行一个汇总报告打印。
创建的MultiHeader结构有点类似于ReportHeader,具体如下:
不仅结构类似,在对MultiReort结构进行初始化也跟ReportHeader类似。
对于每一个从客户端连接过来的套接字,监听者线程都会将其封装成IPerf_ListEntry类型并把它添加到clients链表中,紧接着监听者线程会使用该热乎乎的套接字尝试去接收大小为client_hdr类型大小的数据,这是与客户端定的规则,客户端连接上服务端后要发送客户端首部信息到服务端,以便服务端知道客户端接下来要做哪种形式的测试后准备必要的条件,比如说服务端从客户端首部信息解析出此次要进行双向测试,那么监听者线程还得生成一个线程作为客户端连接回去并接下来开始履行客户端线程的职责发送数据回去。
还是继续说监听者线程返回的对端的套接字吧,接着监听者线程会将存储此套接字信息的thread_Settings*类型的变量作为参数生成一个服务端线程进行后续数据的接收,监听者继续监听新的客户端的连接,然后继续生成一个服务端线程,然后再继续监听,生成的若干服务端线程则努力地在一边同时进行着数据的接收,别忘了还有报告者线程一直存在着,报告者线程从一开始就报告着各种信息,开始时是设置类型的信息,接着是连接类型的信息,后面就为服务端线程打印一大堆的传输类型信息,如果服务端线程中有同一客户端连接过来的情况出现,那么报告者线程还得打印多播类型的信息,这就得使用到MultiReport且其在控制台表现的关键字为"[SUM]......"
截图中红色画框标记出来的内容即为多播类型的统计信息,在客户端连接时添加 -P 选项再加上大于1的选项值就会在客户端和服务端出现这种情况。
而上面这张截图中,红色画框的数据就有点诡异了,1.0-5.0秒算出来的传输的数据量为0,带宽也为0bits/sec,原因在哪?其实上面稍微提到了点,就是从同一客户端发出的连接,在服务端被监听线程接收到后,会让其使用同一个MultiHeader,也就是IPerf_ListEntry结构中holder所指向的内容为同一个。
1 if ( exist != NULL ) 2 { 3 // Copy group ID 4 //将新连接的服务监控线程的多播对象设置为以往的多播对象 5 listtemp->holder = exist->holder; 6 server->multihdr = exist->holder; 7 }
还有一段需要注意的代码:
reporter.c/initReport
1 if ( reporthdr->multireport != NULL && isMultipleReport( agent )) 2 { 3 // 4 reporthdr->multireport->threads++; 5 6 if ( reporthdr->multireport->report->startTime.tv_sec == 0 ) 7 { 8 gettimeofday( &(reporthdr->multireport->report->startTime), NULL ); 9 } 10 reporthdr->report.startTime = reporthdr->multireport->report->startTime; 11 } 12 else 13 { 15 // set start time 16 gettimeofday( &(reporthdr->report.startTime), NULL ); 17 }
以往的时候我们都是直接走else这一步,因为multireport为NULL,isMultipleReport是默认为真的,现在multireport被分配了空间,符合if中的条件则进入if块内执行,当multireport是刚初始化,而不是从别的IPerf_ListEntry中共用而来的时候,startTime.tv_sec的值应该为0的,这时赋值为当前的时间戳;当multireport是从别处共用过来的,那么starttime相对于当前的服务端线程来说(不仅仅是客户端,Server类中的Run函数也调用了InitReport函数)相当于是一个过去许久的时间戳,究竟过去多久,如果客户端是同时并发连接到服务端的话,那么这个时间间隔很短,短到可以忽略,但如果情况是这样的:同一个IP地址的主机,先开一个客户端连接到服务端,在测试还未结束的时候再开一个客户端连接到相同的服务端,那么这个时间戳相对来说就过去得有点久了。 上篇随笔说过starttime和nexttime的作用,nexttime是由starttime和intervalTime计算出来的,而nexttime决定了何时打印传输类型的报告,上篇随笔有段代码:
else while ((stats->intervalTime.tv_sec != 0 || stats->intervalTime.tv_usec != 0) && TimeDifference( stats->nextTime, stats->packetTime ) < 0 )
假使我有一个新数据包发出去了,那么我就得到了stats->packetTime的值,其实近乎于当前的时间戳,那么对于nexttime这个“年代已久”的时间来说(因为starttime是“年代已久”的时间),因为摆脱不了TimeDifference(...) < 0为真的困境,我就得在while里循环执行好几次,如果从stats->nextTime 到 stats->packetTime时间段里只发了一个数据包或者才仅仅几个包的数据(如果stats->packetTime不是从第一个包中获取的时间的话),那么就会看到如上面最近那张从控制台截取的图片所展示的内容一样,打印出一些0数据量0带宽的数据出来或者第一个时间段有数据后面的都为0,直至退出while循环到下一次进入while循环才会“恢复正常”。
好像扯远了,本来是要解决“[SUM]..."这种多播类型的报告数据是如何打印出来的,但上面的问题迟早要说明的,所以遇到就提前说了,现在回到正题。
report.c/reporter_condprintstats
reporter_print( stats, TRANSFER_REPORT, force /*Obivously it's value is zero which means not printMSS*/); if ( isMultipleReport(stats) ) { reporter_handle_multiple_reports( multireport, &stats->info, force ); }
上面代码,在打印完传输信息后,有一个判断,然后进入reporter_handle_multiple_reports函数,这个函数正常情况下都会进入,因为条件判断中的isMultipleReport默认为真的(其实可以通过控制台选项参数让其为假),主要是进入这个函数后,总会遇到multireport为NULL或者线程数小于等于1,继而不执行主体的内容,研究下reporter_handle_multiple_reports函数主体的代码,可以很容易理解[SUM]的数据是怎样打印出来的,这里不再进行过多的讲述。需要注意几点,同一个IP地址出来的客户端在服务端所对应的服务端线程数,程序有进行记录,如果在某个时间段未统计到相应数目的运输记录信息,也就是属于同一IP地址的服务端线程没有在这个函数跑过一次留下点痕迹,或者跑进不同的时间打印间隔(意味着不是同一“批次”),从而未达到current->free == reporthdr->threads这个条件,或者其实线程数只有1,那么就不进行该间隔时间段的统计信息的打印。
更多内容请关注个人微信公众号 物役记 (微信号:materialchains)