今天咱们开始本案例的第三部分,由于这个部分很是重要,我将会讲的很是详细,是对前面的总结。数据库
开局一张图,其他全靠编。这个图片是客户端向服务器端发送数据的所有过程。接下来咱们将采用顺序结构。数组
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的方法就能够向客户端发送数据。好了,写了这么多,以上就是数据接收的所有部分了。下一节继续。