上篇隨筆講到了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,那么就不進行該間隔時間段的統計信息的打印。
