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

在Socket的接收/发送方法:Send()、BeginSend()、Receive()、BeginReceive()中,第一个参数是字节数数组,表示当前接收数据区或需要发送的数据。普通Socket应用中,往往是接收/发送时创建数组,使用后该数组空间由托管堆回收。显然,频繁接收/发送将在托管堆上创建很多的内存碎块,影响系统性能。 

使用Socket异步调事件参数类SocketAsyncEventArgs时考虑了上述情况,基本构思为:自定义一个缓冲区管理类如BufferManager,开辟一个大的、可重用接收/发送收缓冲区,用于SendAsync()、ReceiveAsync()等方法,之前使用SetBuffer()和属性OffSet、Count设定缓冲区空间。 

事实上,在.NET 2.0平台上的Socket传统APM(异步编程模型)中,仍然可以使用这个技术。下面是修改的BufferManager类: 
public sealed class BufferManager
{
  // ...成员字段见其构造函数

  public BufferManager(int maxSessionCount, int recvBufferSize, int sendBufferSize)
  {
  m_maxSessionCount = maxSessionCount; // 服务器允许的最大连接(会话)数

  m_recvBufferSize = recvBufferSize; // 接收缓冲区大小
  m_sendBufferSize = sendBufferSize;

  m_availableRecvOffset = 0; // 当前可以的接收缓冲区偏移地址
  m_availbleSendOffset = 0;

  m_availbleOffsetStack = new Stack(); // 可重复使用的缓冲区偏移地址

  m_totalRecvLength = m_recvBufferSize * m_maxSessionCount;
  m_totalSendLength = m_sendBufferSize * m_maxSessionCount;

  m_recvBuffer = new byte[m_totalRecvLength]; // 接收缓冲区
  m_sendBuffer = new byte[m_totalSendLength];
  }

  public int RecvBufferSize
  {
  get { return m_recvBufferSize; }
  }

  public int SendBufferSize
  {
  get { return m_sendBufferSize; }
  }

  public byte[] RecvBuffer
  {
  get { return m_recvBuffer; }
  }

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

  public void FreeOffset(int recvOffset, int sendOffset)
  {
  lock (this)
  {
  m_availbleOffsetStack.Push(recvOffset); // 回收缓冲区偏移地址
  m_availbleOffsetStack.Push(sendOffset);
  }
  }

  public void GetOffset(ref int recvOffset, ref int sendOffset)
  {
  lock (this)
  {
  if (m_availbleOffsetStack.Count > 0) // 有释放的可重用缓冲区
  {
  sendOffset = m_availbleOffsetStack.Pop(); // 再次使用
  recvOffset = m_availbleOffsetStack.Pop();
  }
  else
  {
  if (m_totalRecvLength >= m_availableRecvOffset + m_recvBufferSize &&
  m_totalSendLength >= m_availbleSendOffset + m_recvBufferSize) // 有空间
  {
  recvOffset = m_availableRecvOffset;
  sendOffset = m_availbleSendOffset;

  m_availableRecvOffset += m_recvBufferSize; // 调整可用块指针
  m_availbleSendOffset += m_sendBufferSize;
  }
  else
  {
  throw new IndexOutOfRangeException("buffer index out of range.");
  }
  }
  }
  }
}
上述代码中,m_maxSessionCount是Socket服务器最大的可连客户端Socket数,BufferManager构造函数要求该数以及接收和发送缓冲区的大小,从而创建两个大的、可重复使用共享缓冲区。 

具体使用步骤如下: 
创建一个BufferManager对象 m_bufferManager;
获取一个Socket对象m_socket;
获取接收/发送缓冲区偏移 m_bufferManager.Getffset(ref m_recvBufferOffset, ref m_sendBufferOffset);
异步接收:m_socket(m_bufferManager.RecvBuffer, m_recvBufferOffset, m_bufferManager.RecvBufferSize,...);
异步发送:先考虑发送串长度,然后决定是否使用缓冲区,见随后的代码;
关闭Socket时需要调用:m_bufferManager.FreeOffset(m_recvBufferOffset, m_sendBufferOffset)回收偏移。
下面是发送字符串datagramText的示例代码: 
int byteLength = Encoding.ASCII.GetByteCount(datagramText);
if (byteLength <= m_bufferManager.SendBufferSize) // 可以使用发送缓冲区
{
  byte[] sendBuffer = m_bufferManager.SendBuffer;
  Encoding.ASCII.GetBytes(datagramText, 0, byteLength, sendBuffer, m_sendBufferOffset); // 拷贝到缓冲区
  m_socket.BeginSend(sendBuffer, m_sendBufferOffset, byteLength, ...); // 发送
}
else // 字符串太长,另建字节数组发送
{
  byte[] data = Encoding.ASCII.GetBytes(datagramText); // 获得数据字节数组
  m_socket.BeginSend(data, 0, data.Length, ...); // 发送
}
在数据发送时,如果发送缓冲的大小比实际发送包大的话,那么上述异步发送就可以使用BufferManager公共缓冲区。当然,用缓冲区分多次发送数据,也是一个可以考虑和有效的方案,但实现比较复杂(留待以后解决)。数据接收则直接使用BufferManager,因为长数据包由Socket自动分多次接收,不需要考虑分包及包接收顺序等问题。 

基于事件驱动的SocketAsyncEventArgs性能的改善,不仅与使用共享缓冲区的技术相关,更与其在完成端口(IOCP)共享SocketAsyncEventArgs对象有关,该对象可重复使用。而在传统的异步Socket处理时,总会创建一个IAsyncResult对象,该对象不可重复使用,且必须调用AsyncWaitHandle.Close()释放资源。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值