基于SocketAsyncEventArgs(IOCP)的高性能TCP服务器实现(一)——封装SocketAsyncEventArgs

2 篇文章 1 订阅

 

最近碰到一个需求,就是有数千台设备,这些设备都是通过运营商的网络,基于TCP/IP协议发送一组信息给服务器,并且这些设备只是单向发送,不需要服务器返回信息,设备的信息发送频率在一秒钟一次。服务器端接受到之后,解析信息,然后入库。这是正常的操作。所以对服务器端的信息接受软件提出了较高的要求,这么多设备,这么高的频率,要保证服务器端接受软件的健壮,不能崩溃掉。在网上查找了相关文章之后,发现SocketAsyncEventArgs这个类,可以实现上述我想要的功能。参考了其他大神的文章之后,在下不才在这里滥竽充数一下,各位见谅。

1、定义变量和辅助类

SocketAsyncEventArgs这个是微软提供的一个类,可以在https://docs.microsoft.com/zh-cn/dotnet/api/system.net.sockets.socketasynceventargs?redirectedfrom=MSDN&view=netframework-4.8官网查看。但是微软的例子比较粗糙,没有达到我想要的目的,所以我这里进行了一些改造。首先创建一个类,我这里叫SocketServer。

在类SocketServer里面,首先定义一些变量,使得我的这个服务端的接收软件,可以支持较多的客户端,也就是设备。

        private int m_maxConnectNum;    //最大连接数  
        private int m_revBufferSize;    //最大接收字节数  
        BufferManager m_bufferManager; //处理信息的工具
        const int opsToAlloc = 2;
        Socket listenSocket;            //监听Socket  
        SocketEventPool m_pool;
        int m_clientCount;              //连接的客户端数量  
        Semaphore m_maxNumberAcceptedClients;
        List<AsyncUserToken> m_clients; //客户端列表  

其中BufferManager这个类是用来管理,客户端发来的信息的,不是特别重要,具体代码网上已经有人实现了,这里我就直接贴出来了:

    // This class creates a single large buffer which can be divided up 
    // and assigned to SocketAsyncEventArgs objects for use with each 
    // socket I/O operation.  
    // This enables bufffers to be easily reused and guards against 
    // fragmenting heap memory.
    // 
    // The operations exposed on the BufferManager class are not thread safe.
    class BufferManager
    {
        int m_numBytes;                 // the total number of bytes controlled by the buffer pool
        byte[] m_buffer;                // the underlying byte array maintained by the Buffer Manager
        Stack<int> m_freeIndexPool;     // 
        int m_currentIndex;
        int m_bufferSize;

        public BufferManager(int totalBytes, int bufferSize)
        {
            m_numBytes = totalBytes;
            m_currentIndex = 0;
            m_bufferSize = bufferSize;
            m_freeIndexPool = new Stack<int>();
        }

        // Allocates buffer space used by the buffer pool
        public void InitBuffer()
        {
            // create one big large buffer and divide that 
            // out to each SocketAsyncEventArg object
            m_buffer = new byte[m_numBytes];
        }

        // Assigns a buffer from the buffer pool to the 
        // specified SocketAsyncEventArgs object
        //
        // <returns>true if the buffer was successfully set, else false</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);
                m_currentIndex += m_bufferSize;
            }
            return true;
        }

        // Removes the buffer from a SocketAsyncEventArg object.  
        // This frees the buffer back to the buffer pool
        public void FreeBuffer(SocketAsyncEventArgs args)
        {
            m_freeIndexPool.Push(args.Offset);
            args.SetBuffer(null, 0, 0);
        }

    }

SocketEventPool这个类用来异步管理客户端的,代码如下: 

    class SocketEventPool
    {
        Stack<SocketAsyncEventArgs> m_pool;


        public SocketEventPool(int capacity)
        {
            m_pool = new Stack<SocketAsyncEventArgs>(capacity);
        }

        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);
            }
        }

        // Removes a SocketAsyncEventArgs instance from the pool  
        // and returns the object removed from the pool  
        public SocketAsyncEventArgs Pop()
        {
            lock (m_pool)
            {
                return m_pool.Pop();
            }
        }

        // The number of SocketAsyncEventArgs instances in the pool  
        public int Count
        {
            get { return m_pool.Count; }
        }

        public void Clear()
        {
            m_pool.Clear();
        }
    }

客户端过来的信息,在服务器接收端都是异步处理,用AsyncUserToken这个类来管理客户端(也就是设备),代码也有人已经实现了,下面贴出代码:

    class AsyncUserToken
    {
        /// <summary>  
        /// 客户端IP地址  
        /// </summary>  
        public IPAddress IPAddress { get; set; }

        /// <summary>  
        /// 远程地址  
        /// </summary>  
        public EndPoint Remote { get; set; }

        /// <summary>  
        /// 通信SOKET  
        /// </summary>  
        public Socket Socket { get; set; }

        /// <summary>  
        /// 连接时间  
        /// </summary>  
        public DateTime ConnectTime { get; set; }

        / <summary>  
        / 所属用户信息  
        / </summary>  
        //public UserInfoModel UserInfo { get; set; }


        /// <summary>  
        /// 数据缓存区  
        /// </summary>  
        public List<byte> Buffer { get; set; }


        public AsyncUserToken()
        {
            this.Buffer = new List<byte>();
        }
    }

上面这两段代码都是辅助类,没那么重要,这里不多说。下面重点讲讲,我们这个服务器接收端怎么来实现的。首先关心的是,服务器端接收到客户端发送来的信息,要进行处理,这里定义了一个委托来帮助我们,也就是说服务器接收到信息,就会进入这个函数进行处理:

        /// <summary>  
        /// 接收到客户端的数据  
        /// </summary>  
        /// <param name="token">客户端</param>  
        /// <param name="buff">客户端数据</param>  
        public delegate void OnReceiveData(AsyncUserToken token, byte[] buff);

        /// <summary>  
        /// 接收到客户端的数据事件  
        /// </summary>  
        public event OnReceiveData ReceiveClientData;

这样调用我实现的服务器端时,就可以直接注册自定义的函数来绑定,例如下面代码:

         _socketServer.ReceiveClientData += onReceiveData;

        private void onReceiveData(AsyncUserToken token, byte[] buff)
        {
            //Dosomething;
        }

二、创建自定义封装SocketServer类

好了,上面我们定义了一些参数,那么在SocketServer这个类初始化的时候,就需要把这些参数初始化,看下面代码:

        /// <summary>  
        /// 构造函数  
        /// </summary>  
        /// <param name="numConnections">最大连接数</param>  
        /// <param name="receiveBufferSize">缓存区大小</param>  
        public SocketServer(int numConnections, int receiveBufferSize)
        {
            m_clientCount = 0;
            m_maxConnectNum = numConnections;
            m_revBufferSize = receiveBufferSize;
            // allocate buffers such that the maximum number of sockets can have one outstanding read and   
            //write posted to the socket simultaneously    
            m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToAlloc, receiveBufferSize);

            m_pool = new SocketEventPool(numConnections);
            m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
        }

三、SocketServer类的初始化

上面代码是类SocketServer的构造函数,在构造函数里我们设定了最大连接数以及每条消息的大小,因为我们要希望我们的服务器端软件能够接受更多客户端的信息。通过构造函数初始化参数之后,我们需要给每一个客户端或者说每一个Socket分配一定的内存,如下代码:

        /// <summary>  
        /// 初始化  
        /// </summary>  
        public void Init()
        {
            // Allocates one large byte buffer which all I/O operations use a piece of.  This gaurds   
            // against memory fragmentation  
            m_bufferManager.InitBuffer();
            m_clients = new List<AsyncUserToken>();
            // preallocate pool of SocketAsyncEventArgs objects  
            SocketAsyncEventArgs readWriteEventArg;

            for (int i = 0; i < m_maxConnectNum; i++)
            {
                readWriteEventArg = new SocketAsyncEventArgs();
                readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
                readWriteEventArg.UserToken = new AsyncUserToken();

                // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object  
                m_bufferManager.SetBuffer(readWriteEventArg);
                // add SocketAsyncEventArg to the pool  
                m_pool.Push(readWriteEventArg);
            }
        }

        void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            // determine which type of operation just completed and call the associated handler  
            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");
            }

        }

四、服务的启动和终止

分配好内容之后,所有的准备工作就好了,下面我们可以直接写SocketServer这个类的启动函数了:

        /// <summary>  
        /// 启动服务  
        /// </summary>  
        /// <param name="localEndPoint"></param>  
        public bool Start(IPEndPoint localEndPoint)
        {
            try
            {
                m_clients.Clear();
                listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                listenSocket.Bind(localEndPoint);
                // start the server with a listen backlog of 100 connections  
                listenSocket.Listen(m_maxConnectNum);
                // post accepts on the listening socket  
                StartAccept(null);
                return true;
            }
            catch (Exception)
            {
                return false;
            }
        }

        // Begins an operation to accept a connection request from the client   
        //  
        // <param name="acceptEventArg">The context object to use when issuing   
        // the accept operation on the server's listening socket</param>  
        public void StartAccept(SocketAsyncEventArgs acceptEventArg)
        {
            if (acceptEventArg == null)
            {
                acceptEventArg = new SocketAsyncEventArgs();
                acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
            }
            else
            {
                // socket must be cleared since the context object is being reused  
                acceptEventArg.AcceptSocket = null;
            }

            m_maxNumberAcceptedClients.WaitOne();
            if (!listenSocket.AcceptAsync(acceptEventArg))
            {
                ProcessAccept(acceptEventArg);
            }
        }


        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            try
            {
                Interlocked.Increment(ref m_clientCount);
                // Get the socket for the accepted client connection and put it into the   
                //ReadEventArg object user token  
                SocketAsyncEventArgs readEventArgs = m_pool.Pop();
                AsyncUserToken userToken = (AsyncUserToken)readEventArgs.UserToken;
                userToken.Socket = e.AcceptSocket;
                userToken.ConnectTime = DateTime.Now;
                userToken.Remote = e.AcceptSocket.RemoteEndPoint;
                userToken.IPAddress = ((IPEndPoint)(e.AcceptSocket.RemoteEndPoint)).Address;

                lock (m_clients) { m_clients.Add(userToken); }

                if (ClientNumberChange != null)
                    ClientNumberChange(1, userToken);
                if (!e.AcceptSocket.ReceiveAsync(readEventArgs))
                {
                    ProcessReceive(readEventArgs);
                }
            }
            catch (Exception me)
            {
                LogHelper.WriteLog(me.Message + "\r\n" + me.StackTrace);
            }

            // Accept the next connection request  
            if (e.SocketError == SocketError.OperationAborted) return;
            StartAccept(e);
        }

通过上述步骤,我们就可以通过一个IP地址和一个端口来启动SocketServer服务了。为了使SocketServer这个服务器接收软件更加健壮,我们再添加一个停止服务的功能:

        /// <summary>  
        /// 停止服务  
        /// </summary>  
        public void Stop()
        {
            foreach (AsyncUserToken token in m_clients)
            {
                try
                {
                    listenSocket.Shutdown(SocketShutdown.Both);
                }
                catch (Exception) { }
            }
            

            listenSocket.Close();
            int c_count = m_clients.Count;
            lock (m_clients) { m_clients.Clear(); }

            if (ClientNumberChange != null)
                ClientNumberChange(-c_count, null);
        }

上述代码通过循环来关闭每一个Socket连接。

五、注册服务端信息接收处理函数

在前面的代码中,我们在初始化的时候,已经注册了SocketAsyncEventArgs这个类中的Completed事件,这个事件的意思就是,服务器不管是接收还是发送,完成后的函数都是IO_Completed。

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

        void IO_Completed(object sender, SocketAsyncEventArgs e)
        {
            // determine which type of operation just completed and call the associated handler  
            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");
            }

        }

而在委托的IO_Completed函数中,我们通过接收到的信息判断是接收消息还是发送消息,如果是接收,那么我们使用ProcessReceive(SocketAsyncEventArgs e)这个函数来处理接收到的信息。

        // This method is invoked when an asynchronous receive operation completes.   
        // If the remote host closed the connection, then the socket is closed.    
        // If data was received then the data is echoed back to the client.  
        //  
        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            try
            {
                // check if the remote host closed the connection  
                AsyncUserToken token = (AsyncUserToken)e.UserToken;
                if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
                {
                    //读取数据  
                    byte[] data = new byte[e.BytesTransferred];
                    Array.Copy(e.Buffer, e.Offset, data, 0, e.BytesTransferred);
                    lock (token.Buffer)
                    {
                        token.Buffer.AddRange(data);
                    }
                    if (ReceiveClientData != null)
                    {
                        ReceiveClientData(token, data);
                    }

                    //继续接收. 为什么要这么写,请看Socket.ReceiveAsync方法的说明  
                    if (!token.Socket.ReceiveAsync(e))
                        this.ProcessReceive(e);
                }
                else
                {
                    CloseClientSocket(e);
                }
            }
            catch (Exception xe)
            {
                LogHelper.WriteLog(xe.Message + "\r\n" + xe.StackTrace);
            }
        }

上面的代码可以看出来,我们接收到信息后,是要再把信息委托给另外的注册委托函数进行处理:

                    if (ReceiveClientData != null)
                    {
                        ReceiveClientData(token, data);
                    }

到这里为止,我们就把SocketServer这个类的接收功能给实现了,如果有读者对客户端发送信息感兴趣,就直接参考下面的代码:

六、注册服务端信息发送处理函数

        // This method is invoked when an asynchronous send operation completes.    
        // The method issues another receive on the socket to read any additional   
        // data sent from the client  
        //  
        // <param name="e"></param>  
        private void ProcessSend(SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                // done echoing data back to the client  
                AsyncUserToken token = (AsyncUserToken)e.UserToken;
                // read the next block of data send from the client  
                bool willRaiseEvent = token.Socket.ReceiveAsync(e);
                if (!willRaiseEvent)
                {
                    ProcessReceive(e);
                }
            }
            else
            {
                CloseClientSocket(e);
            }
        }

        //关闭客户端  
        private void CloseClientSocket(SocketAsyncEventArgs e)
        {
            AsyncUserToken token = e.UserToken as AsyncUserToken;

            lock (m_clients) { m_clients.Remove(token); }
            //如果有事件,则调用事件,发送客户端数量变化通知  
            if (ClientNumberChange != null)
                ClientNumberChange(-1, token);
            // close the socket associated with the client  
            try
            {
                token.Socket.Shutdown(SocketShutdown.Send);
            }
            catch (Exception) { }
            token.Socket.Close();
            // decrement the counter keeping track of the total number of clients connected to the server  
            Interlocked.Decrement(ref m_clientCount);
            m_maxNumberAcceptedClients.Release();
            // Free the SocketAsyncEventArg so they can be reused by another client  
            e.UserToken = new AsyncUserToken();
            m_pool.Push(e);
        }



        /// <summary>  
        /// 对数据进行打包,然后再发送  
        /// </summary>  
        /// <param name="token"></param>  
        /// <param name="message"></param>  
        /// <returns></returns>  
        public void SendMessage(AsyncUserToken token, byte[] message)
        {
            if (token == null || token.Socket == null || !token.Socket.Connected)
                return;
            try
            {
                //新建异步发送对象, 发送消息  
                SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs();
                sendArg.UserToken = token;
                sendArg.SetBuffer(byte, 0, byte.Length);  //将数据放置进去.  
                token.Socket.SendAsync(sendArg);
            }
            catch (Exception e)
            {
                LogHelper.WriteLog("SendMessage - Error:" + e.Message);
            }
        }

上述的代码,都已提供下载,下载地址:

https://download.csdn.net/download/aplsc/11817545

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 基于SocketAsyncEventArgs的高并发客户端是一种用于处理大量客户端连接的异步编程模型。SocketAsyncEventArgs类提供了高效的网络通信操作,可以处理多个客户端的请求,并且能够在不阻塞主线程的情况下同时处理多个请求。 在构建一个基于SocketAsyncEventArgs的高并发客户端时,需要以下步骤: 1. 创建一个SocketAsyncEventArgs对象的池,用于存储可重复使用的SocketAsyncEventArgs实例。这个池的大小可以根据需求进行配置,通常是根据服务器的负载和性能来确定。 2. 创建一个SocketAsyncEventArgs对象的工作队列,用于存储待处理的客户端请求。当客户端连接到服务器时,将创建一个SocketAsyncEventArgs实例,并将其加入到工作队列中。 3. 创建一个监听套接字,用于接受新的客户端连接。当有新的客户端连接时,将从SocketAsyncEventArgs对象池中获取一个可用的SocketAsyncEventArgs实例,并使用该实例处理该客户端的请求。 4. 在处理客户端请求的SocketAsyncEventArgs实例中,可以使用异步方法来执行读取、写入和关闭操作。这样可以确保在处理一个请求时,不会阻塞其他请求的处理,并且可以充分利用系统资源。 5. 当请求处理完成后,将SocketAsyncEventArgs实例重置,并将其返回到SocketAsyncEventArgs对象池中,以便可以被其他请求重用。 通过以上步骤,可以实现一个基于SocketAsyncEventArgs的高并发客户端,能够有效处理多个客户端的请求,并提供高性能和可扩展性。同时,使用异步编程模型可以最大限度地提高系统的并发能力和响应速度。 ### 回答2: 基于SocketAsyncEventArgs的高并发客户端是一种使用异步Socket编程模型来处理大量并发连接的技术。SocketAsyncEventArgs是.NET Framework提供的一个高性能的异步网络编程组件。 基于SocketAsyncEventArgs的高并发客户端可以实现以下功能: 1. 同时处理多个连接请求:通过使用SocketAsyncEventArgs对象池,可以预先创建多个SocketAsyncEventArgs对象并复用它们来处理多个客户端连接请求,从而实现高并发处理能力。 2. 异步接收和发送数据:使用SocketAsyncEventArgs的异步方法,可以实现高效的数据传输,避免了传统的同步I/O方式中的线程阻塞等待问题。 3. 使用IOCP模型提升性能:SocketAsyncEventArgs内部使用IOCP(I/O完成端口)模型来实现异步操作,这种模型可以减少系统开销,并提高并发处理能力。 4. 支持连接池管理:通过使用连接池管理SocketAsyncEventArgs对象,可以灵活地管理客户端连接,包括创建、回收和释放等,以提高连接的复用性和资源的有效利用。 基于SocketAsyncEventArgs的高并发客户端适用于需要同时处理大量客户端连接请求的场景,如高性能服务器、网络游戏服务器等。它可以通过有效地利用异步I/O和IOCP模型来实现高并发处理能力,提高系统的吞吐量和响应速度。 总之,基于SocketAsyncEventArgs的高并发客户端是一种使用异步Socket编程模型来实现高性能、高并发处理的技术,通过预先创建和复用SocketAsyncEventArgs对象、异步操作和使用IOCP模型等手段,可以实现高效的数据传输和同时处理多个连接请求的能力。 ### 回答3: 基于SocketAsyncEventArgs的高并发客户端可以使用异步编程模型来实现。在传统的同步模型下,每个客户端连接都需要创建一个新的线程,当并发量较高时,线程的创建和销毁会带来较大的开销,导致服务器性能下降。 而使用SocketAsyncEventArgs实现高并发客户端,则可以避免频繁的线程创建和销毁,提高服务器的并发处理能力。基于SocketAsyncEventArgs,可以使用对象池来管理SocketAsyncEventArgs对象的创建和回收,避免频繁的GC开销,提高性能。 在基于SocketAsyncEventArgs的高并发客户端中,可以使用异步方法来接收和发送数据。通过BeginReceive和BeginSend方法,可以异步的从客户端接收和发送数据,而不会阻塞主线程,提高性能。 此外,还可以使用AsyncAwait异步编程模式来处理异步操作,将回调函数改写为异步方法,使代码更加简洁易读。 在处理高并发时,需要注意资源的合理利用和线程的调度。可以使用线程池来管理任务的调度,避免线程的频繁创建和销毁。同时,要正确处理多线程下的共享资源,避免出现竞争条件和死锁等问题。 综上所述,基于SocketAsyncEventArgs的高并发客户端可以极大地提高服务器的性能和并发处理能力,有效避免了多线程带来的开销问题。同时,需要合理利用异步编程模型、对象池和线程池等技术手段来实现高效的并发处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值