今天咱們開始本案例的第三部分,由於這個部分很是重要,我將會講的很是詳細,是對前面的總結。數據庫
開局一張圖,其他全靠編。這個圖片是客戶端向服務器端發送數據的所有過程。接下來咱們將採用順序結構。數組
1首先是服務器端的搭建。安全
咱們先要包服務器端的IP地址與端口號進行綁定,就是一些配置,以下圖。服務器
private IPEndPoint iPEndPoint;//端口號 private Socket serverSocket;服務器端
private List<Client> clientList; public Server() { } public Server(string ipStr,int port) { SetIpEndPoint(ipStr, port); } public void SetIpEndPoint(string ipStr,int port) { iPEndPoint = new IPEndPoint(IPAddress.Parse(ipStr), port);//將IP與端口號合在一塊兒組成一個新類 } //提供和服務器端的一些基本設置 public void Start() { serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); serverSocket.Bind(iPEndPoint);//綁定端口號 serverSocket.Listen(0); //開始接受由客戶端傳來的請求 serverSocket.BeginAccept(AcceptCallBack, null); } //開始接收數據的回調函數 private void AcceptCallBack(IAsyncResult ar) { //答應客戶端發來的請求,並建立一個socket來表示一個客戶端 Socket clientSocket = serverSocket.EndAccept(ar); //建立客戶端類,並設置一些client的初始化 Client client = new Client(clientSocket, this); client.Start(); clientList.Add(client); }
從圖中的BeginAccept()這個方法咱們知道就是一個異步方法,服務器端開始贊成來自客戶端的請求,而後AceeptCallBack這個方法就是BeginAccept的回調函數,它完成對應的操做並返回。在AccetpCallBack中,serversocket在贊成請求的時候會返回一個socket類,這個就就是服務器端爲它單首創建的socket,而後在單首創建一個client類,由於,會有不少的客服端,在server是不可能對全部的client都進行處理,全部就建立一個單獨的client來進行處理,來一個客戶端就單首創建一個client,這樣作就避免了耦合性。而後會client進行一些基本配置,並把它放在clientList中便於管理。下面是client類:mvc
2client鏈接異步
private Socket clientSocket; private Server server;
private MySqlConnection myConn; public Client() { } public Client(Socket clientSocket, Server server) { this.clientSocket = clientSocket; this.server = server; myConn = ConnHelper.Connet(); } public void Start() { //客戶端在獲得服務器贊成後,開始接受數據 clientSocket.BeginReceive(msg.Data, msg.StartIndex, 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); Start();//再次調用本身,爲了下一個的數據接收 } catch (Exception e) { Console.WriteLine(e); Close(); } } //結束方法 private void Close() { ConnHelper.CloseConnection(myConn); if (clientSocket != null) { clientSocket.Close(); server.ReduceClient(this); } }
一個client對應一個客戶端,全部它的基本配置也是必要的,在構造函數中就能夠完成了。咱們在上面發現了 myConn = ConnHelper.Connet();這句話,其實咱們應該知道這個是什麼意思。每個客服端都要建立一個單獨的client,每個client都要進行基本配置,這個基本配置都是相同的,爲了效率考慮,咱們就單獨寫一個類,在每次建立client的時候,就直接調用就好了,因此咱們建立一個ConnHelper類,用與基本配置。socket
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using MySql.Data.MySqlClient; namespace GameServer.Tool { public class ConnHelper { public const string CONNECTSTRING="datasource=127.0.0.1;port=3306;database=test002;user=root;pwd=root;" //鏈接數據庫函數 public static MySqlConnection Connet() { MySqlConnection conn = new MySqlConnection(CONNECTSTRING); try { conn.Open(); return conn; } catch (Exception e) { Console.WriteLine("鏈接數據庫出現異常" + e); return null; } } //斷開數據庫函數 public static void CloseConnection(MySqlConnection conn) { if (conn != null) { conn.Close(); } else { Console.WriteLine("MySqlConnection不存在"); } } } }
這個方法就不用多說了,就是一些基本配置。函數
咱們接着client類說,這個start()中的 clientSocket.BeginReceive(msg.Data, msg.StartIndex, msg.RemainSize, SocketFlags.None, ReceiveCallBack, null);由於client是server中的clientList中所擁有的,因此BeginReceive的真正做用是服務器端接收來自客戶端的數據,固然這個也是有回調函數的,就是ReceiveCallBack()。在這個方法中咱們首先endreceive完成數據的接收,而後獲得到底有多少個數據,在進行安全校驗後,咱們就開始讀取數據,就是這個方法 msg.ReadMessage(count,OnProcessMessage);這個msg究竟是什麼呢,就是message類,就是用於處理client的類,就下來咱們看看Message類:this
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Common; namespace GameServer.Severs { public class Message { private byte[] data = new byte[1024]; private int startIndex = 0;//開始索引 public byte[] Data { get { return data; } } public int StartIndex { get { return startIndex; } } //還剩餘什麼 public int RemainSize { get { return data.Length - startIndex; } } //讀數據 public void ReadMessage(int newDataAmount,Action<RequestCode,ActionCode,string>processDataCallBack) { startIndex += newDataAmount; while (true) { //若是數據長度不足4的話,會返回 if (startIndex <= 4) return; //獲得傳來數據的長度,由於toint32一次只會解析前四個字節,這樣就知道了數組中還有幾個數據 int count = BitConverter.ToInt32(data, 0); if ((startIndex - 4) >= count) { RequestCode requestCode = (RequestCode)BitConverter.ToInt32(data, 4); ActionCode actionCode = (ActionCode)BitConverter.ToInt32(data, 8); //索引減4就是真正的數據長度,就接受數據 string s = Encoding.UTF8.GetString(data, 12, count); Console.WriteLine("解析出來的數據爲:" + s); //將數據進行更新 Array.Copy(data, count + 4, data, 0, startIndex - 4 - count); startIndex -= (count + 4); processDataCallBack(requestCode, actionCode, s); } else { break; } } } //這個方式服務器端向客戶端發送數據 格式爲 數據長度 Actioncode 數據 //由於服務器端向客戶端發送的數據,客戶端是不用設置控制器的,因此用不到request code的 //這個方法是由client的send函數來調用的,而後再由sever的SendRespose()來完成的 //在客戶端向服務器端發起請求的時候,server會根據客戶端單首創建一個client用來處理 public static byte[] PackData(ActionCode ActionData, string data) { byte[] actionCodeBytes = BitConverter.GetBytes((int)ActionData); byte[] dataBtyes = Encoding.UTF8.GetBytes(data); int dataAmount = actionCodeBytes.Length + dataBtyes.Length; byte[] dataAmountBytes = BitConverter.GetBytes(dataAmount); return dataAmountBytes.Concat(actionCodeBytes).Concat(dataBtyes).ToArray(); } } }
這個message其實就是咱們前面的那個Message,咱們將ReadMessage進行了調整,由於咱們對服務器端與客戶端之間的數據傳輸進行了一下的規定:spa
從圖中咱們將左邊的做爲客戶端,右邊的做爲服務器端。客戶端向服務器端發送數據分爲四個部分:數據長度:RequestCode(這個是一個requestcode對應一個controller):ActionCode(用這個能夠找到方法):數據。從上面的read message中咱們能夠知道先獲得先獲得數據長度而後獲得request code而後獲得action code,從12開始就是數據的真正部分。 Array.Copy(data, count + 4, data, 0, startIndex - 4 - count);這個方法就是將沒有完成的數據的從新複製。
Action<RequestCode,ActionCode,string>processDataCallBack這個就是一個委託。咱們在client中寫 msg.ReadMessage(count,OnProcessMessage);這樣寫也就是說Action<RequestCode,ActionCode,string>processDataCallBack這個是指向OnProcessMessage的。在完成message中的 processDataCallBack(requestCode, actionCode, s);操做後,在client就獲得了。在client寫下這個方法:
private void OnProcessMessage(RequestCode requestCode, ActionCode actionCode, string data) { server.HandleRequest(requestCode, actionCode, data, this); }
message中的processDataCallBack(requestCode, actionCode, s);就與client中的 private void OnProcessMessage(RequestCode requestCode, ActionCode actionCode, string data)是同樣的了。
在上面的代碼中server.hanleRequest就是找到server中對應的方法了,如今仍是在客戶端向服務器端傳輸數據。
//這個方法是客戶端向服務器端發送數據 public void HandleRequest(RequestCode requestCode, ActionCode actionCode, string data, Client client) { controllerManager.HandleRequest(requestCode, actionCode, data, client); }
上面個的方法就是服務器端處理來自客戶端的數據。在這個裏面又有一個方法,這個是controllermanager中的方法。咱們從client中獲得的數據而後經過server向指定的controller找到方法進行處理。可是是要經過controller manager來完成的,這樣作的方式是爲了不耦合。傳來的數據有數據長度:request code:action code:數據。而後去找controllermanager.HandleRequest的方法。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Common; using System.Reflection; using GameServer.Severs; namespace GameServer.Controller { public class ControllerManager { //字典用來管理全部的controller private Dictionary<RequestCode, BaseController> controlDict = new Dictionary<RequestCode, BaseController>(); private Server server; //構造函數 public ControllerManager(Server server) { this.server = server; //這個是構造方法,在Server構造的時候,本構造方法也會構造,初始化方法也會執行。 InitController(); } //sever會根據客戶端建立一個單獨的controller,controllermangager的做用就是把controller與server進行交互 //以免耦合 void InitController() { DefaultController defaultController = new DefaultController(); //一個狀態對應一個control這個有點像mvc controlDict.Add(defaultController.RequestCode, defaultController); } public void HandleRequest(RequestCode requestCode, ActionCode actionCode, string data,Client client) { //開始處理數據 由requestcode來找controller //RequestCode 對應一個controller BaseController controller; bool isGet = controlDict.TryGetValue(requestCode, out controller);//獲得狀態對應的控制器 if (isGet == false) { Console.WriteLine("沒法獲得[" + requestCode + "]對應的控制器"); return; } //由action code來找控制器對應的方法 //經過action來找到名字 string methodName = Enum.GetName(typeof(ActionCode), actionCode); MethodInfo mi = controller.GetType().GetMethod(methodName); if (mi == null) { Console.WriteLine("警告在" + controller.GetType() + "controller中沒有對應的處理方法" + methodName); return; } object[] parameters = new object[] { data,client,server }; object o = mi.Invoke(controller, parameters); if (o == null || string.IsNullOrEmpty(o as string)) { return; } server.SendRespose(client, actionCode, data); } } }
這個就是controllermanager中的代碼。HandleRequest這個就是處理數據傳來的數據。這個方法就是將requsetcode和actioncode進行判斷,看看是否正確。
object[] parameters = new object[] { data,client,server };
object o = mi.Invoke(controller, parameters);
if (o == null || string.IsNullOrEmpty(o as string))
{
return;
}
這個部分咱們來看看,mi.Invoke()這個方法就是能夠調用特定controller的方法了。而後就能夠然會數據了。 咱們還要有controller。basecontroller就是一個基類,defaultccontroller就是一個默認的controller並繼承與basecontroller。 server.SendRespose(client, actionCode, data);這個方法就是server向客戶端返回數據了,就是數據長度:actioncode:data。去找sever中的方法。
//這個方法是服務器端將數據發送到客戶端 //與這個方法交互的是Client Message ConnHelper public void SendRespose(Client client, ActionCode actionCode,string data) { client.Send(actionCode, data); }
而後去找client中的方法:
public void Send(ActionCode actionCode, string data) { byte[] bytes = Message.PackData(actionCode, data); clientSocket.Send(bytes); }
而後去找PackData方法:
//這個方式服務器端向客戶端發送數據 格式爲 數據長度 Actioncode 數據 //由於服務器端向客戶端發送的數據,客戶端是不用設置控制器的,因此用不到request code的 //這個方法是由client的send函數來調用的,而後再由sever的SendRespose()來完成的 //在客戶端向服務器端發起請求的時候,server會根據客戶端單首創建一個client用來處理 public static byte[] PackData(ActionCode ActionData, string data) { byte[] actionCodeBytes = BitConverter.GetBytes((int)ActionData); byte[] dataBtyes = Encoding.UTF8.GetBytes(data); int dataAmount = actionCodeBytes.Length + dataBtyes.Length; byte[] dataAmountBytes = BitConverter.GetBytes(dataAmount); return dataAmountBytes.Concat(actionCodeBytes).Concat(dataBtyes).ToArray(); }
因爲上面的方法上面已經說明了,咱們在這裏就不說了。接着就能夠 clientSocket.Send(bytes);這樣就是整個數據接收的過程了。
在最後咱們在進行一下總結一下。有客戶端向服務器端發送數據 clientSocket.BeginReceive。而後server就會建立一個client,而後在其中寫一些方法,隨後將經過clientsockets.send(byte),將數據發出。在服務器端會有一個clientsocket類,這個就是服務器端與客戶端傳輸的媒介,用send的方法就能夠向客戶端發送數據。好了,寫了這麼多,以上就是數據接收的所有部分了。下一節繼續。