`
evget
  • 浏览: 144640 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
文章分类
社区版块
存档分类

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
分享到:
评论
1 楼 panpanjava 2012-03-05  
 

相关推荐

    C#实现Socket高性能、大容量并发(附完整实例源码)

    - **连接池**:对于短连接场景,可实现Socket连接池,复用已建立的连接,减少握手和关闭的开销。 2. **大容量并发处理** - **多线程/线程池**:为每个客户端请求分配一个工作线程,但过多线程会消耗大量资源。...

    c# socket demo 已经封装成共通

    在.NET框架中,C#语言提供了丰富的库支持网络通信,其中Socket类是核心部分,用于实现TCP/IP协议栈中的各种网络通信。本示例“c# socket demo 已经封装成共通”聚焦于C#中如何使用Socket进行TCP通信,并且已经封装...

    NET_Source_C#Socket通信_socket_高性能异步通讯_

    此外,SocketAsyncEventArgs(SAEA)组件可以更高效地复用接收和发送缓冲区,减少内存分配和垃圾回收,进一步提升性能。 3. **Socket与缓冲区管理**: 在处理大量数据时,有效管理缓冲区至关重要。可以创建一个...

    C#高性能Socket设计实现

    通过预分配缓冲区并合理地复用,可以减少内存分配和释放的开销,提高性能。 5. **并发处理**:多线程或异步I/O可用于同时处理多个连接。C#的ThreadPool类和Task Parallel Library (TPL)可以帮助开发者轻松实现并发...

    C#实现的多线程异步Socket数据包接收器框架[整理].pdf

    【C#实现的多线程异步Socket数据包接收器框架】 C#中的多线程异步Socket数据包接收器框架是一种高效且可靠的方法,用于处理来自多个客户端的数据传输。在给定的描述中,我们可以看到这个框架是为了解决交通流量数据...

    C#Modbus Socket通信实例.rar

    C#中实现Modbus TCP通信,我们需要使用Socket类,它是.NET Framework提供的基础网络通信组件。Socket类提供了低级别的网络编程接口,可以用于创建TCP/IP或UDP/IP连接。在本例中,我们将专注于TCP,因为Modbus TCP是...

    C# 高性能服务器 - 端口-心跳高性能Socket服务器

    C#配合负载均衡中间件如Nginx或自定义解决方案可实现这一目标。 总结来说,C#的高性能Socket服务器设计涉及多线程处理、端口管理、心跳机制、异常处理、缓冲区优化等多个方面。开发者需要根据具体需求,结合C#的...

    socket通讯 C#实现

    在IT领域,网络通信是不可或缺的一部分,而Socket通信则是实现跨网络节点数据交换的基础。本文将深入探讨如何使用C#语言来实现Socket通信,包括客户端和服务器端的创建与交互。 首先,我们要理解Socket的基本概念。...

    C#高性能大容量SOCKET并发

    2. **缓冲区管理**:合理的缓冲区大小和复用策略可以降低内存分配频率,提高性能。 3. **非阻塞I/O**:使用异步接收和发送数据,使Socket在等待数据时不会阻塞线程,提高并发处理能力。 4. **心跳机制**:保持连接...

    c# socket server

    在IT行业中,网络编程是不可或缺的一部分,而C#语言提供了强大的Socket类库来实现网络通信。本主题聚焦于"C# Socket Server",特别是涉及到异步编程模型`SocketAsyncEventArgs`的性能问题。在这个场景下,我们将深入...

    c# SOCKET 聊天通讯 实例

    在C#中,Socket类位于System.Net.Sockets命名空间下,提供了TCP和UDP两种传输协议的接口,用于实现客户端和服务器之间的数据交换。 **C/S模式** C/S(Client/Server)模式是网络应用的常见架构,包括一个服务端...

    C#高性能大容量SOCKET并发完成端口例子.rar

    本示例"**C#高性能大容量SOCKET并发完成端口例子**"聚焦于C#语言中实现高并发、大容量的Socket通信技术,这在构建大规模、实时性强的服务端应用时尤为关键。本文将深入探讨这个主题,并结合标签"C#SOCKET并发"来详细...

    C#异步Socket包装类.7z

    3. 状态管理:维护连接状态、发送和接收缓冲区等信息,以便于控制通信流程。 4. 并发控制:当有多个并发连接时,需要考虑线程安全问题,防止竞态条件。 5. 性能优化:根据实际情况调整缓冲区大小,合理设置超时...

    C#Socket异步服务器 IOCP 源码

    例如,适当调整缓冲区大小,减少内存分配,优化数据处理逻辑,以及使用非阻塞操作等。 8. **异常处理与资源管理**: 为了保证服务器的健壮性,必须妥善处理异常,并确保及时释放资源。例如,当客户端断开连接或...

    C# Socket TCP UDP 应用实例

    在IT领域,网络通信是不可或缺的一部分,而C#语言提供了强大的Socket类库,使得开发者能够构建基于TCP和UDP协议的应用程序。本实例旨在通过源代码深入解析C#中Socket、TCP与UDP的使用方法。 首先,让我们理解一下...

    C#异步SOCKET

    5. **StateObject**: 在异步操作中,你可能需要在操作之间保持一些状态信息,例如接收或发送缓冲区。这时,你可以创建一个自定义的类(如`StateObject`)来存储这些信息,并在回调方法中使用它。 6. **...

    (C#)封装好的TCP通讯接口

    在封装时,我们需要确保数据被正确地转换为字节序列(例如通过Encoding.GetBytes方法),并考虑到可能的缓冲区大小限制。 3. **数据接收**:同样利用NetworkStream的Read方法,可以从网络流中读取接收到的数据。...

Global site tag (gtag.js) - Google Analytics