天天看點

經過一年時間的沉澱 再次回首 TCP Socket伺服器程式設計 (二)

------------------

前言

------------------ 

發了第一篇文章後,有不少同志留言,看來socket程式設計仍然是軟體系統裡面一個比較難的部分。

第一篇文章

主要介紹了傳輸協定的設計,這個是整個socket架構最底層基礎的部分,接下來整個socket伺服器大樓都将在這個協定設計基礎上不斷搭建出來。

這篇文章我主要接上文提出的伺服器各個性能參數給出解決思路。

-------------------

socket服務端接收子產品設計 

------------------- 

當伺服器Accept一個新的socket之後,會對這個socket進行封裝,成為一個connection(當然是自定義了) 。之後的處理都會交給這個connection負責。

由于socket發送的資料存在分包、黏包問題,connection接收子產品注定了要使用接收隊列。當然這個所謂的接受隊列并沒有大家想象的這麼深奧,大緻的代碼結構如下: 

public class SocketReceiveQueue

{

    private Queue<ISocketReceivePackage> queue = null;

    private MemoryStream receiveBuffer = null;

}

即一個queue的接收隊列+一個stream。處理邏輯:

a. 接收到資料,壓入receiveBuffer

b. 從receiveBuffer讀取資料、擷取協定包ISocketReceivePackage,這裡可能會有多個,也可能一個也沒有。

c. 當接收完畢後,協定包再從queue出隊列,交給注冊的協定處理handler處理。

到目前為止,整塊接收邏輯并沒有涉及具體的業務、也絕對不應該涉及具體的業務。唯一要額外注意的就是接收包的長度問題,即協定包聲明的length是否過大。

這裡要注意,由于整個接收子產品沒有涉及到具體的業務邏輯,也就不應該在這裡對任何的輸入進行檢測(非法攻擊、頻率等),代碼上就是以最快速度解析完協定包,然後丢給上層handler分析。

用戶端請求性能分析 

當協定包來到了與業務相關的Handler之後,我們開始進行性能檢測。首先是請求頻率,使用如下公式:

requestInterval = (requestInterval * requestTimes + interval) / (requestTimes + 1)

計算得到的requestInterval就是用戶端的請求頻率。 數學上也很簡單,就是一個類似f(x) = af(x)+b的疊代算法。這個算法的特定當然就是性能高,我隻需要記錄使用者目前請求時間、請求累計次數之後,就能完全監控了使用者的請求性能。

此外還需要記錄顧客的錯誤次數。從設計理論上來說,客戶傳輸過來的資料不應該有錯,除非代碼出錯。當然,如果在值類型之間轉換出現的問題算是錯誤的話(使用者正常輸入了錯誤的數值),這個不算入錯誤。這個錯誤值是需要記錄在資料庫裡面;一旦發現錯誤值過大,則直接封這個IP了。

還需要說明的是,在伺服器一定有一個觸發器,每x秒會周遊一次所有的connection,一旦發現有長時間無請求的空連接配接,要主動踢出。

socket服務端發送子產品設計 

當伺服器處理完資料後,需要将處理結果回複給用戶端。如果使用簡單的設計思路,即直接壓入socket發送,性能是非常的低的;是以socket的發送必定需要使用發送隊列。使用發送隊列的優勢在于:

a. 當伺服器内部需要發送的資料激增的時候,通過壓入發送隊列,能夠減輕IO的處理壓力。很多時候我們會發現整個伺服器的性能瓶頸就在IO的處理上(收發) ,而不是伺服器的資料庫操作等。是以設計上就要以減輕IO處理為目标。

b. 使用發送隊列,能夠把多個回複資料包合并一次發送,極大減輕了IO的壓力。

發送隊列的結構也就是一個Queue,大緻的設計如下:

經過一年時間的沉澱 再次回首 TCP Socket伺服器程式設計 (二)
經過一年時間的沉澱 再次回首 TCP Socket伺服器程式設計 (二)

代碼

public class SendMessageQueueController

   Queue<SocketConnection> queue;

public class SocketConnection

    private SocketReceiveQueue receiveQueue;//接收隊列

    private SocketSendQueue sendQueue;//發送隊列

public class SocketSendQueue 

    private Queue<ISocketSendPackage> queue;//發送協定集合

經過一年時間的沉澱 再次回首 TCP Socket伺服器程式設計 (二)

代碼邏輯如下: 

a. 把需要發送的協定包壓入目前SocketSendQueue.

b. 判斷SocketConnection是否已經存在在SendMessageQueueController,如果不存在,則入列;如果存在,則傳回。

c. SendMessageQueueController每隔x毫秒檢查一次發送隊列,如果發現有資料,則進入while循環,直到所有SocketConnection出列并發送所有的資料。之後再進入等待。

d. 所謂包合并發送,就是把多個協定包一次寫入發送的Stream裡面,然後讓socket發送。

這塊的設計問題主要集中線上程沖突,需要在關鍵地方加幾把鎖,否則就容易出現線程沖突了。

後記

本文介紹的方法并不是最好的方法,我也相信業界有更加成熟的思路。不過我文中列舉的一些設計思路至少我用起來還是能夠滿足現有需求的。

如果各位同志有更好的思路,希望多多留言指教。

在下一篇文章中将結合具體的傳輸協定開始設計服務端的通用邏輯子產品,例如重發、資料緩存、登入登出等。