天天看點

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

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

IPerf主要分為如下幾個子產品:

選項參數處理;

線程封裝和角色扮演;

四種線程模式(或者說角色):

用戶端線程;

服務端線程;

報告者線程;

監聽者線程。

套接字選項設定與提取;

連結清單和數組的封裝和維護;

處理多并發Condition條件變量的封裝;

時間戳封裝;

Windows下作為背景服務運作的建立和運作。

下面盡可能針對每個子產品進行說明:

選項參數的處理:

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

程式的主要子產品就是角色線程的生成、運作和銷毀,其他子產品包括時間戳、條件變量、維護的連結清單等都是為此服務的,是以這裡打算先說一下其他子產品然後在逐一分析不同類型的線程。

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

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

繼續閱讀