IPerf——网络测试工具介绍与源码解析(3)
DWORD WINAPI thread_run_wrapper( void* paramPtr )
{
struct thread_Settings* thread = (struct thread_Settings*) paramPtr;
switch ( thread->mThreadMode )
{
case kMode_Server:
{
server_spawn( thread );
} break;
case kMode_Client:
{
client_spawn( thread );
} break;
case kMode_Reporter:
{
reporter_spawn( thread );
} break;
case kMode_Listener:
{
} break;
default:
{
FAIL(, "Unknown Thread Type!\n", thread);
} break;
} if ( thread->runNext != NULL )
{
thread_start( thread->runNext );
}
Settings_Destroy( thread ); return ;
} // end run_wrapper
生成线程的入口函数
【报告者线程 kMode_Reporter】
IPerf不管是在客户端还是在服务端,都会创建一个报告者线程,该线程是用来输出各种信息到控制台界面,根据其报告的内容可将信息分为五种类型,这些类型都在代码中做了定义标识
/*
* The type field of ReporterData is a bitmask
* with one or more of the following
*/
#define TRANSFER_REPORT 0x00000001
#define SERVER_RELAY_REPORT 0x00000002
#define SETTINGS_REPORT 0x00000004
#define CONNECTION_REPORT 0x00000008
#define MULTIPLE_REPORT 0x00000010
传输类型:数据传输过程中的数据体现,例如:
[ ID] Interval Transfer Bandwidth
[244] 0.0- 1.0 sec 131 MBytes 1.10 Gbits/sec
[244] 1.0- 2.0 sec 281 MBytes 2.36 Gbits/sec
[244] 2.0- 3.0 sec 310 MBytes 2.60 Gbits/sec
服务端返回类型:UDP模式下打印服务端返回的内容,主要为延迟抖动、丢包率的统计信息,例如:
[244] Server Report:
[244] 0.0-10.0 sec 1.25 MBytes 1.05 Mbits/sec 0.000 ms 0/ 893 (0%)
设置类型:对于客户端,打印连接的对端地址和连接的端口,对于服务端,打印监听连接的端口等,例如:
------------------------------------------------------------
Client connecting to 127.0.0.1, UDP port 5001
Sending 1470 byte datagrams, IPG target: 11215.21 us (kalman adjust)
UDP buffer size: 64.0 KByte (default)
------------------------------------------------------------------------------------------------------------------------
Server listening on TCP port 5001
TCP window size: 64.0 KByte (default)
------------------------------------------------------------
连接类型:打印连接的信息,例如:
[244] local 127.0.0.1 port 24003 connected with 127.0.0.1 port 5001
多播类型:在同一客户端发生多个连接到服务端时,对于服务端,在一定的打印时间段里,比如上面的1.0- 2.0 sec,程序将识别出为同一客户端的数据量进行累加,做一个总的输出打印,例如:
其中[SUM]开头所打印的信息类型为多播类型
[288] 6.0- 7.0 sec 163 MBytes 1.37 Gbits/sec
[ 40] 6.0- 7.0 sec 164 MBytes 1.37 Gbits/sec
[SUM] 6.0- 7.0 sec 327 MBytes 2.74 Gbits/sec
[288] 7.0- 8.0 sec 164 MBytes 1.38 Gbits/sec
[ 40] 7.0- 8.0 sec 164 MBytes 1.37 Gbits/sec
[SUM] 7.0- 8.0 sec 328 MBytes 2.75 Gbits/sec
那么,对于报告者线程,这是如何进行实现的呢?
IPerf维护了一个节点类型为ReportHeader的全局变量ReportRoot,作为维护报告者首部的根节点,该结构的组成情况是这样的:(这里只列出相关的结构,全面具体的结构体内容请进一步查看源码)
ReportHeader中的ReportData中的有个整型类型的type成员变量,它的值表明了该报告者首部属于那种类型的报告,Reporter.cpp会根据此变量的值进行相应的处理。
int reporter_process_report ( ReportHeader *reporthdr )
{
if ( (reporthdr->report.type & SETTINGS_REPORT) != )
{
//...please read the sourc code for getting more information...
}
else if ( (reporthdr->report.type & CONNECTION_REPORT) != )
{
//...please read the sourc code for getting more information...
}
else if ( (reporthdr->report.type & SERVER_RELAY_REPORT) != )
{
//...please read the sourc code for getting more information...
} if ( (reporthdr->report.type & TRANSFER_REPORT) != )
{
//...please read the sourc code for getting more information...
}
return need_free;
}
报告者线程的绝大部分时间都花在打印传输类型的报告内容。
在初始化传输报告首部这一步,程序在初始化传输类型的ReportHeader时会申请如下结构的空间大小:
其中ReportStruct类型共有NUM_REPORT_STRUCTS(#define NUM_REPORT_STRUCTS 700)个,后面它是循环使用的。
//src/Reporter.c/InitReport
reporthdr = (ReportHeader *) malloc( sizeof(ReportHeader) +
NUM_REPORT_STRUCTS * sizeof(ReportStruct) );
if ( reporthdr != NULL )
{
// Only need to make sure the headers are clean
memset( reporthdr, , sizeof(ReportHeader));
reporthdr->data = (ReportStruct*)(reporthdr+);
reporthdr->multireport = agent->multihdr;
data = &reporthdr->report;
//Set reporterindex with the last one
reporthdr->reporterindex = NUM_REPORT_STRUCTS - ;
...
...
ReportHeader的data指向第一个ReportStruct结构的地址,agentindex和reporterindex为整型类型,作为data的下标与其结合,data[agentindex]表示当前最新发送包所在的填充位置,data[reporterindex]为报告者线程已报告到控制台的数据包的位置。
客户端线程每次发送数据到服务端后,都会填充一次ReportStrut结构,重要的信息有三项,记录当前发送的数据量大小、包发送出去的时间戳以及包的标识ID,所以可以把ReportStruct看作是Packet,毕竟ReportStructural的成员变量的命名说明其作为一个packet看待会更好,然后会将其填充到data[agentindex]中,并且将angentindex进行加一处理。当填充到ReportStrut数组的尾部时则会回到数组的第一项重新填充,以此方式循环利用,reporterindex永远不能超过agentindex,因为我数据都没填充,残留的是无效的数据,怎么可以进行提前打印呢。
来,再说得具体点。
首先,在InitReport函数中,如果选项参数中有使用到有-i选项的话(该选项参数的值存储在thread_settings类型的mInterval变量中),则将该值赋予ReportHeader中ReportData的intervalTime变量,然后将当前时间赋予ReportData的starttime变量(通过gettimeofday),再将startime + intervalTime初始化ReportData中的nexttime,这个值说明下一次将要打印报告的时间戳,具体看代码:
if ( agent->mInterval != 0.0 )
{
struct timeval *interval = &data->intervalTime;
interval->tv_sec = (long) agent->mInterval;
//Equal to Zero Josephus
interval->tv_usec = (long) ((agent->mInterval - interval->tv_sec)
* rMillion);
}
//starttime和nexttime的初始化
else
{ // set start time
gettimeofday( &(reporthdr->report.startTime), NULL );
}
reporthdr->report.nextTime = reporthdr->report.startTime;
TimeAdd( reporthdr->report.nextTime, reporthdr->report.intervalTime );
然后,在每次客户端线程发完数据后,判断-i选项是否有效,有效的情况下,给当前的包,也就是ReportStruct结构类型的变量填充值,包括发送的数据量大小currLen,获取当前的时间戳,PackID在TCP模式下起的作用只有一个——在发送完毕时添加一个数据量为0,PacketID为-1的包标识发送数据完毕,其余的时候PaketID的值均为0,然后调用ReportPacket函数。
ReportPacket函数的作用是维护agentindex和reportindex的先后关系,将数据包的内容添加到ReportHeader->data[agentindex]中,并将agentindex做加一处理。
此时,报告者线程在reporter_spawn中做循环操作,这点在开始的时候也有提到过,循环操作中有调用reporter_process_report函数,所以也可以说reporter_process_report函数一直被报告者线程调用,在该函数中,当处理到运输类型的报告首部时,首先对reporterindex和agentindex在某些特殊情况下进行了处理,确保reporterindex没有“超越”agentindex,然后调用reporter_handle_packet函数,来重点看一下这个函数:
reporter_handle_packet函数一开始就判断当前将要打印(或报告)的包(data[reporthdr->reporterindex])是否是最后一个包(通过PacketID值是否小于0),如果是,则将finished置为1,后面将这个值返回,上层可以通过函数的返回值销毁该运输类型结构体变量,如果不是,则调用reporter_condprintstats函数,但在调用该函数时,将当前可能要打印的包的时间赋予ReportData中的packetTime,注意此时并没有把该包的大小也加到ReportData的TotalLen中,而是等到reporter_condprintstats函数返回时才加上,原因等下说明,来深究一下reporter_condprintstats这个函数:
reporter_condprintstats函数中,如果传进来的参数force不等于0,在TCP模式下说明数据发送完了,将要打印的是统计的信息,如果force等于0,则会执行循环,循环的条件为:
else while ((stats->intervalTime.tv_sec != || stats->intervalTime.tv_usec != ) &&
TimeDifference( stats->nextTime, stats->packetTime ) < )
选项参数-i有使用,体现在隔段时间需要将当前发送信息以打印的方式报告一次,stats是ReportHeader中的ReporterData,其实“罪魁祸首”,起到最大作用的就是ReportData类型的成员变量report,也就是现在的stats,如果nexttime 小于 当前可能要打印的包的时间戳(注意在上层已经将包的时间戳赋予了packetTime),想象一下,本来要nexttime这个时间戳打印报告的,但是现在还没打印的第一个包的时间戳都超过了这个时间,那还不赶紧打印,所以符合条件,开始执行循环体的内容,对ReportData中Transfer_Info类型的变量info进行赋值,并注意保存本次的状态信息并在下次打印时做一系列的相减操作,接着调用reporter_print函数并传入Transfer_info类型的参数值进行控制台输出打印。一般来说,该while循环只执行一次,除非打印的时间间隔太小,也就是-i选项值设的过小,如果想要实现while循环执行多次的效果,可以试试在客户端线程发送数据完毕后紧接着在后面阻塞一段时间。
刚才提到的为什么在reporter_condprintstats函数返回时才加上将要打印的包的大小,因为循环体条件中判断两个时间时使用的是小于符号,注定后面的包大小不宜在该时间段中打印出来。
如果还不太明白,可以结合下图来理解的:)
未完待续...
IPerf——网络测试工具介绍与源码解析(3)的更多相关文章
- IPerf——网络测试工具介绍与源码解析(4)
上篇随笔讲到了TCP模式下的客户端,接下来会讲一下TCP模式普通场景下的服务端,说普通场景则是暂时不考虑双向测试的可能,毕竟了解一项东西还是先从简单的情况下入手会快些. 对于服务端,并不是我们认为的直 ...
- IPerf——网络测试工具介绍与源码解析(2)
对于IPerf源码解析,我是基于2.0.5版本在Windows下执行的情况进行分析的,提倡开始先通过对源码的简单修改使其能够在本地编译器运行起来,这样可以打印输出一些中间信息,对于理解源码的逻辑,程序 ...
- IPerf——网络测试工具介绍与源码解析(1)
IPerf是一个开源的测试网络宽带并能统计并报告延迟抖动.数据包丢失率信息的控制台命令程序,通过参数选项可以方便地看出,通过设置不同的选项值对网络带宽的影响,对于学习网络编程还是有一定的借鉴意义,至少 ...
- 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 ...
随机推荐
- php安装grpc报No releases available for package解决方法
1.pecl.php.net搜索相应grpc的下载文件,这里找了个stable版本 https://pecl.php.net/get/grpc-1.17.0.tg 2.wge下载+pecl insta ...
- 自定义Appium
改造appium-android-driver 这个driver是UIAutomator1的driver,负责UIAutomator1的服务启动.停止.命令接收和执行. 工程结构 appium-and ...
- haproxy(8):haproxy代理MySQL要考虑的问题
HaProxy系列文章:http://www.cnblogs.com/f-ck-need-u/p/7576137.html haproxy可以通过 TCP协议 来代理MySQL.但是两个问题必须考虑: ...
- zabbix分布式监控部署--技术流ken
前言 zabbix proxy可以代替zabbix server检索客户端的数据,然后把数据汇报给zabbix server,并且在一定程度上分担了zabbix server的压力.zabbix pr ...
- 好好耕耘 redis和memcached的区别
观点一: 1.Redis和Memcache都是将数据存放在内存中,都是内存数据库.不过memcache还可用于缓存其他东西,例如图片.视频等等: 2.Redis不仅仅支持简单的k/v类型的数据,同时还 ...
- vim撤销undo与反撤销redo
普通模式下 u 撤销 ctrl + r 反撤销
- iis访问网络路径映射问题(UNC share)
最近在做一个功能,涉及到nas网络磁盘文件的保存和访问,在服务器上将对应的路径映射为Z盘,结果在iis上部署网站直接访问该路径,报无法找到该路径的错误. 用的是.net core开发,在vs直接启动程 ...
- H5 贪吃蛇源码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 通过DFS求解有向图(邻接表存储)中所有简单回路
前言 查阅了网上许多关于通过DFS算法对有向图中所有简单回路的查找,发现有很多关于使用DFS求解有向回路中所有简单回路的帖子,(在按照节点编号情况下)但大多数仅仅寻找了编号递增的回路.又或者未对结果去 ...
- 提取Chrome插件为crx文件
在Chrome浏览器输入 chrome://extensions/,点开右上角开发者模式 记录上图中的ID:gidgenkbbabolejbgbpnhbimgjbffefm 在资源管理器中找到Chro ...