異步Socket Tcp服務器實現(C#)

原創性申明

本文作者: 小竹zz  本文地址:http://blog.csdn.net/zhujunxxxxx 轉載請註明出處。

介紹

我之前寫過一篇IOCP的文章: http://blog.csdn.net/zhujunxxxxx/article/details/43573879 這個比異步socket性能好,因爲它複用對象了。

在c#中微軟已經提供了TcpListener和TcpClient來實現Tcp的通訊,這部分已經有人寫了比較好的異步服務器代碼 http://www.cnblogs.com/gaochundong/archive/2013/04/14/csharp_async_tcp_server.html 這位博主寫的博客質量真是高,我經常瀏覽他的博客總是有很多讓我驚喜的地方,他使用的是TcpListener來實現的異步服務器的。

我的socket版本其實本質上和他的沒有區別,就只是改寫了一點點,所以在這裏貼一份代碼就是了,多的不解釋了

代碼

服務器核心代碼 AsyncServer.cs
/// <summary>
    /// 異步SOCKET 服務器
    /// </summary>
    public class AsyncServer : IDisposable
    {

        #region Fields
        /// <summary>
        /// 服務器程序允許的最大客戶端連接數
        /// </summary>
        private int _maxClient;

        /// <summary>
        /// 當前的連接的客戶端數
        /// </summary>
        private int _clientCount;

        /// <summary>
        /// 服務器使用的異步socket
        /// </summary>
        private Socket _serverSock;

        /// <summary>
        /// 客戶端會話列表
        /// </summary>
        private List<Session> _clients;

        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>
        /// 異步Socket TCP服務器
        /// </summary>
        /// <param name="listenPort">監聽的端口</param>
        public AsyncServer(int listenPort)
            : this(IPAddress.Any, listenPort,1024)
        {
        }

        /// <summary>
        /// 異步Socket TCP服務器
        /// </summary>
        /// <param name="localEP">監聽的終結點</param>
        public AsyncServer(IPEndPoint localEP)
            : this(localEP.Address, localEP.Port,1024)
        {
        }

        /// <summary>
        /// 異步Socket TCP服務器
        /// </summary>
        /// <param name="localIPAddress">監聽的IP地址</param>
        /// <param name="listenPort">監聽的端口</param>
        /// <param name="maxClient">最大客戶端數量</param>
        public AsyncServer(IPAddress localIPAddress, int listenPort,int maxClient)
        {
            this.Address = localIPAddress;
            this.Port = listenPort;
            this.Encoding = Encoding.Default;

            _maxClient = maxClient;
            _clients = new List<Session>();
            _serverSock = new Socket(localIPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        }

        #endregion


        #region Server

        /// <summary>
        /// 啓動服務器
        /// </summary>
        /// <returns>異步TCP服務器</returns>
        public AsyncServer Start()
        {
            if (!IsRunning)
            {
                IsRunning = true;
                _serverSock.Bind(new IPEndPoint(this.Address, this.Port));
                _serverSock.Listen(1024);
                _serverSock.BeginAccept(new AsyncCallback(HandleAcceptConnected), _serverSock);
            }
            return this;
        }

        /// <summary>
        /// 啓動服務器
        /// </summary>
        /// <param name="backlog">
        /// 服務器所允許的掛起連接序列的最大長度
        /// </param>
        /// <returns>異步TCP服務器</returns>
        public AsyncServer Start(int backlog)
        {
            if (!IsRunning)
            {
                IsRunning = true;
                _serverSock.Bind(new IPEndPoint(this.Address, this.Port));
                _serverSock.Listen(backlog);
                _serverSock.BeginAccept(new AsyncCallback(HandleAcceptConnected), _serverSock);
            }
            return this;
        }

        /// <summary>
        /// 停止服務器
        /// </summary>
        /// <returns>異步TCP服務器</returns>
        public AsyncServer Stop()
        {
            if (IsRunning)
            {
                IsRunning = false;
                _serverSock.Close();
                //TODO 關閉對所有客戶端的連接

            }
            return this;
        }

        #endregion

        #region Receive
        /// <summary>
        /// 處理客戶端連接
        /// </summary>
        /// <param name="ar"></param>
        private void HandleAcceptConnected(IAsyncResult ar)
        {
            if (IsRunning)
            {
                Socket server = (Socket)ar.AsyncState;
                Socket client = server.EndAccept(ar);
                
                //檢查是否達到最大的允許的客戶端數目
                if (_clientCount == _maxClient)
                {
                    //TODO 觸發事件
                    RaiseServerException(null);
                }
                else
                {
                    Session session = new Session(client);
                    lock (_clients)
                    {
                        _clients.Add(session);
                        _clientCount++;
                        RaiseClientConnected(session); //觸發客戶端連接事件
                    }
                    session.RecvDataBuffer = new byte[client.ReceiveBufferSize];
                    //開始接受來自該客戶端的數據
                    client.BeginReceive(session.RecvDataBuffer, 0, session.RecvDataBuffer.Length, SocketFlags.None,
                     new AsyncCallback(HandleDataReceived), session);
                }
                //接受下一個請求
                server.BeginAccept(new AsyncCallback(HandleAcceptConnected), ar.AsyncState);
            }
        }
        /// <summary>
        /// 處理客戶端數據
        /// </summary>
        /// <param name="ar"></param>
        private void HandleDataReceived(IAsyncResult ar)
        {
            if (IsRunning)
            {
                Session session = (Session)ar.AsyncState;
                Socket client = session.ClientSocket;
                try
                {
                    //如果兩次開始了異步的接收,所以當客戶端退出的時候
                    //會兩次執行EndReceive
                    int recv = client.EndReceive(ar);
                    if (recv == 0)
                    {
                        //TODO 觸發事件 (關閉客戶端)
                        CloseSession(session);
                        RaiseNetError(session);
                        return;
                    }
                    //TODO 處理已經讀取的數據 ps:數據在session的RecvDataBuffer中
                    RaiseDataReceived(session);
                    //TODO 觸發數據接收事件
                }
                catch (SocketException ex)
                {
                    //TODO 異常處理
                    RaiseNetError(session);
                }
                finally
                {
                    //繼續接收來自來客戶端的數據
                    client.BeginReceive(session.RecvDataBuffer, 0, session.RecvDataBuffer.Length, SocketFlags.None,
                     new AsyncCallback(HandleDataReceived), session);
                }
            }
        }
        #endregion

        #region Send
        /// <summary>
        /// 發送數據
        /// </summary>
        /// <param name="session">接收數據的客戶端會話</param>
        /// <param name="data">數據報文</param>
        public void Send(Session session, byte[] data)
        {
            Send(session.ClientSocket,data);
        }

        /// <summary>
        /// 異步發送數據至指定的客戶端
        /// </summary>
        /// <param name="client">客戶端</param>
        /// <param name="data">報文</param>
        public void Send(Socket client, byte[] data)
        {
            if (!IsRunning)
                throw new InvalidProgramException("This TCP Scoket server has not been started.");

            if (client == null)
                throw new ArgumentNullException("client");

            if (data == null)
                throw new ArgumentNullException("data");
            client.BeginSend(data, 0, data.Length, SocketFlags.None,
             new AsyncCallback(SendDataEnd), client);
        }

        /// <summary>
        /// 發送數據完成處理函數
        /// </summary>
        /// <param name="ar">目標客戶端Socket</param>
        private void SendDataEnd(IAsyncResult ar)
        {
            ((Socket)ar.AsyncState).EndSend(ar);
        }
        #endregion

        #region Events
        /// <summary>
        /// 接收到數據事件
        /// </summary>
        public event EventHandler<EventArgs> DataReceived;

        private void RaiseDataReceived(Session session)
        {
            if (DataReceived != null)
            {
                DataReceived(this, new AsyncEventArgs(session));
            }
        }

        /// <summary>
        /// 與客戶端的連接已建立事件
        /// </summary>
        public event EventHandler<AsyncEventArgs> ClientConnected;
        /// <summary>
        /// 與客戶端的連接已斷開事件
        /// </summary>
        public event EventHandler<AsyncEventArgs> ClientDisconnected;

        /// <summary>
        /// 觸發客戶端連接事件
        /// </summary>
        /// <param name="session"></param>
        private void RaiseClientConnected(Session session)
        {
            if (ClientConnected != null)
            {
                ClientConnected(this, new AsyncEventArgs(session));
            }
        }
        /// <summary>
        /// 觸發客戶端連接斷開事件
        /// </summary>
        /// <param name="client"></param>
        private void RaiseClientDisconnected(Socket client)
        {
            if (ClientDisconnected != null)
            {
                ClientDisconnected(this, new AsyncEventArgs("連接斷開"));
            }
        }
        /// <summary>
        /// 網絡錯誤事件
        /// </summary>
        public event EventHandler<AsyncEventArgs> NetError;
        /// <summary>
        /// 觸發網絡錯誤事件
        /// </summary>
        /// <param name="client"></param>
        private void RaiseNetError(Session session)
        {
            if (NetError != null)
            {
                NetError(this, new AsyncEventArgs(session));
            }
        }

        /// <summary>
        /// 異常事件
        /// </summary>
        public event EventHandler<AsyncEventArgs> ServerException;
        /// <summary>
        /// 觸發異常事件
        /// </summary>
        /// <param name="client"></param>
        private void RaiseServerException(Session session)
        {
            if (ServerException != null)
            {
                ServerException(this, new AsyncEventArgs(session));
            }
        }
        #endregion


        #region Close
        /// <summary>
        /// 關閉一個與客戶端之間的會話
        /// </summary>
        /// <param name="closeClient">需要關閉的客戶端會話對象</param>
        public void CloseSession(Session session)
        {
            if (session != null)
            {
                session.Datagram = null;
                session.RecvDataBuffer = null;

                _clients.Remove(session);
                _clientCount--;
                //TODO 觸發關閉事件
                session.Close();
            }
        }
        /// <summary>
        /// 關閉所有的客戶端會話,與所有的客戶端連接會斷開
        /// </summary>
        public void CloseAllClient()
        {
            foreach (Session client in _clients)
            {
                CloseSession(client);
            }
            _clientCount = 0;
            _clients.Clear();
        }

        /// <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
                        RaiseServerException(null);
                    }
                }
                disposed = true;
            }
        }
        #endregion
    }

其中使用了一個Session類,來封裝對客戶端的連接
Session,cs
/// <summary>
    /// 客戶端與服務器之間的會話類
    /// </summary>
    public class Session
    {
        #region 字段
        /// <summary>
        /// 接收數據緩衝區
        /// </summary>
        private byte[] _recvBuffer;

        /// <summary>
        /// 客戶端發送到服務器的報文
        /// 注意:在有些情況下報文可能只是報文的片斷而不完整
        /// </summary>
        private string _datagram;

        /// <summary>
        /// 客戶端的Socket
        /// </summary>
        private Socket _clientSock;

        #endregion

        #region 屬性

        /// <summary>
        /// 接收數據緩衝區 
        /// </summary>
        public byte[] RecvDataBuffer
        {
            get
            {
                return _recvBuffer;
            }
            set
            {
                _recvBuffer = value;
            }
        }

        /// <summary>
        /// 存取會話的報文
        /// </summary>
        public string Datagram
        {
            get
            {
                return _datagram;
            }
            set
            {
                _datagram = value;
            }
        }

        /// <summary>
        /// 獲得與客戶端會話關聯的Socket對象
        /// </summary>
        public Socket ClientSocket
        {
            get
            {
                return _clientSock;

            }
        }


        #endregion

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="cliSock">會話使用的Socket連接</param>
        public Session(Socket cliSock)
        {

            _clientSock = cliSock;
        }
        /// <summary>
        /// 關閉會話
        /// </summary>
        public void Close()
        {

            //關閉數據的接受和發送
            _clientSock.Shutdown(SocketShutdown.Both);

            //清理資源
            _clientSock.Close();
        }
    }

事件類
class AsyncEventArgs : EventArgs
    {
        /// <summary>
        /// 提示信息
        /// </summary>
        public string _msg;

        public Session _sessions;

        /// <summary>
        /// 是否已經處理過了
        /// </summary>
        public bool IsHandled { get; set; }

        public AsyncEventArgs(string msg)
        {
            this._msg = msg;
            IsHandled = false;
        }
        public AsyncEventArgs(Session session)
        {
            this._sessions = session;
            IsHandled = false;
        }
        public AsyncEventArgs(string msg, Session session)
        {
            this._msg = msg;
            this._sessions = session;
            IsHandled = false;
        }
    }

 Socket版本的異步客戶端代碼
/// <summary>
    /// 異步客戶端
    /// </summary>
    public class AsyncClient
    {
        #region Fields

        private Socket _client;
        private bool disposed = false;
        private int retries = 0;

        #endregion

        #region Properties

        /// <summary>
        /// 是否已與服務器建立連接
        /// </summary>
        public bool Connected { get { return _client.Connected; } }
        /// <summary>
        /// 遠端服務器的IP地址列表
        /// </summary>
        public IPAddress[] Addresses { get; private set; }
        /// <summary>
        /// 遠端服務器的端口
        /// </summary>
        public int Port { get; private set; }
        /// <summary>
        /// 連接重試次數
        /// </summary>
        public int Retries { get; set; }
        /// <summary>
        /// 連接重試間隔
        /// </summary>
        public int RetryInterval { get; set; }
        /// <summary>
        /// 遠端服務器終結點
        /// </summary>
        public IPEndPoint RemoteIPEndPoint
        {
            get { return new IPEndPoint(Addresses[0], Port); }
        }
        /// <summary>
        /// 本地客戶端終結點
        /// </summary>
        protected IPEndPoint LocalIPEndPoint { get; private set; }
        /// <summary>
        /// 通信所使用的編碼
        /// </summary>
        public Encoding Encoding { get; set; }

        #endregion

        #region 構造函數

        /// <summary>
        /// 異步TCP客戶端
        /// </summary>
        /// <param name="remoteEP">遠端服務器終結點</param>
        public AsyncClient(IPEndPoint remoteEP)
            : this(new[] { remoteEP.Address }, remoteEP.Port)
        {
        }
        /// <summary>
        /// 異步TCP客戶端
        /// </summary>
        /// <param name="remoteEP">遠端服務器終結點</param>
        /// <param name="localEP">本地客戶端終結點</param>
        public AsyncClient(IPEndPoint remoteEP, IPEndPoint localEP)
            : this(new[] { remoteEP.Address }, remoteEP.Port, localEP)
        {
        }

        /// <summary>
        /// 異步TCP客戶端
        /// </summary>
        /// <param name="remoteIPAddress">遠端服務器IP地址</param>
        /// <param name="remotePort">遠端服務器端口</param>
        public AsyncClient(IPAddress remoteIPAddress, int remotePort)
            : this(new[] { remoteIPAddress }, remotePort)
        {
        }

        /// <summary>
        /// 異步TCP客戶端
        /// </summary>
        /// <param name="remoteIPAddress">遠端服務器IP地址</param>
        /// <param name="remotePort">遠端服務器端口</param>
        /// <param name="localEP">本地客戶端終結點</param>
        public AsyncClient(
          IPAddress remoteIPAddress, int remotePort, IPEndPoint localEP)
            : this(new[] { remoteIPAddress }, remotePort, localEP)
        {
        }

        /// <summary>
        /// 異步TCP客戶端
        /// </summary>
        /// <param name="remoteHostName">遠端服務器主機名</param>
        /// <param name="remotePort">遠端服務器端口</param>
        public AsyncClient(string remoteHostName, int remotePort)
            : this(Dns.GetHostAddresses(remoteHostName), remotePort)
        {
        }

        /// <summary>
        /// 異步TCP客戶端
        /// </summary>
        /// <param name="remoteHostName">遠端服務器主機名</param>
        /// <param name="remotePort">遠端服務器端口</param>
        /// <param name="localEP">本地客戶端終結點</param>
        public AsyncClient(
          string remoteHostName, int remotePort, IPEndPoint localEP)
            : this(Dns.GetHostAddresses(remoteHostName), remotePort, localEP)
        {
        }

        /// <summary>
        /// 異步TCP客戶端
        /// </summary>
        /// <param name="remoteIPAddresses">遠端服務器IP地址列表</param>
        /// <param name="remotePort">遠端服務器端口</param>
        public AsyncClient(IPAddress[] remoteIPAddresses, int remotePort)
            : this(remoteIPAddresses, remotePort, null)
        {
        }

        /// <summary>
        /// 異步TCP客戶端
        /// </summary>
        /// <param name="remoteIPAddresses">遠端服務器IP地址列表</param>
        /// <param name="remotePort">遠端服務器端口</param>
        /// <param name="localEP">本地客戶端終結點</param>
        public AsyncClient(
          IPAddress[] remoteIPAddresses, int remotePort, IPEndPoint localEP)
        {
            this.Addresses = remoteIPAddresses;
            this.Port = remotePort;
            this.LocalIPEndPoint = localEP;
            this.Encoding = Encoding.Default;

            if (this.LocalIPEndPoint != null)
            {
                _client = new Socket(LocalIPEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            }

            Retries = 3;
            RetryInterval = 5;
        }

        #endregion

        #region Connect
        /// <summary>
        /// 連接到服務器
        /// </summary>
        /// <returns>異步TCP客戶端</returns>
        public AsyncClient Connect()
        {
            if (!Connected)
            {
                // start the async connect operation
                _client.BeginConnect(
                   Addresses, Port, HandleTcpServerConnected, _client);
            }

            return this;
        }
        /// <summary>
        /// 關閉與服務器的連接
        /// </summary>
        /// <returns>異步TCP客戶端</returns>
        public AsyncClient Close()
        {
            if (Connected)
            {
                retries = 0;
                _client.Close();
                RaiseServerDisconnected(Addresses, Port);
            }

            return this;
        }

        #endregion

        #region Receive

        private void HandleTcpServerConnected(IAsyncResult ar)
        {
            try
            {
                _client.EndConnect(ar);
                RaiseServerConnected(Addresses, Port);
                retries = 0;
            }
            catch (Exception ex)
            {

            }
            // we are connected successfully and start asyn read operation.
            byte[] buffer = new byte[_client.ReceiveBufferSize];
            _client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, HandleDatagramReceived, buffer);
        }

        private void HandleDatagramReceived(IAsyncResult ar)
        {
            try
            {
                int recv = _client.EndReceive(ar);

                byte[] buffer = (byte[])ar.AsyncState;

                byte[] receivedBytes = new byte[recv];

                Buffer.BlockCopy(buffer, 0, receivedBytes, 0, recv);

                RaiseDatagramReceived(_client, receivedBytes);

                // then start reading from the network again
                _client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, HandleDatagramReceived, buffer);
            }
            catch (Exception)
            {

            }
        }

        #endregion

        #region Events

        /// <summary>
        /// 接收到數據報文事件
        /// </summary>
        public event EventHandler<EventArgs> DatagramReceived;

        private void RaiseDatagramReceived(Socket sender, byte[] datagram)
        {
            if (DatagramReceived != null)
            {
                DatagramReceived(this, new EventArgs());
            }
        }

        /// <summary>
        /// 與服務器的連接已建立事件
        /// </summary>
        public event EventHandler<EventArgs> ServerConnected;
        /// <summary>
        /// 與服務器的連接已斷開事件
        /// </summary>
        public event EventHandler<EventArgs> ServerDisconnected;
        /// <summary>
        /// 與服務器的連接發生異常事件
        /// </summary>
        public event EventHandler<EventArgs> ServerExceptionOccurred;

        private void RaiseServerConnected(IPAddress[] ipAddresses, int port)
        {
            if (ServerConnected != null)
            {
                ServerConnected(this, new EventArgs());
            }
        }

        private void RaiseServerDisconnected(IPAddress[] ipAddresses, int port)
        {
            if (ServerDisconnected != null)
            {
                ServerDisconnected(this, new EventArgs());
            }
        }

        private void RaiseServerExceptionOccurred(
          IPAddress[] ipAddresses, int port, Exception innerException)
        {
            if (ServerExceptionOccurred != null)
            {
                ServerExceptionOccurred(this, new EventArgs());
            }
        }

        #endregion

        #region Send

        /// <summary>
        /// 發送報文
        /// </summary>
        /// <param name="datagram">報文</param>
        public void Send(byte[] datagram)
        {
            if (datagram == null)
                throw new ArgumentNullException("datagram");

            if (!Connected)
            {
                RaiseServerDisconnected(Addresses, Port);
                throw new InvalidProgramException(
                  "This client has not connected to server.");
            }

            _client.BeginSend(datagram, 0, datagram.Length, SocketFlags.None, HandleDataSend, _client);
        }

        private void HandleDataSend(IAsyncResult ar)
        {
            ((Socket)ar.AsyncState).EndSend(ar);
        }

        /// <summary>
        /// 發送報文
        /// </summary>
        /// <param name="datagram">報文</param>
        public void Send(string datagram)
        {
            Send(this.Encoding.GetBytes(datagram));
        }

        #endregion

        #region IDisposable Members

        /// <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
                    {
                        Close();

                        if (_client != null)
                        {
                            _client = null;
                        }
                    }
                    catch (SocketException)
                    {
                    }
                }

                disposed = true;
            }
        }

        #endregion

    }



發佈了127 篇原創文章 · 獲贊 161 · 訪問量 94萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章