C#高性能大容量SOCKET并发(四):缓存设计

C#高性能大容量SOCKET并发(零):代码结构说明 C#版完成端口具有以下特点: 连接在线管理(提供在线连接维护,连接会话管理,数据接收,连接断开等相关事件跟踪); 发送数据智能合并(组件会根据资源使用情况,对多个同时发送向同一连接的多个消息数据进行合并写入缓冲区; 内存池管理(提供一系列的接收和发送buffer可复用池); 数据读写封装; 通讯协议封装; 阅读详情

在编写服务端大并发的应用程序,需要非常注意缓存设计,缓存的设计是一个折衷的结果,需要通过并发测试反复验证。有很多服务程序是在启动时申请足够的内存空间,避免在运行期间再申请空间,这种是固定空间申请。还有一种是在运行期间动态增长的缓存设计,随着运行动态申请内存,这种事动态空间申请。这两种机制各有优劣,固定空间申请优点是效率高,运行稳定,缺点是对应用场景具有限制;动态空间申请优点是能适应更好的应用场景,缺点是效率相对低一些,并发数降一些;这种性能下降不是太明显,毕竟申请释放内存的效率NET是有优化的,具体需要根据应用场景设计。

在C#版IOCP中我们结合了固定缓存设计和动态缓存设计,其中服务端支持连接数使用了固定缓存设计(AsyncSocketUserTokenPool),根据程序启动时设置的最大连接数申请固定个数的对象。其中接收数据缓存(DynamicBufferManager m_receiveBuffer)、发送数据列表(AsyncSendBufferManager m_sendBuffer)是随着接收数据大小动态增长。

固定缓存设计

固定缓存设计我们需要建立一个列表进行,并在初始化的时候加入到列表中,实现非常简单,列出代码供参考。

    public class AsyncSocketUserTokenPool
    {
        private Stack<AsyncSocketUserToken> m_pool;

        public AsyncSocketUserTokenPool(int capacity)
        {
            m_pool = new Stack<AsyncSocketUserToken>(capacity);
        }

        public void Push(AsyncSocketUserToken item)
        {
            if (item == null)
            {
                throw new ArgumentException("Items added to a AsyncSocketUserToken cannot be null");
            }
            lock (m_pool)
            {
                m_pool.Push(item);
            }
        }

        public AsyncSocketUserToken Pop()
        {
            lock (m_pool)
            {
                return m_pool.Pop();
            }
        }

        public int Count
        {
            get { return m_pool.Count; }
        }
    }
初始化加入列表的代码如下:

        public void Init()
        {
            AsyncSocketUserToken userToken;
            for (int i = 0; i < m_numConnections; i++) //按照连接数建立读写对象
            {
                userToken = new AsyncSocketUserToken(m_receiveBufferSize);
                userToken.ReceiveEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                userToken.SendEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                m_asyncSocketUserTokenPool.Push(userToken);
            }
        }

动态缓存设计

动态缓存是随着数据量大小动态增长,申请的内存在运行过程中重复利用,不释放,这样对内存只进行读写,不进行申请和释放,整体性能较高,因为内存申请释放比读写的效率低很多,因为申请释放内存需要进行加锁,进行系统内核和用户切换,因此使用动态缓存可以降低内核和用户态切换,提高性能。动态缓存的代码如下:

public class DynamicBufferManager
    {
        public byte[] Buffer { get; set; } //存放内存的数组
        public int DataCount { get; set; } //写入数据大小

        public DynamicBufferManager(int bufferSize)
        {
            DataCount = 0;
            Buffer = new byte[bufferSize];
        }

        public int GetDataCount() //获得当前写入的字节数
        {
            return DataCount;
        }

        public int GetReserveCount() //获得剩余的字节数
        {
            return Buffer.Length - DataCount;
        }

        public void Clear(int count) //清理指定大小的数据
        {
            if (count >= DataCount) //如果需要清理的数据大于现有数据大小,则全部清理
            {
                DataCount = 0;
            }
            else
            {
                for (int i = 0; i < DataCount - count; i++) //否则后面的数据往前移
                {
                    Buffer[i] = Buffer[count + i];
                }
                DataCount = DataCount - count;
            }
        }

        public void WriteBuffer(byte[] buffer, int offset, int count)
        {
            if (GetReserveCount() >= count) //缓冲区空间够,不需要申请
            {
                Array.Copy(buffer, offset, Buffer, DataCount, count);
                DataCount = DataCount + count;
            }
            else //缓冲区空间不够,需要申请更大的内存,并进行移位
            {
                int totalSize = Buffer.Length + count - GetReserveCount(); //总大小-空余大小
                byte[] tmpBuffer = new byte[totalSize];
                Array.Copy(Buffer, 0, tmpBuffer, 0, DataCount); //复制以前的数据
                Array.Copy(buffer, offset, tmpBuffer, DataCount, count); //复制新写入的数据
                DataCount = DataCount + count;
                Buffer = tmpBuffer; //替换
            }
        }

        public void WriteBuffer(byte[] buffer)
        {
            WriteBuffer(buffer, 0, buffer.Length);
        }

        public void WriteShort(short value, bool convert)
        {
            if (convert)
            {
                value = System.Net.IPAddress.HostToNetworkOrder(value); //NET是小头结构,网络字节是大头结构,需要客户端和服务器约定好
            }
            byte[] tmpBuffer = BitConverter.GetBytes(value);
            WriteBuffer(tmpBuffer);
        }

        public void WriteInt(int value, bool convert)
        {
            if (convert)
            {
                value = System.Net.IPAddress.HostToNetworkOrder(value); //NET是小头结构,网络字节是大头结构,需要客户端和服务器约定好
            }            
            byte[] tmpBuffer = BitConverter.GetBytes(value);
            WriteBuffer(tmpBuffer);
        }

        public void WriteLong(long value, bool convert)
        {
            if (convert)
            {
                value = System.Net.IPAddress.HostToNetworkOrder(value); //NET是小头结构,网络字节是大头结构,需要客户端和服务器约定好
            }
            byte[] tmpBuffer = BitConverter.GetBytes(value);
            WriteBuffer(tmpBuffer);
        }

        public void WriteString(string value) //文本全部转成UTF8,UTF8兼容性好
        {
            byte[] tmpBuffer = Encoding.UTF8.GetBytes(value);
            WriteBuffer(tmpBuffer);
        }
    }

异步发送列表

异步发送列表是在动态缓存的基础上加了一个列表管理,记录每个包的位置信息,并提供管理函数,代码示例如下:

namespace SocketAsyncSvr
{
    struct SendBufferPacket
    {
        public int Offset;
        public int Count;
    }

    //由于是异步发送,有可能接收到两个命令,写入了两次返回,发送需要等待上一次回调才发下一次的响应
    public class AsyncSendBufferManager
    {
        private DynamicBufferManager m_dynamicBufferManager;
        public DynamicBufferManager DynamicBufferManager { get { return m_dynamicBufferManager; } }
        private List<SendBufferPacket> m_sendBufferList;
        private SendBufferPacket m_sendBufferPacket;

        public AsyncSendBufferManager(int bufferSize)
        {
            m_dynamicBufferManager = new DynamicBufferManager(bufferSize);
            m_sendBufferList = new List<SendBufferPacket>();
            m_sendBufferPacket.Offset = 0;
            m_sendBufferPacket.Count = 0;
        }

        public void StartPacket()
        {
            m_sendBufferPacket.Offset = m_dynamicBufferManager.DataCount;
            m_sendBufferPacket.Count = 0;
        }

        public void EndPacket()
        {
            m_sendBufferPacket.Count = m_dynamicBufferManager.DataCount - m_sendBufferPacket.Offset;
            m_sendBufferList.Add(m_sendBufferPacket);
        }

        public bool GetFirstPacket(ref int offset, ref int count)
        {
            if (m_sendBufferList.Count <= 0)
                return false;
            offset = m_sendBufferList[0].Offset;
            count = m_sendBufferList[0].Count;
            return true;
        }

        public bool ClearFirstPacket()
        {
            if (m_sendBufferList.Count <= 0)
                return false;
            int count = m_sendBufferList[0].Count;
            m_dynamicBufferManager.Clear(count);
            m_sendBufferList.RemoveAt(0);
            return true;
        }

        public void ClearPacket()
        {
            m_sendBufferList.Clear();
            m_dynamicBufferManager.Clear(m_dynamicBufferManager.DataCount);
        }
    }
}

DEMO下载地址: http://download.youkuaiyun.com/detail/sqldebug_fan/7467745
免责声明:此代码只是为了演示C#完成端口编程,仅用于学习和研究,切勿用于商业用途。水平有限,C#也属于初学,错误在所难免,欢迎指正和指导。邮箱地址:fansheng_hx@163.com。

C#高性能大容量SOCKET并发(十一):编写上传客户端 C#的TcpClient封装了NET的底层Socket操作,基于TCP协议,提供了阻塞和非阻塞模式调用,具体是设置m_tcpClient.Client.Blocking = true表示使用阻塞模式,反之则使用非阻塞模式。阻塞模式表示接收完指定长度的数据才返回,非阻塞模式表示收到一点数据就返回。 如我们调用m_tcpClient.Client.Receive(m_recvBuffer.Buffer, sizeof(int), packetLength, SocketFlags.None),假设传入的长度为1 阅读详情

相关推荐

C# Socket高并发

C#高性能大容量SOCKET并发(转) C#高性能大容量SOCKET并发(零):代码结构说明 C#高性能大容量SOCKET并发(一):IOCP完成端口例子介绍 C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装 C#高性能大容量SOCKET并发(三):接收、发送 C#高性能大容量SOCKET并发(四):缓存设计 C#高性能大容量SOCKET并发...

ishenjiaming的博客 2897

C# socket通信 接收缓冲区大小设置,以及粘包问题的解决

C# socket通信 接收缓冲区大小,以及粘包问题的解决

wufadeshou的博客 9155

C#高性能大容量SOCKET并发(三):接收、发送

异步数据接收有可能收到的数据不是一个完整包,或者接收到的数据超过一个包的大小,因此我们需要把接收的数据进行缓存。异步发送我们也需要把每个发送的包加入到一个队列,然后通过队列逐个发送出去,如果每个都实时发送,有可能造成上一个数据包未发送完成,这时再调用SendAsync会抛出异常,提示SocketAsyncEventArgs正在进行异步操作,因此我们需要建立接收缓存和发送缓存。

SQLDebug_Fan的专栏 3万+

C# 这么多的缓存技术,该如何选择

通过使用内存缓存、分布式缓存、输出缓存、数据缓存、缓存依赖、Cache-Aside 模式、Read-Through 和 Write-Through 缓存、CDN 缓存、HTTP 缓存以及缓存并发控制等技术,您可以为用户提供更快、更稳定的应用程序体验。在 Write-Through 缓存中,当数据需要更新时,缓存系统会将数据同时写入缓存和数据源,确保它们保持一致。4. 缓存失效策略:根据数据的更新频率和敏感性选择合适的缓存失效策略,例如绝对过期时间、滑动过期时间或基于缓存依赖的失效策略。

caopeihuaaini的博客 2095

从零打造:C# 实现高性能缓存策略与机制,全程代码解析与实战演示

以上代码展示了如何使用 C# 实现一个基础的高性能缓存框架,包括缓存数据存储、滑动/绝对过期策略、LRU 驱逐策略以及线程安全考虑。在实际项目中,也可以选择使用成熟的第三方缓存库(如 Microsoft.Extensions.Caching.Memory 或 Redis),它们通常提供了更为丰富和高度优化的缓存功能。实现一个高性能缓存策略与机制涉及多个关键点,包括缓存数据存储、缓存策略(如过期策略、驱逐策略)、线程安全、性能优化等。上述代码中已经实现了基于滑动过期和绝对过期的缓存项过期判断。

java专栏 763

C#读取web.config配置,建立高速缓存机制

针对.net框架下,建立配置文件全局共享机制解决方法,利用application实现高速缓存全局共享,并与session进行区分

C# ReceiveBufferSize和TcpWindowSize

万兆网高速下载,开始的时候,怎么设置ReceiveBufferSize,下载速度都没啥变化,不晓得怎么回事。 后面用wireshark抓包,发现window size 和我设置的ReceiveBufferSize不一致,我这就疑惑了,怎么会事,你TM在逗我。 经过多次试验,发现设置ReceiveBufferSize和SendBufferSize要在socket connect之前设置,链接了再

yuhai738639的博客 4344

Clion编译stm32cube初始化工程时non constant or forward reference address expression for section .ARM.extab报错

相信应该有同学在看了稚晖君关于clion开发stm32的文章之后也想在自己PC上实现“优雅の嵌入式开发”遇到和我类似的问题。他的文章是22年写的,我个人猜测应当是版本更新导致的不适配。意思就是说 READONLY 只能在gcc11以后的版本中使用,gcc10及以前的版本将无法正确识别,也就有如下错误。或者就可以将gcc升级至最新的版本。记得在clion工具链中修改c,cpp编译器的位置。可以将文本括号中的 READONLY 全部删除。当时由于没有注意到这行注释导致在这里卡了好久。

2301_77256849的博客 2510

C# Socket 高并发

https://www.cnblogs.com/AaronAndJoe/p/5796369.html C#高性能大容量SOCKET并发(零):代码结构说明 C#高性能大容量SOCKET并发(一):IOCP完成端口例子介绍 C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装 C#高性能大容量SOCKET并发(三):接收、发送 C#高性能大容量SOCKET...

qq_18932003的博客 1079

ComfyUI安装包运行前准备:增加虚拟内存

在Windows系统中,虚拟内存是一种重要的系统资源管理机制,它主要通过将硬盘空间用作内存来扩展系统的可用内存容量。以下是虚拟内存的主要作用: 1. 扩展物理内存容量 当物理内存(RAM)不足时,系统会将一部分数据从内存转移到硬盘上的虚拟内存区域(也称为“分页文件”或“交换文件”)。这样可以为正在运行的程序提供额外的“内存空间”,确保系统能够继续运行,而不会因为内存不足而崩溃。 2. 提高系统稳定性 虚拟内存为系统提供了一个缓冲区。如果没有虚拟内存...

聚梦小课堂 1万+

C#高性能大容量SOCKET并发(转)

C#高性能大容量SOCKET并发(零):代码结构说明 C#高性能大容量SOCKET并发(一):IOCP完成端口例子介绍C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装C#高性能大容量SOCKET并发(三):接收、发送C#高性能大容量SOCKET并发(四):缓存设计C#高性能大容量SOCKET并发(五):粘包、分包、解包 C#高性能大容量SOCKET并发(六):超...

Andrewniu的博客 1601

Cocos2d-x手游逆向实战:Frida高阶Hook与动态解密技术

Cocos2d-x作为主流跨平台游戏引擎,其混合架构(C++核心+JNI桥接+Lua/JS脚本)和强加固策略(符号剥离、anti-frida、so加壳)使传统逆向方法失效。本文围绕‘无符号定位’‘C++函数硬编码地址识别’‘Lua层AST级劫持’‘OpenGL内存回溯解密’四大关键技术展开,深入解析如何在真实上线项目中稳定hook被strip的libcocos2d.so关键函数、绕过反调试心跳检测、从glTexImage2D参数逆向追踪明文资源,并结合熵值扫描实现零先验知识的内存明文发现。面向已掌握Frid

weixin_30345577的博客 672

C#高性能大容量SOCKET并发完成端口例子(有C#客户端)完整实例源码

遥望星空 好好干,有前途! 博客园 首页 新随笔 联系 管理 订阅 随笔- 1082  文章- 0  评论- 151  C#高性能大容量SOCKET并发(转) C#高性能大容量SOCKET并发(零):代码结构说明  C#高性能大容量SOCKET并发(一):IOCP完成端口例子介绍 C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装 C#高性...

m0_37777700的博客 3293

光伏电站发电功率预测实战:从基础物理模型到LSTM时间序列分析

本文系统介绍了光伏电站发电功率预测的实战方法,从基础的物理晴空模型到先进的LSTM时间序列分析。通过结合电工杯等竞赛中常用的代码与模型,详细阐述了如何利用历史数据和数值天气预报构建高精度预测体系,并针对阴雨天气等预测瓶颈提出了分场景建模与模型融合等优化策略,为电站运维和电网调度提供可靠的数据支持。

pp12345的博客 215

Vue3 - Element Plus 组件库 Tabs 标签页右侧区域插入按钮,<el-tabs> / <el-tab-pane> 选项卡组件右侧自定义插槽内容教程(右边区域添加自定义按钮功能区)

vue3,elementplus,tabs选项卡如何在插槽按钮,tabs标签页组件怎么自定义右侧内容,vue3elementplustabs组件右侧插槽按钮,element-plus在el-tabs组件的最右侧添加按钮,如何在选项卡右边加入按钮搜索框等内容,哪个属性能在tab选项卡右侧插入自定义按钮,在el-tabs组件最右侧添加按钮,vue3el-tabs组件怎么自定义右侧插槽内容,Tabs标签页右侧自定义插槽,ElementPlusTab右边加按钮,el-tabs组件自定义右侧按钮,在E

🏆 前端领域优质创作者 7015

C#高性能大容量SOCKET并发(十):SocketAsyncEventArgs线程模型

SocketAsyncEventArgs编程模式不支持设置同时工作线程个数,使用的NET的IO线程,由NET底层提供,这点和直接使用完成端口API编程不同。NET底层IO线程也是每个异步事件都是由不同的线程返回到Completed事件,因此在Completed事件需要对用户对象进行加锁,避免同一个用户对象同时触发两个Completed事件。

SQLDebug_Fan的专栏 1万+

跨越边界:用MessagePipe实现无缝的进程间通信

MessagePipe是一个用于.NET平台的消息传递库,它专注于简化异步消息处理和进程间通信。通过提供一系列易于使用的API,开发者可以快速搭建起稳定可靠的通信管道,支持多种传输协议(如TCP、Unix域套接字等),并内置了序列化/反序列化功能,使得跨语言和跨平台的数据交换变得轻而易举。为了方便序列化和反序列化操作,我们需要为每种消息定义对应的类或结构体,并标记属性。这样可以确保消息在发送方和接收方之间正确无误地传递。[Key0set;[Key1set;

墨夶的博客 816

C#高性能大容量SOCKET并发(五):粘包、分包、解包

C#高性能大容量SOCKET并发(五):粘包、分包、解包 原文:C#高性能大容量SOCKET并发(五):粘包、分包、解包 粘包 使用TCP长连接就会引入粘包的问题,粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。粘包可能...

weixin_30662539的博客 266

以阿克曼转向车为例对URDF和XACRO文件进行解读

   最近我在古月学院购买了在Gazebo中实现阿克曼转向车的仿真课程,课程资料的阿克曼小车的XACRO文件在Gazebo里加载的时候报了三个错误,在这之前,我没接触过URDF或XACRO文件,本篇文章就以这个阿克曼小车的XACRO文件为例,对URDF或XACRO文件的格式和内容进行解读,期望经过解读和学习,找到错误原因及所在,并解决这三个错误    先来放一下上面右图所示的模型的完整的XACRO文件: <?xml version="1.0"?> <!-- racecar.urdf.x

慕羽★的博客 3700

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

上一篇: C#高性能大容量SOCKET并发(三):接收、发送
下一篇: C#高性能大容量SOCKET并发(五):粘包、分包、解包
SQLDebug_Fan
博客等级 码龄17年 710粉丝 26原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值