C# Socket高性能 IOCP

本文作者:小飞Jim,转载请写明出处:http://blog.csdn.net/lanmangfeige/article/details/53118043

C#实现Socket通讯有同步模式与异步模式,异步模式的效率比同步模式高。在异步模式中,SocketAsyncEventArgs 类提供了增强功能。

这些增强功能可以避免重复分配的和在大量异步套接字 I/O 内进行同步的对象。通过创建一个SocketAsyncEventArgsPool 池来提高客户端连接速率。

源码下载地址:http://download.csdn.net/detail/lanmangfeige/9679294

IOCP主类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;   
using System.Net.Sockets;
using System.Threading;   

namespace CsharpIOCP
{
    class IOCP
    {
        /// <summary>
        /// Socket-Server
        /// </summary>
        Socket s_Server;

        /// <summary>
        /// 通讯SAEA池
        /// </summary>
        SAEAPool saeaPool_Receive;

        /// <summary>
        /// 侦听客户端
        /// </summary>
        public void ListenClient()
        {
            try
            {
                int iClientMaxCount = 1000; ;//最大客户端数量
                s_Server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                s_Server.Bind(new IPEndPoint(IPAddress.Any, 8801));
                s_Server.Listen(iClientMaxCount);               
                int ibufferSize = 1024; //每个缓冲区大小
                BufferManager bufferManager = new BufferManager(ibufferSize * iClientMaxCount, ibufferSize);
                saeaPool_Receive = new SAEAPool(iClientMaxCount);
                for (int i = 0; i < iClientMaxCount; i++) //填充SocketAsyncEventArgs池
                {
                    SocketAsyncEventArgs saea_New = new SocketAsyncEventArgs();
                    saea_New.Completed += new EventHandler<SocketAsyncEventArgs>(OnIOCompleted);
                    bufferManager.SetBuffer(saea_New);
                    SAEAUserToken userToken = new SAEAUserToken();
                    SocketAsyncEventArgs saea_Send = new SocketAsyncEventArgs();
                    saea_Send.Completed += new EventHandler<SocketAsyncEventArgs>(OnIOCompleted);
                    userToken.SAEA_Send = saea_Send;
                    userToken.HeartbeatTime = DateTime.Now;
                    saea_New.UserToken = userToken;
                    saeaPool_Receive.Add(saea_New);
                }

                Thread tCheckClientHeartbeat = new Thread(CheckClientHeartbeat);
                tCheckClientHeartbeat.IsBackground = true;
                tCheckClientHeartbeat.Start();

                StartAccept(null);
            }
            catch { }
        }

        /// <summary>
        /// 接受来自客户机的连接请求操作
        /// </summary>
        private void StartAccept(SocketAsyncEventArgs saea_Accept)
        {
            if (saea_Accept == null)
            {
                saea_Accept = new SocketAsyncEventArgs();
                saea_Accept.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
            }
            else
                saea_Accept.AcceptSocket = null;  //重用前进行对象清理

            if (!s_Server.AcceptAsync(saea_Accept))
                ProcessAccept(saea_Accept);
        }

        /// <summary>
        /// 连接完成异步操作回调
        /// </summary>
        private void OnAcceptCompleted(object sender, SocketAsyncEventArgs e)
        {
            ProcessAccept(e);
        }

        /// <summary>
        /// 接收或发送完成异步操作回调
        /// </summary>
        private void OnIOCompleted(object sender, SocketAsyncEventArgs e)
        {
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);
                    break;
                case SocketAsyncOperation.Send:
                    ProcessSend(e);
                    break;
            }
        }

        /// <summary>
        /// 异步连接操作完成后调用该方法
        /// </summary>
        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            Socket s = e.AcceptSocket;
            if (s != null && s.Connected)
            {
                try
                {
                    string sClientIP = ((IPEndPoint)s.RemoteEndPoint).Address.ToString();
                    Console.WriteLine(sClientIP + " Client online");
                    SocketAsyncEventArgs saea_Receive = saeaPool_Receive.Pull();
                    if (saea_Receive != null)
                    {
                        Console.WriteLine("Online Client total:" + saeaPool_Receive.GetUsedSAEACount());
                        SAEAUserToken userToken = (SAEAUserToken)saea_Receive.UserToken;
                        userToken.S = s;

                        if (!userToken.S.ReceiveAsync(saea_Receive))
                            ProcessReceive(saea_Receive);
                    }
                    else
                    {
                        s.Close(); 
                        Console.WriteLine(sClientIP + " Can't connect server,because connection pool has been finished !");
                    }
                }
                catch { }
            }
            StartAccept(e);
        }

        /// <summary>
        /// 异步接收操作完成后调用该方法
        /// </summary>
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            try
            {
                if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
                {
                    SAEAUserToken userToken = (SAEAUserToken)e.UserToken;
                    userToken.HeartbeatTime = DateTime.Now;
                    string sClientIP = ((IPEndPoint)userToken.S.RemoteEndPoint).Address.ToString();
                    try
                    {
                        byte[] abFactReceive = new byte[e.BytesTransferred];
                        Array.Copy(e.Buffer, e.Offset, abFactReceive, 0, e.BytesTransferred);
                        Console.WriteLine("From the " + sClientIP + " to receive " + e.BytesTransferred + " bytes of data:" + BitConverter.ToString(abFactReceive));
                    }
                    catch { }
                    finally
                    {
                        if (!userToken.S.ReceiveAsync(e))
                            ProcessReceive(e);
                    }
                }
                else
                    CloseClientSocket(e);
            }
            catch { }
        }

        /// <summary>
        /// 异步发送操作完成后调用该方法
        /// </summary>
        private void ProcessSend(SocketAsyncEventArgs e)
        {
            
        }

        /// <summary>
        /// Socket 断开处理
        /// </summary>
        private void CloseClientSocket(SocketAsyncEventArgs saea)
        {
            try
            {
                SAEAUserToken userToken = (SAEAUserToken)saea.UserToken;
                if (!saeaPool_Receive.Push(saea))
                    return;                
                if (userToken.S != null)
                {                    
                    if (userToken.S.Connected)
                    {                        
                        try
                        {
                            userToken.S.Shutdown(SocketShutdown.Both);
                        }
                        catch { }
                        string sClientIP = ((IPEndPoint)userToken.S.RemoteEndPoint).Address.ToString();
                        Console.WriteLine(sClientIP + " disconnect !");
                    }
                    userToken.S.Close();                    
                }                
                Console.WriteLine("Online Client total:" + saeaPool_Receive.GetUsedSAEACount());
            }
            catch { }
        }

        /// <summary>
        /// 客户端心跳检测
        /// </summary>
        private void CheckClientHeartbeat()
        {
            while (true)
            {
                try
                {
                    int iCheckInterval = 10 * 1000; //10秒检测间隔
                    Thread.Sleep(iCheckInterval);
                    List<SocketAsyncEventArgs> lUserdSAEA = saeaPool_Receive.GetUsedSAEA();
                    if (lUserdSAEA != null && lUserdSAEA.Count > 0)
                    {
                        foreach (SocketAsyncEventArgs saea in lUserdSAEA)
                        {
                            SAEAUserToken userToken = (SAEAUserToken)saea.UserToken;
                            if (userToken.HeartbeatTime.AddMilliseconds(iCheckInterval).CompareTo(DateTime.Now) < 0)
                            {
                                if (userToken.S != null)
                                {
                                    try
                                    {
                                        string sClientIP = ((IPEndPoint)userToken.S.RemoteEndPoint).Address.ToString();
                                        Console.WriteLine(sClientIP + " the heartbeat timeout !");
                                    }
                                    catch { }
                                    userToken.S.Close(); //服务端主动关闭心跳超时连接,在此关闭连接,会触发OnIOCompleted回调                                    
                                }
                            }
                        }
                    }
                }
                catch { }
            }
        }
    }
}


SocketAsyncEventArgs 对象池

using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;

namespace CsharpIOCP
{
    /// <summary>
    /// 与每个客户Socket相关联,进行Send和Receive投递时所需要的参数
    /// </summary>
    internal sealed class SAEAPool
    {
        List<SocketAsyncEventArgs> pool; //为每一个Socket客户端分配一个SocketAsyncEventArgs,用一个List管理,在程序启动时建立
        Int32 capacity; //pool对象池的容量
        Int32 boundary; //已分配和未分配对象的边界

        internal SAEAPool(Int32 capacity)
        {
            pool = new List<SocketAsyncEventArgs>();
            boundary = 0;
            this.capacity = capacity;
        }

        /// <summary>
        /// 往pool对象池中增加新建立的对象,因为这个程序在启动时会建立好所有对象,
        /// 故这个方法只在初始化时会被调用,因此,没有加锁。
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        internal bool Add(SocketAsyncEventArgs arg)
        {
            if (arg != null && pool.Count < capacity)
            {
                pool.Add(arg);
                boundary++;
                return true;
            }
            else
                return false;
        }

        /// <summary>
        /// 获取正在使用的 SocketAsyncEventArgs 对象集合
        /// </summary>
        /// <returns></returns>
        internal List<SocketAsyncEventArgs> GetUsedSAEA()
        {
            lock (pool)
            {
                List<SocketAsyncEventArgs> lUsedSAEA = new List<SocketAsyncEventArgs>();
                for (int i = boundary; i < capacity; i++)
                    lUsedSAEA.Add(pool[i]);
                return lUsedSAEA;
            }
        }

        /// <summary>
        /// 获取已使用的 SocketAsyncEventArgs 对象总数
        /// </summary>
        /// <returns></returns>
        internal string GetUsedSAEACount()
        {
            return (capacity - boundary).ToString();
        }

        /// <summary>
        /// 从对象池中取出一个对象,交给一个socket来进行投递请求操作
        /// </summary>
        /// <returns></returns>
        internal SocketAsyncEventArgs Pull()
        {
            lock (pool)
            {
                if (boundary > 0)
                {
                    --boundary;
                    return pool[boundary];
                }
                else
                    return null;
            }
        }

        /// <summary>
        /// 一个socket客户断开,与其相关的SocketAsyncEventArgs被释放,重新投入Pool中,以备用。
        /// </summary>
        /// <param name="arg"></param>
        /// <returns></returns>
        internal bool Push(SocketAsyncEventArgs arg)
        {
            if (arg != null)
            {
                lock (pool)
                {
                    int index = pool.IndexOf(arg, boundary); //找出被断开的客户
                    if (index >= 0)
                    {
                        if (index == boundary) //正好是边界元素
                            boundary++;
                        else
                        {
                            pool[index] = pool[boundary]; //将断开客户移到边界上,边界右移
                            pool[boundary++] = arg;
                        }
                    }
                    else
                        return false;
                }
                return true;
            }
            else
                return false;
        }
    }
}


SocketAsyncEventArgs 缓冲区管理类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;

namespace CsharpIOCP
{
    /// <summary>
    /// SocketAsyncEventArgs 缓冲区管理类,
    /// 创建一个大型缓冲区,该缓冲区可以进行分割并指定给 SocketAsyncEventArgs 对象以便用在每个套接字 I/O 操作中。
    /// 这样可以很方便地重用缓冲区,并防止堆内存碎片化。
    /// </summary>
    internal class BufferManager
    {
        int m_numBytes; //缓冲池的总容量
        byte[] m_buffer; //缓冲池
        Stack<int> m_freeIndexPool; //后进先出数据结构
        int m_currentIndex; //当前缓冲池索引
        int m_bufferSize; //单个缓冲区容量

        /// <summary>
        /// 缓冲池重载
        /// </summary>
        /// <param name="totalBytes">缓冲池的总容量</param>
        /// <param name="bufferSize">单个缓冲区容量</param>
        public BufferManager(int totalBytes, int bufferSize)
        {
            m_numBytes = totalBytes;
            m_currentIndex = 0;
            m_bufferSize = bufferSize;
            m_freeIndexPool = new Stack<int>();
            InitBuffer();
        }

        /// <summary>
        /// 初始化缓冲池
        /// </summary>
        private void InitBuffer()
        {            
            m_buffer = new byte[m_numBytes]; //创建大型缓冲池
        }

        /// <summary>
        /// 从缓冲池中分配一个缓冲区给指定SocketAsyncEventArgs对象
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        public bool SetBuffer(SocketAsyncEventArgs args)
        {
            lock(m_freeIndexPool)
            {
                if (m_freeIndexPool.Count > 0)
                {
                    args.SetBuffer(m_buffer, m_freeIndexPool.Pop(), m_bufferSize);
                }
                else
                {
                    if ((m_numBytes - m_bufferSize) < m_currentIndex)
                        return false;
                    args.SetBuffer(m_buffer, m_currentIndex, m_bufferSize);
                    m_currentIndex += m_bufferSize;
                }
                return true;
            }
        }

        /// <summary>
        /// 将缓冲区释放到缓冲池中
        /// </summary>
        /// <param name="args"></param>
        public void FreeBuffer(SocketAsyncEventArgs args)
        {
            lock (m_freeIndexPool)
            {
                m_freeIndexPool.Push(args.Offset);
                args.SetBuffer(null, 0, 0);
            }
        }

    }
}

SocketAsyncEventArgs  用户标记类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;

namespace CsharpIOCP
{
    /// <summary>
    /// SAEA用户标记类
    /// </summary>
   internal class SAEAUserToken
    {
        /// <summary>
        /// 用于发送数据的SocketAsyncEventArgs
        /// </summary>
        public SocketAsyncEventArgs SAEA_Send;

        /// <summary>
        /// 连接套接字
        /// </summary>
        public Socket S;

        /// <summary>
        /// 最新一次心跳时间
        /// </summary>
        public DateTime HeartbeatTime;
    }
}


  • 1
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1、在C#中,不用去面对完成端口的操作系统内核对象,Microsoft已经为我们提供了SocketAsyncEventArgs类,它封装了IOCP的使用。请参考:http://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx?cs-save-lang=1&cs-lang=cpp#code-snippet-1。 2、我的SocketAsyncEventArgsPool类使用List对象来存储对客户端来通信的SocketAsyncEventArgs对象,它相当于直接使用内核对象时的IoContext。我这样设计比用堆栈来实现的好处理是,我可以在SocketAsyncEventArgsPool池中找到任何一个与服务器连接的客户,主动向它发信息。而用堆栈来实现的话,要主动给客户发信息,则还要设计一个结构来存储已连接上服务器的客户。 3、对每一个客户端不管还发送还是接收,我使用同一个SocketAsyncEventArgs对象,对每一个客户端来说,通信是同步进行的,也就是说服务器高度保证同一个客户连接上要么在投递发送请求,并等待;或者是在投递接收请求,等待中。本例只做echo服务器,还未考虑由服务器主动向客户发送信息。 4、SocketAsyncEventArgs的UserToken被直接设定为被接受的客户端Socket。 5、没有使用BufferManager 类,因为我在初始化时给每一个SocketAsyncEventArgsPool中的对象分配一个缓冲区,发送时使用Arrary.Copy来进行字符拷贝,不去改变缓冲区的位置,只改变使用的长度,因此在下次投递接收请求时恢复缓冲区长度就可以了!如果要主动给客户发信息的话,可以new一个SocketAsyncEventArgs对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值