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

简介:
(原创文章,转载请注明来源: 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中的简介及源码资源下载地址。



本文转自peterzb博客园博客,原文链接:http://www.cnblogs.com/peterzb/archive/2009/05/29/1491603.html,如需转载请自行联系原作者。

目录
相关文章
|
网络协议 Unix Linux
# 2个类轻松构建高效Socket通信库
本文介绍了一种通过两个类`EpollEventHandler`和`IEpollEvent`构建高效Socket通信库的方法。该库支持TCP、UDP和Unix域套接字,采用I/O多路复用技术(如epoll),提升并发处理能力。通过抽象基类和具体事件类的设计,简化了API使用,便于开发者快速上手。文章还提供了服务端与客户端的实例代码,展示其在实际项目中的应用效果。此Socket库适应嵌入式环境,功能定制性强,有助于减少外部依赖并提升维护效率。
383 95
# 2个类轻松构建高效Socket通信库
|
9月前
|
存储 安全 固态存储
基于C#实现的支持文件传输的Socket聊天室
基于C#实现的支持文件传输的Socket聊天室
470 5
|
C# 开发者
C# 一分钟浅谈:Socket 编程基础
【10月更文挑战第7天】本文介绍了Socket编程的基础知识、基本操作及常见问题,通过C#代码示例详细展示了服务器端和客户端的Socket通信过程,包括创建、绑定、监听、连接、数据收发及关闭等步骤,帮助开发者掌握Socket编程的核心技术和注意事项。
526 3
C# 一分钟浅谈:Socket 编程基础
|
开发框架 .NET C#
C#|.net core 基础 - 删除字符串最后一个字符的七大类N种实现方式
【10月更文挑战第9天】在 C#/.NET Core 中,有多种方法可以删除字符串的最后一个字符,包括使用 `Substring` 方法、`Remove` 方法、`ToCharArray` 与 `Array.Copy`、`StringBuilder`、正则表达式、循环遍历字符数组以及使用 LINQ 的 `SkipLast` 方法。
550 8
|
存储 C# 索引
C# 一分钟浅谈:数组与集合类的基本操作
【9月更文挑战第1天】本文详细介绍了C#中数组和集合类的基本操作,包括创建、访问、遍历及常见问题的解决方法。数组适用于固定长度的数据存储,而集合类如`List<T>`则提供了动态扩展的能力。文章通过示例代码展示了如何处理索引越界、数组长度不可变及集合容量不足等问题,并提供了解决方案。掌握这些基础知识可使程序更加高效和清晰。
353 6
|
12月前
|
监控 算法 数据处理
内网实时监控中的 C# 算法探索:环形缓冲区在实时数据处理中的关键作用
本文探讨了环形缓冲区在内网实时监控中的应用,结合C#实现方案,分析其原理与优势。作为固定长度的循环队列,环形缓冲区通过FIFO机制高效处理高速数据流,具备O(1)时间复杂度的读写操作,降低延迟与内存开销。文章从设计逻辑、代码示例到实际适配效果展开讨论,并展望其与AI结合的潜力,为开发者提供参考。
451 2
|
12月前
|
监控 算法 安全
公司电脑监控软件关键技术探析:C# 环形缓冲区算法的理论与实践
环形缓冲区(Ring Buffer)是企业信息安全管理中电脑监控系统设计的核心数据结构,适用于高并发、高速率与短时有效的多源异构数据处理场景。其通过固定大小的连续内存空间实现闭环存储,具备内存优化、操作高效、数据时效管理和并发支持等优势。文章以C#语言为例,展示了线程安全的环形缓冲区实现,并结合URL访问记录监控应用场景,分析了其在流量削峰、关键数据保护和高性能处理中的适配性。该结构在日志捕获和事件缓冲中表现出色,对提升监控系统效能具有重要价值。
342 1
|
网络协议 C# 开发工具
C#中简单Socket编程
1. 先运行服务器代码。服务器将开始监听指定的IP和端口,等待客户端连接。 1. 然后运行客户端代码。客户端将连接到服务器并发送消息。 1. 服务器接收到消息后,将回应客户端,并在控制台上显示接收到的消息。 1. 客户端接收到服务器的回应消息,并在控制台上显示。
882 15
|
消息中间件 网络协议 C#
C#使用Socket实现分布式事件总线,不依赖第三方MQ
`CodeWF.EventBus.Socket` 是一个轻量级的、基于Socket的分布式事件总线系统,旨在简化分布式架构中的事件通信。它允许进程之间通过发布/订阅模式进行通信,无需依赖外部消息队列服务。
C#使用Socket实现分布式事件总线,不依赖第三方MQ
|
C# 数据安全/隐私保护
C# 一分钟浅谈:类与对象的概念理解
【9月更文挑战第2天】本文从零开始详细介绍了C#中的类与对象概念。类作为一种自定义数据类型,定义了对象的属性和方法;对象则是类的实例,拥有独立的状态。通过具体代码示例,如定义 `Person` 类及其实例化过程,帮助读者更好地理解和应用这两个核心概念。此外,还总结了常见的问题及解决方法,为编写高质量的面向对象程序奠定基础。
376 2