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

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

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

固定缓存设计

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

[csharp] view plain copy
print ?
  1. public class AsyncSocketUserTokenPool  
  2. {  
  3.     private Stack<AsyncSocketUserToken> m_pool;  
  4.   
  5.     public AsyncSocketUserTokenPool(int capacity)  
  6.     {  
  7.         m_pool = new Stack<AsyncSocketUserToken>(capacity);  
  8.     }  
  9.   
  10.     public void Push(AsyncSocketUserToken item)  
  11.     {  
  12.         if (item == null)  
  13.         {  
  14.             throw new ArgumentException("Items added to a AsyncSocketUserToken cannot be null");  
  15.         }  
  16.         lock (m_pool)  
  17.         {  
  18.             m_pool.Push(item);  
  19.         }  
  20.     }  
  21.   
  22.     public AsyncSocketUserToken Pop()  
  23.     {  
  24.         lock (m_pool)  
  25.         {  
  26.             return m_pool.Pop();  
  27.         }  
  28.     }  
  29.   
  30.     public int Count  
  31.     {  
  32.         get { return m_pool.Count; }  
  33.     }  
  34. }  
    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; }
        }
    }
初始化加入列表的代码如下:

[csharp] view plain copy
print ?
  1. public void Init()  
  2. {  
  3.     AsyncSocketUserToken userToken;  
  4.     for (int i = 0; i < m_numConnections; i++) //按照连接数建立读写对象  
  5.     {  
  6.         userToken = new AsyncSocketUserToken(m_receiveBufferSize);  
  7.         userToken.ReceiveEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);  
  8.         userToken.SendEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);  
  9.         m_asyncSocketUserTokenPool.Push(userToken);  
  10.     }  
  11. }  
        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);
            }
        }

动态缓存设计

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

[csharp] view plain copy
print ?
  1. public class DynamicBufferManager  
  2.     {  
  3.         public byte[] Buffer { getset; } //存放内存的数组  
  4.         public int DataCount { getset; } //写入数据大小  
  5.   
  6.         public DynamicBufferManager(int bufferSize)  
  7.         {  
  8.             DataCount = 0;  
  9.             Buffer = new byte[bufferSize];  
  10.         }  
  11.   
  12.         public int GetDataCount() //获得当前写入的字节数  
  13.         {  
  14.             return DataCount;  
  15.         }  
  16.   
  17.         public int GetReserveCount() //获得剩余的字节数  
  18.         {  
  19.             return Buffer.Length - DataCount;  
  20.         }  
  21.   
  22.         public void Clear(int count) //清理指定大小的数据  
  23.         {  
  24.             if (count >= DataCount) //如果需要清理的数据大于现有数据大小,则全部清理  
  25.             {  
  26.                 DataCount = 0;  
  27.             }  
  28.             else  
  29.             {  
  30.                 for (int i = 0; i < DataCount - count; i++) //否则后面的数据往前移  
  31.                 {  
  32.                     Buffer[i] = Buffer[count + i];  
  33.                 }  
  34.                 DataCount = DataCount - count;  
  35.             }  
  36.         }  
  37.   
  38.         public void WriteBuffer(byte[] buffer, int offset, int count)  
  39.         {  
  40.             if (GetReserveCount() >= count) //缓冲区空间够,不需要申请  
  41.             {  
  42.                 Array.Copy(buffer, offset, Buffer, DataCount, count);  
  43.                 DataCount = DataCount + count;  
  44.             }  
  45.             else //缓冲区空间不够,需要申请更大的内存,并进行移位  
  46.             {  
  47.                 int totalSize = Buffer.Length + count - GetReserveCount(); //总大小-空余大小  
  48.                 byte[] tmpBuffer = new byte[totalSize];  
  49.                 Array.Copy(Buffer, 0, tmpBuffer, 0, DataCount); //复制以前的数据  
  50.                 Array.Copy(buffer, offset, tmpBuffer, DataCount, count); //复制新写入的数据  
  51.                 DataCount = DataCount + count;  
  52.                 Buffer = tmpBuffer; //替换  
  53.             }  
  54.         }  
  55.   
  56.         public void WriteBuffer(byte[] buffer)  
  57.         {  
  58.             WriteBuffer(buffer, 0, buffer.Length);  
  59.         }  
  60.   
  61.         public void WriteShort(short value, bool convert)  
  62.         {  
  63.             if (convert)  
  64.             {  
  65.                 value = System.Net.IPAddress.HostToNetworkOrder(value); //NET是小头结构,网络字节是大头结构,需要客户端和服务器约定好  
  66.             }  
  67.             byte[] tmpBuffer = BitConverter.GetBytes(value);  
  68.             WriteBuffer(tmpBuffer);  
  69.         }  
  70.   
  71.         public void WriteInt(int value, bool convert)  
  72.         {  
  73.             if (convert)  
  74.             {  
  75.                 value = System.Net.IPAddress.HostToNetworkOrder(value); //NET是小头结构,网络字节是大头结构,需要客户端和服务器约定好  
  76.             }              
  77.             byte[] tmpBuffer = BitConverter.GetBytes(value);  
  78.             WriteBuffer(tmpBuffer);  
  79.         }  
  80.   
  81.         public void WriteLong(long value, bool convert)  
  82.         {  
  83.             if (convert)  
  84.             {  
  85.                 value = System.Net.IPAddress.HostToNetworkOrder(value); //NET是小头结构,网络字节是大头结构,需要客户端和服务器约定好  
  86.             }  
  87.             byte[] tmpBuffer = BitConverter.GetBytes(value);  
  88.             WriteBuffer(tmpBuffer);  
  89.         }  
  90.   
  91.         public void WriteString(string value) //文本全部转成UTF8,UTF8兼容性好  
  92.         {  
  93.             byte[] tmpBuffer = Encoding.UTF8.GetBytes(value);  
  94.             WriteBuffer(tmpBuffer);  
  95.         }  
  96.     }  
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);
        }
    }

异步发送列表

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

[csharp] view plain copy
print ?
  1. namespace SocketAsyncSvr  
  2. {  
  3.     struct SendBufferPacket  
  4.     {  
  5.         public int Offset;  
  6.         public int Count;  
  7.     }  
  8.   
  9.     //由于是异步发送,有可能接收到两个命令,写入了两次返回,发送需要等待上一次回调才发下一次的响应  
  10.     public class AsyncSendBufferManager  
  11.     {  
  12.         private DynamicBufferManager m_dynamicBufferManager;  
  13.         public DynamicBufferManager DynamicBufferManager { get { return m_dynamicBufferManager; } }  
  14.         private List<SendBufferPacket> m_sendBufferList;  
  15.         private SendBufferPacket m_sendBufferPacket;  
  16.   
  17.         public AsyncSendBufferManager(int bufferSize)  
  18.         {  
  19.             m_dynamicBufferManager = new DynamicBufferManager(bufferSize);  
  20.             m_sendBufferList = new List<SendBufferPacket>();  
  21.             m_sendBufferPacket.Offset = 0;  
  22.             m_sendBufferPacket.Count = 0;  
  23.         }  
  24.   
  25.         public void StartPacket()  
  26.         {  
  27.             m_sendBufferPacket.Offset = m_dynamicBufferManager.DataCount;  
  28.             m_sendBufferPacket.Count = 0;  
  29.         }  
  30.   
  31.         public void EndPacket()  
  32.         {  
  33.             m_sendBufferPacket.Count = m_dynamicBufferManager.DataCount - m_sendBufferPacket.Offset;  
  34.             m_sendBufferList.Add(m_sendBufferPacket);  
  35.         }  
  36.   
  37.         public bool GetFirstPacket(ref int offset, ref int count)  
  38.         {  
  39.             if (m_sendBufferList.Count <= 0)  
  40.                 return false;  
  41.             offset = m_sendBufferList[0].Offset;  
  42.             count = m_sendBufferList[0].Count;  
  43.             return true;  
  44.         }  
  45.   
  46.         public bool ClearFirstPacket()  
  47.         {  
  48.             if (m_sendBufferList.Count <= 0)  
  49.                 return false;  
  50.             int count = m_sendBufferList[0].Count;  
  51.             m_dynamicBufferManager.Clear(count);  
  52.             m_sendBufferList.RemoveAt(0);  
  53.             return true;  
  54.         }  
  55.   
  56.         public void ClearPacket()  
  57.         {  
  58.             m_sendBufferList.Clear();  
  59.             m_dynamicBufferManager.Clear(m_dynamicBufferManager.DataCount);  
  60.         }  
  61.     }  
  62. }  
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#也属于初学,错误在所难免,欢迎指正和指导。邮箱地址:[email protected]

猜你喜欢

转载自blog.csdn.net/andrewniu/article/details/80229025