.net下完成端口(IOCP)的实现

代码是MSDN上的,其中缺少BufferManager类,现在将其加上并添加了一些自己的注释,其中有不对的地方还望大家多多指正

class SocketAsyncEventArgsPool
    {
        /// <summary>
        /// SocketAsyncEventArgs栈
        /// </summary>
        Stack<SocketAsyncEventArgs> m_pool;

        /// <summary>
        /// 初始化SocketAsyncArg对象池
        /// </summary>
        /// <param name="capacity">所能保持的最大连接数</param>
        public SocketAsyncEventArgsPool(int capacity)
        {
            m_pool = new Stack<SocketAsyncEventArgs>(capacity);
        }


        /// <summary>
        /// 将SocketAsyncEventArgs对象入栈
        /// </summary>
        /// <param name="item">入栈的SocketAsyncEventArgs对象</param>
        public void Push(SocketAsyncEventArgs item)
        {
            if (item == null) { throw new ArgumentNullException("Items added to a SocketAsyncEventArgsPool cannot be null"); }
            lock (m_pool)
            {
                m_pool.Push(item);
            }
        }

        /// <summary>
        /// 将SocketAsyncEventArgs对象出栈
        /// </summary>
        /// <returns>SocketAsyncEventArgs</returns>
        public SocketAsyncEventArgs Pop()
        {
            lock (m_pool)
            {
                return m_pool.Pop();
            }
        }

        /// <summary>
        /// 已经存在的SocketAsyncEventArgs对象数量
        /// </summary>
        public int Count
        {
            get { return m_pool.Count; }
        }

    }

缓冲区类

//该类用来创建一个可以被分给所有SocketAsyncEventArgs对象进行socket i/o操作的大缓冲区
    //这样使得缓冲区可以被重复利用,防止内存碎片
    //该类非线程安全
    class BufferManager
    {
        /// <summary>
        /// 全部数量的缓冲池大小
        /// </summary>
        int m_numBytes;
        /// <summary>
        /// 缓冲区管理维护基础字节数组
        /// </summary>
        byte[] m_buffer;

        Stack<int> m_freeIndexPool;
        /// <summary>
        /// 当前传输的序号
        /// </summary>
        int m_currentIndex;
        /// <summary>
        /// 缓冲大小
        /// </summary>
        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>();
        }

        /// <summary>
        /// 初始化总缓冲区大小
        /// </summary>
        public void InitBuffer()
        {
            //创建一个大的缓冲池,每个SocketAsyncEventArgs对象划分区域
            m_buffer = new byte[m_numBytes];
        }

        /// <summary>
        /// 从缓冲池分配一个缓冲区到指定的SocketAsyncEventArgs对象
        /// </summary>
        /// <param name="args"></param>
        /// <returns></returns>
        public bool SetBuffer(SocketAsyncEventArgs args)
        {

            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);//为某个SocketAsyncEventArgs分配相应缓冲区,
                m_currentIndex += m_bufferSize;
            }
            return true;
        }

        /// <summary>
        /// 将SocketAsyncEventArgs对象出栈,并释放其所占用的内存
        /// </summary>
        /// <param name="args">需要释放的SocketAsyncEventArgs对象</param>
        public void FreeBuffer(SocketAsyncEventArgs args)
        {
            m_freeIndexPool.Push(args.Offset);
            args.SetBuffer(null, 0, 0);
        }

    }

自定义AsyncUserToken类

 public class AsyncUserToken
    {
        public Socket Socket;
        private StringBuilder _sb;

        public AsyncUserToken()
        {
            _sb = new StringBuilder();
        }
        public StringBuilder sb
        {
            get
            {
                return _sb;
            }
            set
            {
                _sb = value;
            }

        }
    }

具体的实现

//实现socket服务器的连接逻辑。
    //接受一个连接后,从客户端读取所有数据,然后回发给客户端
    //继续,直到客户端断开连接。
    class Server
    {
        #region 变量定义

        /// <summary>
        /// 同时连接的句柄最大数
        /// </summary>
        private int m_numConnections;   

        /// <summary>
        /// 每个I/O套接字的缓冲区最大字节数
        /// </summary>
        private int m_receiveBufferSize;

        /// <summary>
        /// 所有socket共用的缓冲区
        /// </summary>
        BufferManager m_bufferManager;

        const int opsToPreAlloc = 2;

        /// <summary>
        /// 用来侦听的socket
        /// </summary>
        Socket listenSocket;  

        /// <summary>
        /// 写可重用的SocketAsyncEventArgs对象池,读取并接受socket操作
        /// </summary>
        SocketAsyncEventArgsPool m_readWritePool;
        /// <summary>
        /// 由服务器接收的字节计数器
        /// </summary>
        int m_totalBytesRead;
        /// <summary>
        /// 连接到服务器的客户端总数
        /// </summary>
        int m_numConnectedSockets;
        /// <summary>
        /// 信号量,用于限制最大并发量
        /// </summary>
        Semaphore m_maxNumberAcceptedClients;
        #endregion

        /// <summary>
        /// 创建一个server实例
        /// </summary>
        /// <param name="numConnections">最大并发数量</param>
        /// <param name="receiveBufferSize">某个连接的缓冲区大小</param>
        public Server(int numConnections, int receiveBufferSize)
        {
            m_totalBytesRead = 0;
            m_numConnectedSockets = 0;
            m_numConnections = numConnections;//设置同时并发的最大数
            m_receiveBufferSize = receiveBufferSize;//设置每个套接字的缓冲区最大值
            m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToPreAlloc,
                receiveBufferSize);//将最大缓冲区大小设置为最大连接数*某个缓冲区大小*2(用于接受和读取的缓冲区)

            m_readWritePool = new SocketAsyncEventArgsPool(numConnections);//设置SocketAsyncEventArgs池大小
            m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);//设置可同时并发的socket连接数
        }


        /// <summary>
        /// 初始化SocketAsyncEventArgs对象,这些对象可重复利用
        /// </summary>
        public void Init()
        {
            //初始化缓冲区,所有的io操作都在这里
            m_bufferManager.InitBuffer();

            //声明SocketAsyncEventArgs对象池
            SocketAsyncEventArgs readWriteEventArg;

            for (int i = 0; i < m_numConnections; i++)
            {
                readWriteEventArg = new SocketAsyncEventArgs();
                readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);//绑定IO完成事件
                readWriteEventArg.UserToken = new AsyncUserToken();

                m_bufferManager.SetBuffer(readWriteEventArg);//为每个SocketAsyncEventArg分配一个缓冲区空间

                m_readWritePool.Push(readWriteEventArg);//将SocketAsyncEventArg入栈
            }

        }

        /// <summary>
        /// 启动侦听
        /// </summary>
        /// <param name="localEndPoint">终结点</param>
        public void Start(IPEndPoint localEndPoint)
        {
            //创建一个socket用于侦听
            listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            listenSocket.Bind(localEndPoint);
            //设置侦听数
            listenSocket.Listen(100);

            StartAccept(null);//开始接受socket
            Console.WriteLine("Press any key to terminate the server process....");
            Console.ReadKey();
        }


        //开始接受链接
        public void StartAccept(SocketAsyncEventArgs acceptEventArg)
        {
            if (acceptEventArg == null)//判断是否为第一次运行,acceptEventArg该事件只是用来进行侦听连接
            {
                acceptEventArg = new SocketAsyncEventArgs();
                acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);//绑定AcceptEventArg_Completed方法
            }
            else
            {
                // 因为要重复利用,所以对象会被清空
                acceptEventArg.AcceptSocket = null;//清空SocketAsyncEventArgs所对应的socket连接
            }

            m_maxNumberAcceptedClients.WaitOne();//对线程进行阻塞,若超过允许连接的最大数则阻塞,直到接受到Release信号
            bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
            if (!willRaiseEvent)//有可能同步完成而不触发SocketAsyncEventArgs的completed事件,所以需要手动触发一次
            {
                ProcessAccept(acceptEventArg);
            }
        }

        /// <summary>
        /// 该回调方法是用来在接受链接之后执行的
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
        {
            ProcessAccept(e);
        }

        /// <summary>
        /// 处理接受进来的socket连接
        /// </summary>
        /// <param name="e"></param>
        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            Interlocked.Increment(ref m_numConnectedSockets);//当前连接数+1(原子操作)
            Console.WriteLine("Client connection accepted. There are {0} clients connected to the server",
                m_numConnectedSockets);
            SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();//从SocketAsyncEventArgs池中出栈一个SocketAsyncEventArgs类用以进行I/O操作
            ((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket;//接受链接

            bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);//开始接受数据,并会触发readEventArgs的Completed事件
            if (!willRaiseEvent)//原因同上
            {
                ProcessReceive(readEventArgs);
            }

            StartAccept(e);//开始接受新的连接
        }

        /// <summary>
        /// I/O完成事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            // 确定完成的操作类型(发送/接受)
            switch (e.LastOperation)
            {
                case SocketAsyncOperation.Receive:
                    ProcessReceive(e);

                    break;
                case SocketAsyncOperation.Send:
                    ProcessSend(e);
                    break;
                default:
                    throw new ArgumentException("The last operation completed on the socket was not a receive or send");
            }

        }

        // 当一个异步操作完成时,会调用该方法
        // 若远程主机关闭了连接,则socket也同样会被关闭
        // 若收到数据,则会返回给客户端
        /// <summary>
        /// 处理接受过程.
        /// </summary>
        /// <param name="e">I/OSocketAsyncEventArgs而非Accpet</param>
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            AsyncUserToken token = (AsyncUserToken)e.UserToken;
            //判断socket中是否有数据在传输&&判断是否有异常
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);//接受总字节数+=本次传输字节数
                Console.WriteLine("The server has read a total of {0} bytes", m_totalBytesRead);
                Console.WriteLine("Receive buff:" + Encoding.Default.GetString(e.Buffer, e.Offset, e.BytesTransferred));

                token.sb.Append(Encoding.Default.GetString(e.Buffer, e.Offset, e.BytesTransferred));

                byte[] b = Encoding.Default.GetBytes("this is send message");
                e.SetBuffer(e.Offset, e.BytesTransferred);//将接受到的数据原样会送回client
                bool willRaiseEvent = token.Socket.SendAsync(e);//执行发送(会调用completed事件)
                if (!willRaiseEvent)//原因同上
                {
                    ProcessSend(e);
                }

            }
            else
            {
                CloseClientSocket(e);//关闭连接
            }
        }

        // 该方法会在一个异步发送完成后调用
        /// <summary>
        /// 发送后处理方法
        /// </summary>
        /// <param name="e"></param>
        private void ProcessSend(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                AsyncUserToken token = (AsyncUserToken)e.UserToken;
                // 继续读取数据
                bool willRaiseEvent = token.Socket.ReceiveAsync(e);//
                if (!willRaiseEvent)
                {
                    ProcessReceive(e);
                }
            }
            else
            {
                CloseClientSocket(e);
            }
        }

        /// <summary>
        /// 连接关闭
        /// </summary>
        /// <param name="e">需要关闭的SocketAsyncEventArgs对象</param>
        private void CloseClientSocket(SocketAsyncEventArgs e)
        {
            AsyncUserToken token = e.UserToken as AsyncUserToken;

            //关闭与客户端连接的套接字
            try
            {
                token.Socket.Shutdown(SocketShutdown.Send);
            }
            catch (Exception) { }
            token.Socket.Close();

            //对连接数-1(原子操作)
            Interlocked.Decrement(ref m_numConnectedSockets);
            //允许接入新的连接
            m_maxNumberAcceptedClients.Release();
            Console.WriteLine("A client has been disconnected from the server. There are {0} clients connected to the server", m_numConnectedSockets);

            Console.WriteLine("here is total receive buffer:{0}", ((AsyncUserToken)e.UserToken).sb.ToString());

            //将SocketAsyncEventArgs入栈,表明该资源可被继续使用
            m_readWritePool.Push(e);
        }

    }

调用方式如下

        public static void Main(String[] args)
        {

            IPAddress[] addressList = Dns.GetHostEntry(Environment.MachineName).AddressList;

            Server s = new Server(1, 1);
            s.Init();
            s.Start(new IPEndPoint(addressList[addressList.Length - 1], 9900));


            Console.ReadKey();

        }

 

转载于:https://www.cnblogs.com/zhou23141622/archive/2013/05/26/3100515.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
最近有项目要做一个高性能网络服务器,去网络上搜到到的都是C++版本而且是英文或者简单的DEMO,所以自己动手写了C# 的DEMO。 网络上只写接收到的数据,没有说怎么处理缓冲区数据,本DEMO简单的介绍如何处理接收到的数据。简单易用,希望对大家有用. 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对象,或者在初始化中建立几个来专门用于主动发送信息,因为这种需求一般是进行信息群发,建立一个对象可以用于很多次信息发送,总体来看,这种花销不大,还减去了字符拷贝和消耗。 6、测试结果:(在我的笔记本上时行的,我的本本是T420 I7 8G内存) 100客户 100,000(十万次)不间断的发送接收数据(发送和接收之间没有Sleep,就一个一循环,不断的发送与接收) 耗时3004.6325 秒完成 总共 10,000,000 一千万次访问 平均每分完成 199,691.6 次发送与接收 平均每秒完成 3,328.2 次发送与接收 整个运行过程中,内存消耗在开始两三分种后就保持稳定不再增涨。 看了一下对每个客户端的延迟最多不超过2秒。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值