1. 程式人生 > >非同步Socket Tcp伺服器實現(C#)

非同步Socket Tcp伺服器實現(C#)

/// <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類,來封裝對客戶端的連線