C#实现的可复用Socket接收/发送共享缓冲区类

  在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构造函数要求该数以及接收和发送缓冲区的大小,从而创建两个大的、可重复使用共享缓冲区。

具体使用步骤如下:

创建一个BufferManager对象 m_bufferManager
获取缓冲区块索引号:m_bufferBlockIndex = m_bufferManager.GetBufferBlockIndex()
异步接收:先计算出缓冲区偏移地址,然后开始接收
异步发送:先考虑发送串长度,然后决定是否使用缓冲区,见随后的代码
不使用块索引号时: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的缺陷。

  上述代码中,m_maxSessionCount是Socket服务器最大的可连客户端Socket数,BufferManager构造函数要求该数以及接收和发送缓冲区的大小,从而创建两个大的、可重复使用共享缓冲区。

具体使用步骤如下:

创建一个BufferManager对象 m_bufferManager
获取缓冲区块索引号:m_bufferBlockIndex = m_bufferManager.GetBufferBlockIndex()
异步接收:先计算出缓冲区偏移地址,然后开始接收
异步发送:先考虑发送串长度,然后决定是否使用缓冲区,见随后的代码
不使用块索引号时: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的缺陷。

原文地址:http://www.evget.com/zh-CN/Info/ReadInfo.aspx?id=9082
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值