C#高性能Socket服务器的实现(IOCP)

无论什么平台,编写支持高并发性的网络服务器,瓶颈往往出在I/O上,目前最高效的是采用Asynchronous I/O模型,Linux平台提供了epoll,Windows平台提供了I/O Completion Port(IO完成端口,即IOCP)。

在实际应用中,证明C#编写基于.Net IOCP的高性能服务器可以支持10000个以上的TCP长连接。但在具体实现过程中需要注意几个问题:
1.SocketAsyncEventArgs和Buffer最好预先分配,并能回收重复利用。
2.一个Socket的Send和Receive最好分别对应一个SocketAsyncEventArgs,因为当一个SocketAsyncEventArgs被ReceiveAsync调用挂起后,在调用SendAsync时就会出异常。同样不要对一个SocketAsyncEventArgs在一个异步操作被挂起时再次调用。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
namespace IOCPTest
{
    /// <summary>

    /// IOCP SOCKET服务器

    /// </summary>

    public class IOCPServer : IDisposable

    {

        const int opsToPreAlloc = 2;

        #region Fields

        /// <summary>

        /// 服务器程序允许的最大客户端连接数

        /// </summary>

        private int _maxClient;

        /// <summary>

        /// 监听Socket,用于接受客户端的连接请求

        /// </summary>

        private Socket _serverSock;

        /// <summary>

        /// 当前的连接的客户端数

        /// </summary>

        private int _clientCount;



        /// <summary>

        /// 用于每个I/O Socket操作的缓冲区大小

        /// </summary>

        private int _bufferSize = 1024;

        /// <summary>

        /// 信号量

        /// </summary>

        Semaphore _maxAcceptedClients;


        /// <summary>

        /// 缓冲区管理

        /// </summary>

        BufferManager _bufferManager;


        /// <summary>

        /// 对象池

        /// </summary>

        SocketAsyncEventArgsPool _objectPool;

        private bool disposed = false;

        #endregion

        #region Properties


        /// <summary>

        /// 服务器是否正在运行

        /// </summary>

        public bool IsRunning { get; private set; }

        /// <summary>

        /// 监听的IP地址

        /// </summary>

        public IPAddress Address { get; private set; }

        /// <summary>

        /// 监听的端口

        /// </summary>

        public int Port { get; private set; }

        /// <summary>

        /// 通信使用的编码

        /// </summary>

        public Encoding Encoding { get; set; }



        #endregion

        #region Ctors



        /// <summary>

        /// 异步IOCP SOCKET服务器

        /// </summary>

        /// <param name="listenPort">监听的端口</param>

        /// <param name="maxClient">最大的客户端数量</param>

        public IOCPServer(int listenPort, int maxClient)

            : this(IPAddress.Any, listenPort, maxClient)

        {

        }



        /// <summary>

        /// 异步Socket TCP服务器

        /// </summary>

        /// <param name="localEP">监听的终结点</param>

        /// <param name="maxClient">最大客户端数量</param>

        public IOCPServer(IPEndPoint localEP, int maxClient)

            : this(localEP.Address, localEP.Port, maxClient)

        {

        }



        /// <summary>

        /// 异步Socket TCP服务器

        /// </summary>

        /// <param name="localIPAddress">监听的IP地址</param>

        /// <param name="listenPort">监听的端口</param>

        /// <param name="maxClient">最大客户端数量</param>

        public IOCPServer(IPAddress localIPAddress, int listenPort, int maxClient)

        {

            this.Address = localIPAddress;

            this.Port = listenPort;

            this.Encoding = Encoding.Default;



            _maxClient = maxClient;



            _serverSock = new Socket(localIPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);



            _bufferManager = new BufferManager(_bufferSize * _maxClient * opsToPreAlloc, _bufferSize);



            _objectPool = new SocketAsyncEventArgsPool(_maxClient);



            _maxAcceptedClients = new Semaphore(_maxClient, _maxClient);

        }



        #endregion

        #region 初始化



        /// <summary>

        /// 初始化函数

        /// </summary>

        public void Init()

        {

            // Allocates one large byte buffer which all I/O operations use a piece of.  This gaurds 

            // against memory fragmentation

            _bufferManager.InitBuffer();



            // preallocate pool of SocketAsyncEventArgs objects

            SocketAsyncEventArgs readWriteEventArg;



            for (int i = 0; i < _maxClient; i++)

            {

                //Pre-allocate a set of reusable SocketAsyncEventArgs

                readWriteEventArg = new SocketAsyncEventArgs();

                readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(OnIOCompleted);

                readWriteEventArg.UserToken = null;



                // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object

                _bufferManager.SetBuffer(readWriteEventArg);



                // add SocketAsyncEventArg to the pool

                _objectPool.Push(readWriteEventArg);

            }



        }



        #endregion

        #region Start

        /// <summary>

        /// 启动

        /// </summary>

        public void Start()

        {

            if (!IsRunning)

            {

                Init();

                IsRunning = true;

                IPEndPoint localEndPoint = new IPEndPoint(Address, Port);

                // 创建监听socket

                _serverSock = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

                //_serverSock.ReceiveBufferSize = _bufferSize;

                //_serverSock.SendBufferSize = _bufferSize;

                if (localEndPoint.AddressFamily == AddressFamily.InterNetworkV6)

                {

                    // 配置监听socket为 dual-mode (IPv4 & IPv6) 

                    // 27 is equivalent to IPV6_V6ONLY socket option in the winsock snippet below,

                    _serverSock.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27, false);

                    _serverSock.Bind(new IPEndPoint(IPAddress.IPv6Any, localEndPoint.Port));

                }

                else

                {

                    _serverSock.Bind(localEndPoint);

                }

                // 开始监听

                _serverSock.Listen(this._maxClient);

                // 在监听Socket上投递一个接受请求。

                StartAccept(null);

            }

        }

        #endregion

        #region Stop



        /// <summary>

        /// 停止服务

        /// </summary>

        public void Stop()

        {

            if (IsRunning)

            {

                IsRunning = false;

                _serverSock.Close();

                //TODO 关闭对所有客户端的连接



            }

        }



        #endregion

        #region Accept



        /// <summary>

        /// 从客户端开始接受一个连接操作

        /// </summary>

        private void StartAccept(SocketAsyncEventArgs asyniar)

        {

            if (asyniar == null)

            {

                asyniar = new SocketAsyncEventArgs();

                asyniar.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);

            }

            else

            {

                //socket must be cleared since the context object is being reused

                asyniar.AcceptSocket = null;

            }

            _maxAcceptedClients.WaitOne();

            if (!_serverSock.AcceptAsync(asyniar))

            {

                ProcessAccept(asyniar);

                //如果I/O挂起等待异步则触发AcceptAsyn_Asyn_Completed事件

                //此时I/O操作同步完成,不会触发Asyn_Completed事件,所以指定BeginAccept()方法

            }

        }



        /// <summary>

        /// accept 操作完成时回调函数

        /// </summary>

        /// <param name="sender">Object who raised the event.</param>

        /// <param name="e">SocketAsyncEventArg associated with the completed accept operation.</param>

        private void OnAcceptCompleted(object sender, SocketAsyncEventArgs e)

        {

            ProcessAccept(e);

        }



        /// <summary>

        /// 监听Socket接受处理

        /// </summary>

        /// <param name="e">SocketAsyncEventArg associated with the completed accept operation.</param>

        private void ProcessAccept(SocketAsyncEventArgs e)

        {

            if (e.SocketError == SocketError.Success)

            {

                Socket s = e.AcceptSocket;//和客户端关联的socket

                if (s.Connected)

                {

                    try

                    {



                        Interlocked.Increment(ref _clientCount);//原子操作加1

                        SocketAsyncEventArgs asyniar = _objectPool.Pop();

                        asyniar.UserToken = s;



                        Log4Debug(String.Format("客户 {0} 连入, 共有 {1} 个连接。", s.RemoteEndPoint.ToString(), _clientCount));



                        if (!s.ReceiveAsync(asyniar))//投递接收请求

                        {

                            ProcessReceive(asyniar);

                        }

                    }

                    catch (SocketException ex)

                    {

                        Log4Debug(String.Format("接收客户 {0} 数据出错, 异常信息: {1} 。", s.RemoteEndPoint, ex.ToString()));

                        //TODO 异常处理

                    }

                    //投递下一个接受请求

                    StartAccept(e);

                }

            }

        }



        #endregion

        #region 发送数据



        /// <summary>

        /// 异步的发送数据

        /// </summary>

        /// <param name="e"></param>

        /// <param name="data"></param>

        public void Send(SocketAsyncEventArgs e, byte[] data)

        {

            if (e.SocketError == SocketError.Success)

            {

                Socket s = e.AcceptSocket;//和客户端关联的socket

                if (s.Connected)

                {

                    Array.Copy(data, 0, e.Buffer, 0, data.Length);//设置发送数据



                    //e.SetBuffer(data, 0, data.Length); //设置发送数据

                    if (!s.SendAsync(e))//投递发送请求,这个函数有可能同步发送出去,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件

                    {

                        // 同步发送时处理发送完成事件

                        ProcessSend(e);

                    }

                    else

                    {

                        CloseClientSocket(e);

                    }

                }

            }

        }



        /// <summary>

        /// 同步的使用socket发送数据

        /// </summary>

        /// <param name="socket"></param>

        /// <param name="buffer"></param>

        /// <param name="offset"></param>

        /// <param name="size"></param>

        /// <param name="timeout"></param>

        public void Send(Socket socket, byte[] buffer, int offset, int size, int timeout)

        {

            socket.SendTimeout = 0;

            int startTickCount = Environment.TickCount;

            int sent = 0; // how many bytes is already sent

            do

            {

                if (Environment.TickCount > startTickCount + timeout)

                {

                    //throw new Exception("Timeout.");

                }

                try

                {

                    sent += socket.Send(buffer, offset + sent, size - sent, SocketFlags.None);

                }

                catch (SocketException ex)

                {

                    if (ex.SocketErrorCode == SocketError.WouldBlock ||

                    ex.SocketErrorCode == SocketError.IOPending ||

                    ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)

                    {

                        // socket buffer is probably full, wait and try again

                        Thread.Sleep(30);

                    }

                    else

                    {

                        throw ex; // any serious error occurr

                    }

                }

            } while (sent < size);

        }





        /// <summary>

        /// 发送完成时处理函数

        /// </summary>

        /// <param name="e">与发送完成操作相关联的SocketAsyncEventArg对象</param>

        private void ProcessSend(SocketAsyncEventArgs e)

        {

            if (e.SocketError == SocketError.Success)

            {

                Socket s = (Socket)e.UserToken;



                //TODO

            }

            else

            {

                CloseClientSocket(e);

            }

        }



        #endregion

        #region 接收数据





        /// <summary>

        ///接收完成时处理函数

        /// </summary>

        /// <param name="e">与接收完成操作相关联的SocketAsyncEventArg对象</param>

        private void ProcessReceive(SocketAsyncEventArgs e)

        {

            if (e.SocketError == SocketError.Success)//if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)

            {

                // 检查远程主机是否关闭连接

                if (e.BytesTransferred > 0)

                {

                    Socket s = (Socket)e.UserToken;

                    //判断所有需接收的数据是否已经完成

                    if (s.Available == 0)

                    {

                        //从侦听者获取接收到的消息。 

                        //String received = Encoding.ASCII.GetString(e.Buffer, e.Offset, e.BytesTransferred);

                        //echo the data received back to the client

                        //e.SetBuffer(e.Offset, e.BytesTransferred);



                        byte[] data = new byte[e.BytesTransferred];

                        Array.Copy(e.Buffer, e.Offset, data, 0, data.Length);//从e.Buffer块中复制数据出来,保证它可重用



                        string info = Encoding.Default.GetString(data);

                        Log4Debug(String.Format("收到 {0} 数据为 {1}", s.RemoteEndPoint.ToString(), info));

                        //TODO 处理数据



                        //增加服务器接收的总字节数。

                    }



                    if (!s.ReceiveAsync(e))//为接收下一段数据,投递接收请求,这个函数有可能同步完成,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件

                    {

                        //同步接收时处理接收完成事件

                        ProcessReceive(e);

                    }

                }

            }

            else

            {

                CloseClientSocket(e);

            }

        }



        #endregion

        #region 回调函数



        /// <summary>

        /// 当Socket上的发送或接收请求被完成时,调用此函数

        /// </summary>

        /// <param name="sender">激发事件的对象</param>

        /// <param name="e">与发送或接收完成操作相关联的SocketAsyncEventArg对象</param>

        private void OnIOCompleted(object sender, SocketAsyncEventArgs e)

        {

            // Determine which type of operation just completed and call the associated handler.

            switch (e.LastOperation)

            {

                case SocketAsyncOperation.Accept:

                    ProcessAccept(e);

                    break;

                case SocketAsyncOperation.Receive:

                    ProcessReceive(e);

                    break;

                default:

                    throw new ArgumentException("The last operation completed on the socket was not a receive or send");

            }

        }



        #endregion

        #region Close

        /// <summary>

        /// 关闭socket连接

        /// </summary>

        /// <param name="e">SocketAsyncEventArg associated with the completed send/receive operation.</param>

        private void CloseClientSocket(SocketAsyncEventArgs e)

        {

            Log4Debug(String.Format("客户 {0} 断开连接!", ((Socket)e.UserToken).RemoteEndPoint.ToString()));

            Socket s = e.UserToken as Socket;

            CloseClientSocket(s, e);

        }



        /// <summary>

        /// 关闭socket连接

        /// </summary>

        /// <param name="s"></param>

        /// <param name="e"></param>

        private void CloseClientSocket(Socket s, SocketAsyncEventArgs e)

        {

            try

            {

                s.Shutdown(SocketShutdown.Send);

            }

            catch (Exception)

            {

                // Throw if client has closed, so it is not necessary to catch.

            }

            finally

            {

                s.Close();

            }

            Interlocked.Decrement(ref _clientCount);

            _maxAcceptedClients.Release();

            _objectPool.Push(e);//SocketAsyncEventArg 对象被释放,压入可重用队列。

        }

        #endregion

        #region Dispose

        /// <summary>

        /// Performs application-defined tasks associated with freeing, 

        /// releasing, or resetting unmanaged resources.

        /// </summary>

        public void Dispose()

        {

            Dispose(true);

            GC.SuppressFinalize(this);

        }



        /// <summary>

        /// Releases unmanaged and - optionally - managed resources

        /// </summary>

        /// <param name="disposing"><c>true</c> to release 

        /// both managed and unmanaged resources; <c>false</c> 

        /// to release only unmanaged resources.</param>

        protected virtual void Dispose(bool disposing)

        {

            if (!this.disposed)

            {

                if (disposing)

                {

                    try

                    {

                        Stop();

                        if (_serverSock != null)

                        {

                            _serverSock = null;

                        }

                    }

                    catch (SocketException ex)

                    {

                        //TODO 事件

                    }

                }

                disposed = true;

            }

        }

        #endregion

        public void Log4Debug(string msg)

        {

            Console.WriteLine("notice:" + msg);

        }



    }

}

 

最近有项目要做一个高性能网络服务器,去网络上搜到到的都是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、付费专栏及课程。

余额充值