C#Socket異步編程


一、服務端

  由於同步的方式在處理多客戶端處理時會出現多線程資源處理問題,所以在處理並發和並行問題時大多采用異步的形式。Server端只是單獨的接收請求,然后將請求丟給對應的客戶端對象Client進行處理,Client端則對消息進行處理

,將解析出來的消息傳遞給控制器Controller進行處理。

二、涉及到拆包問題(客戶端消息發送形式,以及服務端響應消息形式)

  (1)例如要發送的消息如下

  

string message = "Hello Word!"

  由於使用Socket傳輸數據時,采用的是字節流的形式進行傳遞,所以字符串必須轉換為byte字節數組

 

byte[] databytes = Encoding.UTF8.GetBytes(message)

  當客戶端很頻繁的給服務端發送消息時,服務端接收的消息可能會存在粘包問題,所以要進行拆包,所以要自己定義一下消息的發送組成形式,我這里采用的是(包頭 + 包的內容),包頭為當前包的長度,所以客戶端發送每一條消息時,必須先將當前要發送的消息進行長度獲取,然后將長度轉化為byte字節數組,代碼如下:

 注意:在C#中一個Int32的整數轉換成byte數組后悔占固定的四個字節的長度,利用這個特性,就能完成拆包處理,同時Encoding.UTF8.GetString()在命名空間System.Text下。

int length = databytes.Length //獲取消息的長度
byte[] lengthbytes = BitConverter.GetBytes(length) //將消息的長度轉化為字節數組
byte[] newbytes =  lengthbytes.Concat(databytes).ToArray()//將字符串長度當做包頭,要發送的消息當做包進行連接,得到一個新的包,也就是要發送給服務端的包

  (2)服務端對消息處理時,要按照上面商議的方式對包進行拆解,才能得到對應的一條完整的消息。代碼過程如下:

  

int msgLength = BitConverter.Int32(data,0)//data是一個byte類型的數組,存取的就是客戶端發送給服務端的消息,BitConverter.Int32()方法能取出數組中前4個字節,並將其轉換為Int32的整數,也就是消息的長度。 

  

string retmsg = Encoding.UTF8.GetString(data,4,msgLength) //獲取完整的消息,從第4個字節開始提取msgLength的長度的字節,然后將其轉換為字符串。

三、數據處理類代碼如下(接收消息,拆包,包裝數據):

/// <summary>
    /// 消息的處理
    /// </summary>
    class Message
    {
        /// <summary>
        /// 存儲的最大數據長度
        /// </summary>
        private const int MaxLength = 1024;
        private byte[] data = new byte[MaxLength];
        private int Index = 0;
        public byte[] GetData
        {
            get { return data; }
        }

        private void AddCount(int count)
        {
            Index += count;
        }

        public int RemainSize
        {
            get { return data.Length - Index; }
        }

        public int GetIndex
        {
            get { return Index; }
        }
    
        public void ReadMessage(int amount,Action<string> processDataCallBack)
        {
            AddCount(amount);
            while (true)
            {
                if (Index < 4) return;
                int count = BitConverter.ToInt32(data, 0);
                if (Index - 4 >= count)
                {
                    string str = Encoding.UTF8.GetString(data, 4, count);
                    if (processDataCallBack != null)
                    {
                        processDataCallBack.Invoke(str);//回調函數,根據消息的內容進行相應的業務處理
                    }
                    else
                    {
                        Console.WriteLine("處理請求的回調函數OnProcessMessage為空!");
                    }
                    Array.Copy(data, count + 4, data, 0, Index - 4 - count);
                    Index -= count + 4;
                }
                else
                {
                    break;
                }
            }
        }

        /// <summary>
        /// 對消息進行包裝
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static byte[] GetNewBytes(string str)
        {
            byte[] databytes = Encoding.UTF8.GetBytes(str);
            int length = databytes.Length;
            byte[] lengthbyts = BitConverter.GetBytes(length);
            return lengthbyts.Concat(databytes).ToArray();
        }
    }

四、Client類代碼如下(這里的Client類只負責客戶端socket的處理,也就是客戶端socket套接字的一個代理,每一個客戶端對應一個Client對象。):

/// <summary>
    /// 管理client連接
    /// </summary>
    class Client
    {
        public Client()
        {

        }
        public Client(Socket clientSocket,Server server)
        {
            this.clientSocket = clientSocket;
            this.server = server;
            Msg = new Message();
            mySqlConnection = ConnHelper.Connect();
        }

        private Socket clientSocket;
        private Server server;
        private Message Msg;
        private MySqlConnection mySqlConnection;//持有數據庫的連接對象,這個可以加,可以不加
        public void Start()
        {
            clientSocket.BeginReceive(Msg.GetData, Msg.GetIndex, Msg.RemainSize, SocketFlags.None, ReceiveCallBack, null);
        }

        private void ReceiveCallBack(IAsyncResult ar)
        {
            try
            {
                int count = clientSocket.EndReceive(ar);
                if (count == 0)
                {
                    Close();
                }
                Msg.ReadMessage(count, OnProcessMessage);//這里傳遞了一個回調函數,會將解析出來的消息回調出來交給server中轉
                clientSocket.BeginReceive(Msg.GetData, Msg.GetIndex, Msg.RemainSize, SocketFlags.None, ReceiveCallBack, null);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
                Close();
            }
        }

        /// <summary>
        /// server端處理客戶端的消息,這里使用了一個中介者,服務端當中介,將消息轉發給對應的控制器,去執行對應的代碼邏輯
        /// </summary>
        /// <param name="requestCode"></param>
        /// <param name="actionCode"></param>
        /// <param name="data"></param>
        private void OnProcessMessage(string data)
        {
            server.RequestHandle(data, this);
        }

        /// <summary> 
        /// 給客戶端返回響應
        /// </summary>
        public void Send(string data)
        {
            byte[] databytes = Message.GetNewBytes(data);
            clientSocket.Send(databytes);
        }
        
        private void Close()
        {
            ConnHelper.CloseConnection(mySqlConnection);//關閉數據庫的連接
            server.RemoveClient(this);
            if (clientSocket != null)
            clientSocket.Close();
        }
    }

五、服務端代碼如下(服務端接收客戶端的連接,同時又擔任了每一個客戶端消息轉發的角色,降低代碼的耦合性)

/// <summary>
    /// 創建服務,得到client連接
    /// </summary>
    class Server
    {
        public Server()
        {

        }
        public Server(string ip,int port)
        {
            this.controllerManager = new ControllerManager(this);//初始化Controller控制器,這里只是一個簡單的架構,並沒有具體實現
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.ip = new IPEndPoint(IPAddress.Parse(ip), port);
            serverSocket.Bind(this.ip);
            serverSocket.Listen(0);
        }

        private Socket serverSocket;
        private IPEndPoint ip;
        private IPAddress port;
        private List<Client> client_list = new List<Client>();
        private ControllerManager controllerManager;
        private void Start()
        {
            serverSocket.BeginAccept(AcceptCallBack,null);//開始異步接收客戶端的連接
        }

        private void AcceptCallBack(IAsyncResult ar)
        {
            Socket client = serverSocket.EndAccept(ar);
            Client newclient = new Client(client, this);
            newclient.Start();
            client_list.Add(newclient);
            serverSocket.BeginAccept(AcceptCallBack, null);//繼續回調接收客戶端的請求
        }

        public void RemoveClient(Client client)
        {
            lock (client_list)
            {
                client_list.Remove(client);
            }
        }

        /// <summary>
        /// 給客戶端返回響應
        /// </summary>
        /// <param name="client">是哪一個客戶端應該得到響應</param>
        /// <param name="ret">響應體</param>
        public void SendResponse(Client client,string ret)
        {
            //給客戶端發送響應
            client.Send(ret);
        }

        public void RequestHandle(string data, Client client)
        {
            //通過server將消息發送給Controller進行處理
            controllerManager.HandleRequest(data, client);
        }
    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM