一、服務端
由於同步的方式在處理多客戶端處理時會出現多線程資源處理問題,所以在處理並發和並行問題時大多采用異步的形式。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); } }