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


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

IPerf主要分為如下幾個模塊:

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

下面盡可能針對每個模塊進行說明:

選項參數的處理:

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

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

 

程序的主要模塊就是角色線程的生成、運行和銷毀,其他模塊包括時間戳、條件變量、維護的鏈表等都是為此服務的,所以這里打算先說一下其他模塊然后在逐一分析不同類型的線程。

 

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

說套接字選項之前還需要先說一下套接字的生成,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模式下也不難理解。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM