
1 DWORD WINAPI thread_run_wrapper( void* paramPtr ) 2 { 3 struct thread_Settings* thread = (struct thread_Settings*) paramPtr; 4 switch ( thread->mThreadMode ) 5 { 6 case kMode_Server: 7 { 8 server_spawn( thread ); 9 } break; 10 case kMode_Client: 11 { 12 client_spawn( thread ); 13 } break; 14 case kMode_Reporter: 15 { 16 reporter_spawn( thread ); 17 } break; 18 case kMode_Listener: 19 { 20 } break; 21 default: 22 { 23 FAIL(1, "Unknown Thread Type!\n", thread); 24 } break; 25 } 26 27 if ( thread->runNext != NULL ) 28 { 29 thread_start( thread->runNext ); 30 } 31 Settings_Destroy( thread ); 32 33 return 0; 34 } // end run_wrapper
【報告者線程 kMode_Reporter】
IPerf不管是在客戶端還是在服務端,都會創建一個報告者線程,該線程是用來輸出各種信息到控制台界面,根據其報告的內容可將信息分為五種類型,這些類型都在代碼中做了定義標識
1 /* 2 * The type field of ReporterData is a bitmask 3 * with one or more of the following 4 */ 5 #define TRANSFER_REPORT 0x00000001 6 #define SERVER_RELAY_REPORT 0x00000002 7 #define SETTINGS_REPORT 0x00000004 8 #define CONNECTION_REPORT 0x00000008 9 #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會根據此變量的值進行相應的處理。
1 int reporter_process_report ( ReportHeader *reporthdr ) 2 { 3 if ( (reporthdr->report.type & SETTINGS_REPORT) != 0 ) 4 { 5 //...please read the sourc code for getting more information... 6 } 7 else if ( (reporthdr->report.type & CONNECTION_REPORT) != 0 ) 8 { 9 //...please read the sourc code for getting more information... 10 } 11 else if ( (reporthdr->report.type & SERVER_RELAY_REPORT) != 0 ) 12 { 13 //...please read the sourc code for getting more information... 14 } 15 16 if ( (reporthdr->report.type & TRANSFER_REPORT) != 0 ) 17 { 18 //...please read the sourc code for getting more information... 19 } 20 return need_free; 21 }
報告者線程的絕大部分時間都花在打印傳輸類型的報告內容。
在初始化傳輸報告首部這一步,程序在初始化傳輸類型的ReportHeader時會申請如下結構的空間大小:
其中ReportStruct類型共有NUM_REPORT_STRUCTS(#define NUM_REPORT_STRUCTS 700)個,后面它是循環使用的。
//src/Reporter.c/InitReport
1 reporthdr = (ReportHeader *) malloc( sizeof(ReportHeader) + 2 NUM_REPORT_STRUCTS * sizeof(ReportStruct) ); 3 if ( reporthdr != NULL ) 4 { 5 // Only need to make sure the headers are clean 6 memset( reporthdr, 0, sizeof(ReportHeader)); 7 reporthdr->data = (ReportStruct*)(reporthdr+1); 8 reporthdr->multireport = agent->multihdr; 9 data = &reporthdr->report; 10 //Set reporterindex with the last one 11 reporthdr->reporterindex = NUM_REPORT_STRUCTS - 1; 12 ... 13 ...
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,這個值說明下一次將要打印報告的時間戳,具體看代碼:
1 if ( agent->mInterval != 0.0 ) 2 { 3 struct timeval *interval = &data->intervalTime; 4 interval->tv_sec = (long) agent->mInterval; 5 //Equal to Zero Josephus 6 interval->tv_usec = (long) ((agent->mInterval - interval->tv_sec) 7 * rMillion); 8 }
//starttime和nexttime的初始化
1 else 2 { 3 4 // set start time 5 gettimeofday( &(reporthdr->report.startTime), NULL ); 6 } 7 reporthdr->report.nextTime = reporthdr->report.startTime; 8 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,則會執行循環,循環的條件為:
1 else while ((stats->intervalTime.tv_sec != 0 || stats->intervalTime.tv_usec != 0) && 2 TimeDifference( stats->nextTime, stats->packetTime ) < 0 )
選項參數-i有使用,體現在隔段時間需要將當前發送信息以打印的方式報告一次,stats是ReportHeader中的ReporterData,其實“罪魁禍首”,起到最大作用的就是ReportData類型的成員變量report,也就是現在的stats,如果nexttime 小於 當前可能要打印的包的時間戳(注意在上層已經將包的時間戳賦予了packetTime),想象一下,本來要nexttime這個時間戳打印報告的,但是現在還沒打印的第一個包的時間戳都超過了這個時間,那還不趕緊打印,所以符合條件,開始執行循環體的內容,對ReportData中Transfer_Info類型的變量info進行賦值,並注意保存本次的狀態信息並在下次打印時做一系列的相減操作,接着調用reporter_print函數並傳入Transfer_info類型的參數值進行控制台輸出打印。一般來說,該while循環只執行一次,除非打印的時間間隔太小,也就是-i選項值設的過小,如果想要實現while循環執行多次的效果,可以試試在客戶端線程發送數據完畢后緊接着在后面阻塞一段時間。
剛才提到的為什么在reporter_condprintstats函數返回時才加上將要打印的包的大小,因為循環體條件中判斷兩個時間時使用的是小於符號,注定后面的包大小不宜在該時間段中打印出來。
如果還不太明白,可以結合下圖來理解的:)
未完待續...