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
傳輸類型:數據傳輸過程當中的數據體現,例如:app
[ 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/secide
服務端返回類型:UDP模式下打印服務端返回的內容,主要爲延遲抖動、丟包率的統計信息,例如:函數
[244] Server Report:
[244] 0.0-10.0 sec 1.25 MBytes 1.05 Mbits/sec 0.000 ms 0/ 893 (0%)測試
設置類型:對於客戶端,打印鏈接的對端地址和鏈接的端口,對於服務端,打印監聽鏈接的端口等,例如:spa
------------------------------------------------------------
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函數返回時才加上將要打印的包的大小,由於循環體條件中判斷兩個時間時使用的是小於符號,註定後面的包大小不宜在該時間段中打印出來。
若是還不太明白,能夠結合下圖來理解的:)
未完待續...