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

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

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

C#高性能大容量SOCKET并发(四):缓存设计的更多相关文章

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

    C#高性能大容量SOCKET并发(零):代码结构说明 C#高性能大容量SOCKET并发(一):IOCP完成端口例子介绍 C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs ...

  2. C#高性能大容量SOCKET并发(零):代码结构说明

    原文:C#高性能大容量SOCKET并发(零):代码结构说明 C#版完成端口具有以下特点: 连接在线管理(提供在线连接维护,连接会话管理,数据接收,连接断开等相关事件跟踪): 发送数据智能合并(组件会根 ...

  3. C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装

    原文:C#高性能大容量SOCKET并发(二):SocketAsyncEventArgs封装 1.SocketAsyncEventArgs介绍 SocketAsyncEventArgs是微软提供的高性能 ...

  4. C#高性能大容量SOCKET并发(十一):编写上传客户端

    原文:C#高性能大容量SOCKET并发(十一):编写上传客户端 客户端封装整体框架 客户端编程基于阻塞同步模式,只有数据正常发送或接收才返回,如果发生错误则抛出异常,基于TcpClient进行封装,主 ...

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

    原文:C#高性能大容量SOCKET并发(十):SocketAsyncEventArgs线程模型 线程模型 SocketAsyncEventArgs编程模式不支持设置同时工作线程个数,使用的NET的IO ...

  6. C#高性能大容量SOCKET并发(六):超时Socket断开(守护线程)和心跳包

    原文:C#高性能大容量SOCKET并发(六):超时Socket断开(守护线程)和心跳包 守护线程 在服务端版Socket编程需要处理长时间没有发送数据的Socket,需要在超时多长时间后断开连接,我们 ...

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

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

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

    原文:C#高性能大容量SOCKET并发(三):接收.发送 异步数据接收有可能收到的数据不是一个完整包,或者接收到的数据超过一个包的大小,因此我们需要把接收的数据进行缓存.异步发送我们也需要把每个发送的 ...

  9. C#高性能大容量SOCKET并发(九):断点续传

    原文:C#高性能大容量SOCKET并发(九):断点续传 上传断点续传 断点续传主要是用在上传或下载文件,一般做法是开始上传的时候,服务器返回上次已经上传的大小,如果上传完成,则返回-1:下载开始的时候 ...

随机推荐

  1. 轻松学习JavaScript十八:DOM编程学习之DOM简单介绍

    一DOM概述 DOM(文档对象模型)是HTML和XML的应用程序接口(API).DOM将把整个页面规划成由节点层级构成的文档. DOM描绘了一个层次化的节点树,执行开发者加入,移除和改动页面的某一部分 ...

  2. 【Qt程序】基于Qt词典开发系列&lt;十二&gt;呼叫讲述

    我们知道,win7系统自带有讲述人,即能够机器读出当前内容,详细能够将电脑锁定.然后点击左下角的button就可以.之前在用Matlab写扫雷游戏的时候,也以前调用过讲述人来进行游戏的语音提示. 详细 ...

  3. 【烽火传递】dp + 单调队列优化

    题目描述 烽火台又称烽燧,是重要的防御设施,一般建在险要处或交通要道上.一旦有敌情发生,白天燃烧柴草,通过浓烟表达信息:夜晚燃烧干柴,以火光传递军情.在某两座城市之间有 n 个烽火台,每个烽火台发出信 ...

  4. 瀑布流的一些CSS实现方式

    一个选择是用CSS3的多列columns,可以参考这篇文章.但这篇文章给的例子并不怎么好理解,我做了一些更改,在每个元素上加了序号.可以看到,多列布局是在每一列上依次排列元素的,第一列排完才开始排第二 ...

  5. 视频和音频播放的演示最简单的例子9:SDL2广播PCM

    ===================================================== 最简单的视频和音频播放的演示样品系列列表: 最简单的视音频播放演示样例1:总述 最简单的视音 ...

  6. WPF 实现繁花曲线

    原文:WPF 实现繁花曲线 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/nihang1234/article/details/83346919 X ...

  7. Array方法总结

    一.不影响原数组产生一个新数组 slice:切片->返回新数组->复制数组:arr.slice(0) arrayObject.slice(start,end): 切片 var arr= [ ...

  8. Modbus 通信协议详解

    一.Modbus 协议简介     Modbus 协议是应用于电子控制器上的一种通用语言.通过此协议,控制器相互之间.控制器经由网络(例如以太网)和其它设备之间可以通信.它已经成为一通用工业标准.有了 ...

  9. Qt5.9 官方发布的新版本亮点的确不胜枚举(而且修复2000+ bugs)

    作者:Summer Fang链接:https://www.zhihu.com/question/60486611/answer/177584284来源:知乎著作权归作者所有.商业转载请联系作者获得授权 ...

  10. win10下Linux子系统开启ssh服务

    原文:win10下Linux子系统开启ssh服务 为了便于交流共同学习,博主QQ群242629020(stm32-MCU认认真真交流群) 欢迎批评指导!!!电梯:https://jq.qq.com/? ...