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 > || 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所指向的内容为同一个。
if ( exist != NULL )
{
// Copy group ID
//将新连接的服务监控线程的多播对象设置为以往的多播对象
listtemp->holder = exist->holder;
server->multihdr = exist->holder;
}
还有一段需要注意的代码:
reporter.c/initReport
if ( reporthdr->multireport != NULL && isMultipleReport( agent ))
{
//
reporthdr->multireport->threads++; if ( reporthdr->multireport->report->startTime.tv_sec == )
{
gettimeofday( &(reporthdr->multireport->report->startTime), NULL );
}
reporthdr->report.startTime = reporthdr->multireport->report->startTime;
}
else
{
// set start time
gettimeofday( &(reporthdr->report.startTime), NULL );
}
以往的时候我们都是直接走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 != || stats->intervalTime.tv_usec != ) &&
TimeDifference( stats->nextTime, stats->packetTime ) < )
假使我有一个新数据包发出去了,那么我就得到了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,那么就不进行该间隔时间段的统计信息的打印。
IPerf——网络测试工具介绍与源码解析(4)的更多相关文章
- IPerf——网络测试工具介绍与源码解析(2)
对于IPerf源码解析,我是基于2.0.5版本在Windows下执行的情况进行分析的,提倡开始先通过对源码的简单修改使其能够在本地编译器运行起来,这样可以打印输出一些中间信息,对于理解源码的逻辑,程序 ...
- IPerf——网络测试工具介绍与源码解析(1)
IPerf是一个开源的测试网络宽带并能统计并报告延迟抖动.数据包丢失率信息的控制台命令程序,通过参数选项可以方便地看出,通过设置不同的选项值对网络带宽的影响,对于学习网络编程还是有一定的借鉴意义,至少 ...
- IPerf——网络测试工具介绍与源码解析(3)
[线程的生成] 生成线程时需要传入一个thread_Settings类型的变量,thread_Settings包含所有线程运行时需要的信息,命令行选项参数解析后所有得到的属性都存储到该类型的变量中 ...
- IPerf——网络测试工具介绍与源码解析(5)
本篇随笔讲述一下TCP协议下,双向测试模式和交易测试模式下客户端和服务端执行的情况: 双向测试模式: 官方文档的解释 Run Iperf in dual testing mode. This will ...
- Android IntentService使用介绍以及源码解析
版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.IntentService概述及使用举例 IntentService内部实现机制用到了HandlerThread,如果对HandlerThrea ...
- vue系列---Mustache.js模板引擎介绍及源码解析(十)
mustache.js(3.0.0版本) 是一个javascript前端模板引擎.官方文档(https://github.com/janl/mustache.js) 根据官方介绍:Mustache可以 ...
- JUC中Lock和ReentrantLock介绍及源码解析
Lock框架是jdk1.5新增的,作用和synchronized的作用一样,所以学习的时候可以和synchronized做对比.在这里先和synchronized做一下简单对比,然后分析下Lock接口 ...
- 【转载】Android IntentService使用全面介绍及源码解析
一 IntentService介绍 IntentService定义的三个基本点:是什么?怎么用?如何work? 官方解释如下: //IntentService定义的三个基本点:是什么?怎么用?如何wo ...
- Android HandlerThread使用介绍以及源码解析
摘要: 版权声明:本文出自汪磊的博客,转载请务必注明出处. 一.HandlerThread的介绍及使用举例 HandlerThread是什么鬼?其本质就是一个线程,但是Han ...
随机推荐
- python 要掌握面向对象,你得会做这些题
1,面向对象三大特性,各有什么用处,说说你的理解. 继承:解决代码重用问题 多态:多态性,可以在不考虑对象类型的情况下而直接使用对象 封装:明确的区分内外,控制外部对隐藏属性的操作行为,隔离复杂度 2 ...
- 每个 Python 程序员都要知道的日志实践
在现实生活中,记录日志非常重要.银行转账时会有转账记录:飞机飞行过程中,会有黑盒子(飞行数据记录器)记录飞行过程中的一切.如果有出现什么问题,人们可以通过日志数据来搞清楚到底发生了什么. 对于系统开发 ...
- Jenkins入门之新建任务
简单了解了Jenkins界面之后,下面我们简单介绍一下如何使用jenkins创建一个任务.打开Jenkins web管理界面之后,点击左侧最上方的NewItem图标 便会进入如下界面 产生要输入一个构 ...
- JavaSSM框架整合
SSM整合 ssm框架 框架整合 在博客的前面介绍了mybatis,spring,springmvc的使用,那么这篇博客将介绍将mybatis和spring,springmvc的整合. 整合之前,我 ...
- lsyncd —— 多机器实时同步文件神器
lsyncd 是一个支持实时.双向.多机器的多模式文件同步工具. 使用 Lua 语言封装了 inotify 和 rsync 工具,采用了 Linux 内核(2.6.13 及以后)里的 inotify ...
- Go语言学习笔记(五) [函数]
日期:2014年7月29日 1.函数定义:func (p type) funcname(q int) (r,s int) {return 0,0 } func: 保留字,用于定义一个函数 ...
- javascript小实例,在页面中输出当前客户端时间
时间对象(Date())比较简单,本文旨在为初学者入门使用,大牛可略过! 本文承接基础知识实例,说一下实例的要求: 在页面中输出当前客户端时间(2015年1月1日星期一10:10:10这样的格式),每 ...
- C#实现给图片加边框的方法
Bitmap bit= new Bitmap(@"" + Path);//给图片加边框 //Bitmap bit = new Bitmap(Screen.AllScreens[0] ...
- Navicat Mysql快捷键
.ctrl+q 打开查询窗口 .ctrl+/ 注释sql语句 .ctrl+shift +/ 解除注释 .ctrl+r 运行查询窗口的sql语句 .ctrl+shift+r 只运行选中的sql语句 .F ...
- SpringBoot 配置 Servlet、Filter、Listener
SpringBoot 配置 Servlet.Filter.Listener 在SpringBoot应用中,嵌入式的 Servlet 3.0+ 容器不会直接使用 ServletContainerInit ...