Socket的速率控制

(一)、目標

做一個以精確速率向外輸出數據的數據源,要完成這個目標,最基礎的是:

1、找到一種精確的計時器,在精確的時間範圍內控制數據源以指定的速度向外發送數據。

2、通過對套接字選項和線程優先級的設置減少網絡因素對發送速度造成的影響,從而提高發送精度,保證數據的實際發送量儘可能的達到指定的理論發送量。

     針對第一個要求,通過尋找到一種時間精度達到微秒級的精確計數器來保證,在硬件支持的情況下可以通過WindowsAPI獲取時鐘頻率以及震盪次數,通過在事件兩端分別調用函數得到震盪次數的差值並結合時鐘頻率可以計算出精確的時間間隔,通過指定的傳輸速度和精確的延時可以計算出需要發送的數據量。對於第二個要求通過設置數據源所在線程的優先級,以及對套接字選項的設置來減小協議本身對數據傳輸的過多控制而造成的時延,從而使實際的數據發送量儘可能的接近理論值。

(一)、設置線程優先級

首先在主函數中創建線程函數DWORDWINAPI ThreadProc(LPVOIDlpParam),在線程函數中實現數據源的功能,線程創建成功後對線程優先級進行設置,windows下對線程優先級進行設置的API函數爲BOOLWINAPI SetThreadPriority( __in HANDLE hThread, __in intnPriority),其中nPriority定義了線程的優先級。


線程優先級

標誌

優先級值

1

IDLE(最低)

THREAD_PRIORITY_IDLE

如果進程優先級爲realtime則調整爲16,其他情況爲1

2

LOWEST(低)

THREAD_PRIORITY_LOWEST

-2(在原有基礎上-2

3

BELOW(低於標準)

THREAD_PRIORITY_BELOW_NORAL

-1(在原有基礎上-1

4

NORMAL(標準)

THREAD_PRIORITY_NORMAL

不變(取進程優先級)

5

ABOVE(高於標準)

THREAD_PRIORITY_ABOVE_NORMAL

+1(在原有基礎上+1

6

HIGHEST(高)

THREAD_PRIORITY_HIGHEST

+2(在原有基礎上+2

7

CRITICAL(最高)

THREAD_PRIORITY_TIME_CRITICAL

如果進程優先級爲realtime則調整爲31,其他情況爲15

將線程優先級設置爲最高級,即THREAD_PRIORITY_TIME_CRITICAL,排除本地其他進程的干擾,使得操作系統能夠優先調度。

(二)、使用高精度計數器

要實現精確的發送速度,需要有精確的時間控制,在一般情況下使用以下兩種方式就滿足要求了。一是用SetTimer函數建立一個定時器後,在程序中通過處理有定時器發送到線程的消息隊列中的WM_TIMER消息,而得到定時的效果。二是利用GetTickCount函數可以返回計算機啓動後的時間,通過兩次調用GetTickCount函數,然後控制他們的差值來取得定時的效果。但是以上兩種方法都是毫秒級的,在Windows內部有一個高精度運行計時器(high-resolution performance counter),利用它可以獲得高精度的定時間隔,其精度可以達到微秒級,其精度與CPU的時鐘頻率有關。利用API函數QueryPerformanceFrequency能夠得到這個定時器的頻率。利用API函數QueryPerformanceCounter能夠得到定時器的當前值,根據要延時的時間和定時器的頻率,能夠算出定時器要經過的週期數,循環指定的週期數,就達到了高精度定時的目的。以下是API函數的原型:

  • BOOLQueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);

作用:返回硬件支持的高精度計數器的頻率。

返回值:如果硬件支持高精度計數器則返回頻率值,若硬件不支持返回零。

  • BOOLQueryPerformanceCounter(LARGE_INTEGER*lpPerformanceCount);

    作用:得到高精度計時器的值。

    返回值:如果安裝的硬件支持高精度計時器則函數返回計數器的值,如果硬件不支持參數返回零。


採用這種方法的步驟如下:

  1. 首先調用QueryPerformanceFrequency函數取得高精度運行計數器的頻率f,單位是每秒多少次(n/s,此數一般很大,在實驗主機上此頻率值爲2241054

  2. 在循環之外先調用一次QueryPerformanceCounter,得到計數器的值n1,在send之前調用循環調用QueryPerformanceCounter,得到計數器的值n2,兩次數值的差值通過計數器的頻率f換算成時間間隔,t=(n2-n1)/f。當t達到指定的大小後跳出循環,並將此時的n2賦給n1,重新進入循環計時,send發送的數據大小根據指定的速度與定時時間確定。

這種方法存在一個致命的問題,就是忙等,循環過程會佔滿CPU資源。因此需要調整一下算法,循環過程引入休眠機制,降低CPU佔用,但是休眠機制由於操作系統調度的不確定性,會造成循環結束後的實際時間和預設時間會有差別。沒有關係,我們在循環結束後調用QueryPerformanceCounter,重新計算本次和上次循環的精確時間差,運用這個時間來計算send發送的數據大小,這樣保證輸出的數據始終恆定。


(三)設置套接字選項

SO_SNDBUF:通過設置該選項可以改變發送緩衝區的大小,根據應用需求,設置合理的緩衝區大小能夠有效提高數據發送的效率。

TCP_NODELAY:在默認情況下,Windows協議棧發送數據採用Nagle算法,這樣做雖然提高了網絡的吞吐量,但是實時性卻降低了,如果設置了TCP_NODELAY選項,就會禁用Nagle算法,應用程序調用send發送的數據包會被立即投送到網絡,而沒有延遲。考慮到程序中需要做到精確發送的要求,所以將TCP_NODELAY選項設置爲TRUE

SO_DONTROUTE通過設置該選項,可以繞過路由表中的網關所在的表項,設置了該套接字選項的socket可以將數據包不經網關發送,而是發往直接相連的主機。該套接字選項的合法的值是整數形式的布爾標誌值,這裏設爲TRUE

Windows下,對套接字選項進行設置的API函數爲

intsetsockopt(

__in SOCKET s,

__in int level,

__in int optname,

__in const char* optval,

__in int optlen

);

獲取當先套接字選項值的API函數爲

intgetsockopt(

__in SOCKET s,

__in int level,

__in int optname,

__out char* optval,

__inout int* optlen

);

通過以上兩個函數可以查看並重新設置套接字選項的值。


當指定傳輸速度爲64kb/s時,速度的波動控制在了0.002kb/s-0.02kb/s

當指定傳輸速度爲100kb/s時,速度的波動在0.001kb/s-0.02kb/s以內。

當指定傳輸速度爲1000kb/s時,速度的波動控制在了0.015kb/s-0.2kb/s之內。


思考與改進:

    Socket的通信,數據從發送方到接收方,影響速率的因素很多,主要包括以下幾個方面:

1、操作系統調度。

2、其他網絡進程對網絡帶寬資源的爭搶。

3、協議本身,如果是TCP協議,則速率控制受到TCP協議流量控制和擁塞控制機制,鏈路層流量控制機制的影響。

4、協議包在發送和接收途中受到其他網絡數據的擠佔及丟失等。


   我們只能在發送端的應用層以儘可能精確的速率向傳輸層送速率,然而數據經過傳輸層、IP層、鏈路層到物理層最終到網絡上,不可控的因素太多。如果能夠在網卡驅動層檢測實際的進程數據流量,然後根據網卡流量來反饋控制數據投送量,將更爲精確地實現速率的控制,具體實現需要進一步研究。



 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章