IPerf——網絡測試工具介紹與源碼解析(2)

對於IPerf源碼解析,我是基於2.0.5版本在Windows下執行的狀況進行分析的,提倡開始先經過對源碼的簡單修改使其可以在本地編譯器運行起來,這樣能夠打印輸出一些中間信息,對於理解源碼的邏輯,程序實現的過程可以起到事半功倍的效果。html

IPerf主要分爲以下幾個模塊:編程

  • 選項參數處理;
  • 線程封裝和角色扮演;
    • 四種線程模式(或者說角色):
      • 客戶端線程;
      • 服務端線程;
      • 報告者線程;
      • 監聽者線程。
  • 套接字選項設置與提取;
  • 鏈表和數組的封裝和維護;
  • 處理多併發Condition條件變量的封裝;
  • 時間戳封裝;
  • Windows下做爲後臺服務運行的建立和運行。

下面儘量針對每一個模塊進行說明:windows

選項參數的處理:數組

做爲命令行控制檯應用程序,首要考慮到的問題就是對輸入參數命令行選項的處理,若是是簡單的應用程序直接經過case-switch或者if條件語句或許能夠解決,可是一旦到了規模較大,實現內容較爲複雜的控制檯應用程序,好比IPerf,仍是用該處理方法就顯得相對笨拙,在性能、邏輯處理等方面都有所不及。網絡

對於選項參數的處理,IPerf使用的GUN的一個getopt文件,在Linux下已有該頭文件,而在Windows須要本身導入該頭文件和實現文件,加入文件以後,還須要作的就是對文件中的一些長選項和短選項字符串進行處理,由於這是本身定義的需求,處理選項參數的邏輯是必定的,可是要將哪些內容做爲合理的選項和參數以及操做數,那些又是非法的字符和未能識別的操做數,程序須要據此進行判斷,因此須要進行一個初始化的過程,後面在使用的過程當中調用相應的接口對主函數傳進來的args和argv[]做爲輸入參數進行處理就好了,更多關於選項參數的處理,能夠看看該篇文章,或者自行網上找尋。併發

 

程序的主要模塊就是角色線程的生成、運行和銷燬,其餘模塊包括時間戳、條件變量、維護的鏈表等都是爲此服務的,因此這裏打算先說一下其餘模塊而後在逐一分析不一樣類型的線程。socket

 

套接字選項的封裝和設置:tcp

說套接字選項以前還須要先說一下套接字的生成,IPerf對套接字Socket的生成定義了一個名爲WIN32Socket的宏,這個宏內部調用了WSASocket,而套接字的屬性和協議類型是經過定義WSAPROTOCOL_INFO類型靜態函數,並將該函數做爲輸入參數傳到WASSocket實現的。函數

PerfSocket.cpp中只有一個名爲SetSocketOptions的函數,顧名思義就是用來設置套接字選項的值,函數裏面包含設置TCP滑動窗口大小(setsock_tcp_windowsize函數在另外一個名爲tcp_window_size.c的文件中單獨實現)、設置擁塞控制、設置多播、設置IP服務類型(這個不多用獲得)、設置最大報文段大小(setsock_tcp_mss函數在sockets.c文件中實現)、設置非延遲等。固然,除了設置套接字選項外,也有獲取相應選項的函數,好比getsock_tcp_windowsize和getsock_tcp_mss。性能

在SocketAddr.c文件中,IPerf定義了一系列以「SocketAddr_函數功能」格式命名的函數,經過宏條件判斷是否支持IPV6,定義了包括:經過IP地址獲取到或者說轉換成對端的套接字地址結構,將網絡序轉成點分十進制,獲取和設置端口值等,圍繞着定義的iperf_sockaddr類型(IPV4下爲sockaddr_in類型,IPV6下爲sockaddr_storage)判斷該套接字地址是否相同等。

 

鏈表和數組的維護和封裝:

IPerf在實現中建立了幾種不一樣類型的鏈表和數組:在開始時的線程鏈表,報告使用的報告者首部鏈表,監聽(者)線程維護的客戶端鏈表,緊接在傳送類型報告者首部後面的包數組,在服務端和多併發客戶端維護的多組報告首部維護的傳輸信息數組。具體的接下來會詳細講述到,List.cpp封裝對Iperf_ListEntry類型鏈表的增刪查和銷燬操做,而該鏈表僅是監聽者用來存儲和維護已鏈接客戶端的信息,別無它用。

 

處理多併發Condition條件變量的封裝:

Condition是IPerf本身封裝的結構體,變量mCondition爲事件內核對象的句柄,變量mMutex爲互斥量的句柄,

Condition_Initialize( Cond ): 建立一個初始化就處於觸發狀態的互斥量並把返回的句柄值賦予mMutex,建立一個初始化爲未觸發狀態的手動重置事件並把返回的句柄值賦予mCondition;

Condition_Destroy( Cond ):經過mCondition和mMutex的句柄值銷燬事件內核對象和互斥量;

Condition_Lock( Cond )  == Mutex_Lock( &Cond.mMutex ) == WaitForSingleObject( Cond.mMutex, INFINITE )

Condition_Unlock( Cond ) == Mutex_Unlock( &Cond.mMutex ) == ReleaseMutex( Cond.mMutex )

Condition_Wait( Cond ): 首先釋放互斥量,接着阻塞永久等待事件發生,而後等待互斥量;

Condition_TimedWait( Cond ): 首先釋放互斥量,接着阻塞在必定的時間內等待事件發生,而後等待互斥量;

Condition_Signal( Cond ):由於是手動重置事件,當其被調用時,全部正在等待該事件的線程都會變成可調度狀態;首先須要瞭解SetEvent和PulseEvent的區別,由於是手動重置事件,這對於兩個函數就有區別了,在自動重置事件類型下事件發生後被等待接收後會自動重置爲未觸發狀態,具體能夠查看《Windows核心編程》第9章的內容,裏面還介紹了SignalObjectAndWait函數的做用呢。

Condition條件變量定義的宏在使用過程當中本身是不太理解的,由於調用的時候容易將等待事件和獲取互斥量相互混淆,明明剛釋放了互斥量而後永久等待事件發生時,好不容易等到事件發生了又要獲取互斥量的全部權,因此寫者在每次等待和每次進入以及隨後的退出Condition時都加了相應的Debug輸出,這樣或許可以容易理解點,由於時間的關係,只能將這事放到後面去啃明白,可是在輸出的過程當中確實能發現其起到的做用,好比報告者在無可奉告的狀況下等待輸出內容的產生。

 

時間戳:

估計爲了與UNIX統一塊兒來,IPerf在Win32上不是直接調用API使用時間,而是本身封裝了gettimeofday,先經過GetSystemTimeAsFileTime獲取的UTC格式的時間轉換成UNIX新紀元下的時間並經過timeval類型進行返回,在該實現函數中使用了幾個特殊的數字,這在源代碼行上的註釋已經說明清楚,這裏再也不講述;

TimeStamp這個類中就只有一個類型爲timeval的成員變量,成員函數包括獲取當前的時間,對時間進行相加減,比較兩個時間的前後等,比較容易理解。

時間戳主要用在數據傳輸過程當中給每一個發送包賦值,代表這個包發送的時間;還有在-i選項在使用的條件下,計算每次須要打印報告的時間,經過比較將要打印報告的時間和最新發送包的時間戳,決定是否打印這段時間發送的帶寬和發送的數據量以及發送的時間段。

 

做爲後臺服務運行:

僅用於服務端,而且在做爲後臺服務運行時,-o filename 選項參數才能起到做用,一樣也是加入他人實現的文件,稍微看了一下,是經過SCManager的API才建立和執行服務的,這個後面有時間再認真學習,能夠考慮本身在後面的某些項目中能夠複用。

 

本文暫且就講到這裏,下一篇開始講解線程和角色,也就是結合着線程講解客戶端、服務端、報告者、監聽者的執行過程,暫且僅在TCP模式下,UDP後續再來講明,固然理解了TCP模式下的運行邏輯後,相信UDP模式下也不難理解。

相關文章
相關標籤/搜索