C#实现的可复用Socket接收发送共享缓冲区类
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
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(datagramT ext, 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()释放资源。