ACE之旅 (二)<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
——如何處理超過100個客戶端的連接
(一)前記
之所以學習ACE,是因爲嘗試利用ACE解決平時工作中遇到的問題。
在平時從事分佈式程序開發,直接調用系統的Socket API,很經常遇到如下的問題:
1:程序被嚴重阻塞,導致程序滯留在某個地方。
2:對於Socket API 返回的錯誤做了不當的處理。
3:對客戶端的併發處理能力較低。
4:對客戶端的響應嚴重延遲。
以上的問題,也是我學習ACE的強烈動力。看到ACE的技術文檔,有種衝動,尋找到解決問題的衝動。
(二)使用相關接口的注意事項
程序被嚴重阻塞,導致程序滯留在某個地方的原因,是因爲調用具有阻塞作用的函數。如下的函數將會產生阻塞。
1:accept
2:recv、recvfrom
3:send、sendto
在Linux、Unix下可以使用以下的方式在涉及I/O接口上設置超時:
1)調用alarm,它在指定超時滿慢時產生SIGALRM信號。
2)在select中阻塞等待I/O(select有內置的時間限制),以此代替直接阻塞在recv、recvfrom、send、sendto。
3)使用較新的SO_RCVTIMEO 和SO_SNDTIMEO套接口選項。
現在簡要分析一下:
1)在多線程下正確使用信號卻非常困難,因此方式1)建議在末線程化或單線程化的程序中使用。
2)使用方式2)在select調用返回後,在可讀的套接口調用accept、recv、recvfrom 也會產生阻塞。
在調用accept的時候,如果客戶端意外退出。
在調用recv、recvfrom的時候,在等待讀取指定長度後才返回。
所以在使用方式2),還要對套接口使用方式3)或者設置其非阻塞方式。
3)在使用ACE編程的時候,對於ACE_SOCK_Acceptor對象調用enable設置
ACE_NONBLOCK標誌。
對ACE_SOCK_Stream對象調用recv*(recv*_n) 和send*( send*_n)接口的時候,要傳進非空的ACE_Time_Value 指針。
但是調用recv*、send* 和recv*_n、send*_n 是有區別的。最近在編程的時候,我在傳進非空的ACE_Time_Value 指針,對recv*_n、send*_n 的返回值進行判斷,結果都返回-1。
後來改成recv*、send*對返回值判斷,結果是返回實際的操作值。後來看了ACE的ACE_SOCK_Stream 如下的描述,才知道必須對size_t *bytes_transferred進行判斷纔是合理的判斷。
三 如何降低對客戶端響應延遲
對這個問題的思考,是想要對實際遇到的問題找到一個比較好的解決方案。
多數分佈式程序設計導致客戶端響應延遲的主要原因併發處理的客戶端請求的機制不夠理想。
在實際的工作中,我遇到如下的通信模式:
客戶端Client 通過中間服務器Servat 到Server獲取某個信息。其通信圖如下:
<?xml:namespace prefix = v ns = "urn:schemas-microsoft-com:vml" />
圖一
我利用ACE組件進行驗證我的併發實現響應,結果發現當客戶端達到一定數量,併發的響應延遲比較嚴重。
如下是併發實現模式:
圖二
對該模式,寫了程序測試,測試效果如下:
1:處理單個客戶的每個請求平均延遲是2秒。
2:在第1個客戶端還在通信的情況下,響應第2、3個客戶端的第一個請求延遲是2秒。
但是在處理第4個客戶端的第一個請求延遲是6秒。
假設客戶端1、2、3……N,按照順序連接過來,Schedule接收每個客戶端的Req的時間爲t,那麼第N個客戶端的第一個請求被回覆的時間
TN = (N-1)*t + 2
經過多次運行程序,統計t = 0.7秒。
因此如果N >= 9,處理第N個的第一個請求的延遲TN >= 16秒。
雖然圖二的模型是使用多線程,但是併發的模式存在以下的缺陷:
1:單線程Schedule 進行Recv Client 的Req,再分派給多個Servat處理,瓶頸是在Schedule的排隊延遲。
在這裏說明單個線程對客戶端輪詢處理,滿足在合理的時間延遲內處理的客戶端是很有限的。
多線程也不一定能夠提高程序的併發處理效率,如果併發模式是不合理的。
由於程序的瓶頸先是在單個Schedule的隊列延遲。
因此我稍微做了修改併發的模式,如下是修改後的模式:
圖三
經過測試如下:
1:處理單個客戶端的第一個請求延遲是2秒。
2:如果程序中有K(K = 5)個客戶端,那麼第N個客戶端的第一個請求被回覆的最大延遲時間:
N = K*q + r,0<= r < k
TN = (q + 1)*t + 2
經過測試,當最大延遲是6秒的時候,N >= 17。在延遲的效率是明顯有點提高。
但是我在測試程序的過程中,發現如果處理併發處理63個客戶端的請求時候,時間竟然延遲到60秒左右。
因此這跟我要併發處理100多個客戶端的目標還差遠呢。而且在單進程內,SOCKET的最大句柄數是有效。
因此距離處理N>=100個目標還是需要探索。