C#實現的可複用Socket接收/發送共用緩衝區類

來源:互聯網
上載者:User

http://blog.csdn.net/hulihui/article/details/3260685

(原創文章,轉載請註明來源:http://blog.csdn.net/hulihui)

在Socket的接收/發送方法:Send()、BeginSend()、Receive()、BeginReceive()中,第一個參數是位元組數數
組,表示當前接收資料區或需要發送的資料。普通Socket應用中,往往是接收/發送時建立數組,使用後數組空間由託管堆回收(Socket關閉後其關聯
的緩衝區情況類似)。顯然,頻繁建立接收/發送緩衝區將在託管堆上留下很多的記憶體碎塊,影響系統效能。

使用Socket非同步調事件參數類SocketAsyncEventArgs時考慮了上述情況,基本構思為:自訂一個緩衝區管理類如BufferManager,開闢一個大的、可重用接收/發送收緩衝區,用於SendAsync()、ReceiveAsync()等方法,之前使用SetBuffer()和屬性OffSet、Count設定緩衝區空間。

事實上,在.NET 2.0平台上的Socket傳統APM(非同步編程模型)中仍然可用該這個技術。下面是修改的BufferManager類:

public sealed class BufferManager
{
// ... 全部欄位為private,類型和名稱見建構函式

public BufferManager(int maxSessionCount, int receivevBufferSize, int sendBufferSize)
{
m_maxSessionCount = maxSessionCount; // 最大可串連用戶端數, int
m_receiveBufferSize = receivevBufferSize; // 接收緩衝區大小, int
m_sendBufferSize = sendBufferSize; // int

m_bufferBlockIndex = 0; // 當前未用緩衝區塊索引號, int
m_bufferBlockIndexStack = new Stack(); // 可重用緩衝區塊索引號, Stack<int>泛型

m_receiveBuffer = new byte[m_receiveBufferSize * m_maxSessionCount]; // 接收緩衝區大小
m_sendBuffer = new byte[m_sendBufferSize * m_maxSessionCount];
}

public int ReceiveBufferSize
{
get { return m_receiveBufferSize; }
}

public int SendBufferSize
{
get { return m_sendBufferSize; }
}

public byte[] ReceiveBuffer
{
get { return m_receiveBuffer; }
}

public byte[] SendBuffer
{
get { return m_sendBuffer; }
}

public void FreeBufferBlockIndex(int bufferBlockIndex) // 回收塊索引號
{
if (bufferBlockIndex == -1)
{
return;
}

lock (this)
{
m_bufferBlockIndexStack.Push(bufferBlockIndex);
}
}

public int GetBufferBlockIndex() // 擷取可用緩衝區塊索引號
{
lock (this)
{
int blockIndex = -1;

if (m_bufferBlockIndexStack.Count > 0) // 有用過釋放的緩衝塊
{
blockIndex = m_bufferBlockIndexStack.Pop();
}
else
{
if (m_bufferBlockIndex < m_maxSessionCount) // 有未用緩衝區塊
{
blockIndex = m_bufferBlockIndex++;
}
}

return blockIndex;
}
}

public int GetReceivevBufferOffset(int bufferBlockIndex)
{
if (bufferBlockIndex == -1) // 沒有使用共用塊
{
return 0; // 表示建立緩衝區,位移為0
}

return bufferBlockIndex * m_receiveBufferSize; // 接收塊的位移(數組起始下標)
}

public int GetSendBufferOffset(int bufferBlockIndex)
{
if (bufferBlockIndex == -1) // 沒有使用共用塊
{
return 0;
}

return bufferBlockIndex * m_sendBufferSize; // 發送塊位移(數組起始下標)
}

public void Clear()
{
lock (this)
{
m_bufferBlockIndexStack.Clear();
m_receiveBuffer = null;
m_sendBuffer = null;
}
}
}

上述代碼中,m_maxSessionCount是Socket伺服器最大的可連用戶端Socket數,BufferManager建構函式要求該數以及接收和發送緩衝區的大小,從而建立兩個大的、可重複使用共用緩衝區。

具體使用步驟如下:

  1. 建立一個BufferManager對象 m_bufferManager
  2. 擷取緩衝區塊索引號:m_bufferBlockIndex = m_bufferManager.GetBufferBlockIndex()
  3. 非同步接收:先計算出緩衝區位移地址,然後開始接收
  4. 非同步發送:先考慮發送串長度,然後決定是否使用緩衝區,見隨後的代碼
  5. 不使用塊索引號時:m_bufferManager.FreeBufferBlockIndext(m_bufferBlockIndex)回收

下面是申請一個緩衝區索引號的程式碼範例:

m_bufferBlockIndex = bufferManager.GetBufferBlockIndex();
if (m_bufferBlockIndex == -1) // 沒有空塊, 建立接收/發送緩衝區
{
m_receiveBuffer = new byte[m_bufferManager.ReceiveBufferSize];
m_sendBuffer = new byte[m_bufferManager.SendBufferSize];
}
else // 有空的緩衝區塊,直接引用該塊
{
m_receiveBuffer = m_bufferManager.ReceiveBuffer;
m_sendBuffer = m_bufferManager.SendBuffer;
}

下面是Socket非同步接收資料的程式碼範例:

int bufferOffset = m_bufferManager.GetReceivevBufferOffset(m_bufferBlockIndex);  // 計算開始地址
m_socket.BeginReceive(m_receiveBuffer, bufferOffset, m_bufferManager.ReceiveBufferSize,
SocketFlags.None, this.EndReceiveDatagram, this);

下面是Socket非同步發送字串datagramText的程式碼範例:

int byteLength = Encoding.ASCII.GetByteCount(datagramText);
if (byteLength <= m_bufferManager.SendBufferSize) // 可以用共用緩衝區
{
int bufferOffset = m_bufferManager.GetSendBufferOffset(m_bufferBlockIndex); // 計算開始地址
Encoding.ASCII.GetBytes(datagramText, 0, byteLength, m_sendBuffer, bufferOffset);
m_socket.BeginSend(m_sendBuffer, bufferOffset, byteLength, SocketFlags.None,
this.EndSendDatagram, this);
}
else // 不能使用共用緩衝區
{
byte[] data = Encoding.ASCII.GetBytes(datagramText); // 獲得資料位元組數組
m_socket.BeginSend(data, 0, data.Length, SocketFlags.None, this.EndSendDatagram, this);
}

在資料發送時,如果發送緩衝區大小比實際發送的包長度大,上述非同步發送可以使用BufferManager公用緩衝區。否則,需要建立一個發送緩衝區(字
節數組)。此外,用共用緩衝區分多次發送長資料包也是一個可考慮的方案,但實現比較複雜(留待以後解決)。資料接收則直接使用
BufferManager,因為長資料包由Socket自動分多次接收,不需要考慮分包及包接收順序等問題。另一個需要注意的是,擷取的緩衝區索引塊號
要記住回收它們。

基於事件驅動的SocketAsyncEventArgs效能的改善,不僅與使用共用緩衝區的技術相關,更與其在完成連接埠(IOCP)共用
SocketAsyncEventArgs對象有關,該對象可重複使用。而在傳統的非同步Socket處理時,總會建立一個IAsyncResult對象,
該對象不可重複使用,且必須調用AsyncWaitHandle.Close()釋放資源。顯然,共用緩衝區技術只稍稍改善了應用系統的效能,沒有從根本
上消除Socket的APM的缺陷。

上述緩衝區類提供了一個Socket可重複使用的的接收/發送緩衝區技術方案,具體實現可以參看拙文可擴充多線程非同步Socket伺服器架構EMTASS 2.0和版本曆史2.1中的簡介及源碼資源。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.