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