今天的部分依旧是很详细。为何详细呢?是由于这个部分我仍是有点蒙蔽的,因此就要再一次将思路整理一下,看看到底讲了什么,也许在接下来的部分中我都会说的很详细,由于我很懵逼。数组
咱们在前面的部分是将个个部分都说了一遍,接下来就开始真正的项目开发。咱们在unity中建立新的项目,导入资源。服务器
也和昨天的图同样,开局一张图,其余全靠编。网络
这个图是咱们项目的整个框架,这个Gamefacde是一个中介者,是全部脚本的中转站。咱们下面要讲的也是按照这个图片上的。框架
首先建立GameFacade脚本。接着建立GameManager AudioManager PlayerManager RequestMangager ClinetMananger脚本。接着建立一个BaseMangaer,这个是全部manager的基类socket
protected GameFacade gameFacade; public virtual void OnInit() { }//初始化的方法 public virtual void OnDestroy() { }//当项目删除的时候调用 public BaseManager(GameFacade gameFacade) { this.gameFacade = gameFacade; }
接着让全部的manager都继承于BaseManager中并完成构造函数。下面说一个例子:ide
public AudioManager(GameFacade gameFacade) : base(gameFacade) { }
接着在GameFacade中将初始化方法,生命结束的方法都写好:函数
private static GameFacade _instance; public static GameFacade Instance { get { return _instance; } } //将全部的manager在这里声明,方便统一管理 private UIManager uiMng; private CameraManager cameraMng; private AudioManager audioMng; private PlayerManager playerMng; private RequestManager requestMng; private ClientManager clientMng; void Awake() { _instance = this; } //初始化方法 private void InitManager() { uiMng = new UIManager(this); cameraMng = new CameraManager(this); audioMng = new AudioManager(this); playerMng = new PlayerManager(this); requestMng = new RequestManager(this); clientMng = new ClientManager(this); uiMng.OnInit(); cameraMng.OnInit(); audioMng.OnInit(); playerMng.OnInit(); requestMng.OnInit(); clientMng.OnInit(); } //删除方法 private void OnDertroy() { uiMng.OnDestroy(); cameraMng.OnDestroy(); audioMng.OnDestroy(); playerMng.OnDestroy(); requestMng.OnDestroy(); clientMng.OnDestroy(); } // Use this for initialization void Start () { //在开始中调用初始化的方法 InitManager(); }
接着咱们开始肯定一下网络的部分:就是ClientManagerui
private const string IP = "127.0.0.1"; private const int PORT = 6608;
完成一下链接的基本操做:this
public ClientManager(GameFacade gameFacade) : base(gameFacade) { } //这个类是用做处理服务器对客户端的类的并进行管理 //设立两个常量,IP地址与端口号 private const string IP = "127.0.0.1"; private const int PORT = 6608; private Socket clientSocket;//服务器端为客户端单首创建的一个socket private Message msg = new Message();//获得message的成员变量 //初始化方法 public override void OnInit() { base.OnInit(); try { clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //再次开始接收数据 Start(); } catch (Exception e) { Console.WriteLine("没法链接到服务器,请重试:" + e); } } private 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); //而后进行解析 msg.ReadMessage(count, OnProcessDataCallBack); Start(); } catch (Exception e) { Console.WriteLine(e); } } private void OnProcessDataCallBack(ActionCode actionCode ,string data) { //接收来自message的方法完成一些操做 //接着就是一些处理响应的方法 gameFacade.HandleOnReqone(actionCode, data); } //删除的方法 public override void OnDestroy() { base.OnDestroy(); try { //关闭链接 clientSocket.Close(); OnInit();//再次接收下一个客服端的要求 } catch (Exception e) { Console.WriteLine("没法关闭与服务器的链接,请重试" + e); } }
上面这个说是前面已经讲过了。spa
接下来咱们换个说法,就是数据从客户端开始发送,而后到服务器端是怎样的过程。
客服向服务器请求链接,服务器赞成客户端的请求,并将建立一个socket负责专门与这个服务器进行数据接收,以下:
//初始化方法 public override void OnInit() { base.OnInit(); try { clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //再次开始接收数据 Start(); } catch (Exception e) { Console.WriteLine("没法链接到服务器,请重试:" + e); } }
这个就是客户端的一些基本设置,由于这个继承与basemanager因此这个会被执行的。图中的start是下面的代码:
private void Start() { //开始接收来自客户端的数据 clientSocket.BeginReceive(msg.Data, msg.StartIndex, msg.RemainSize, SocketFlags.None, ReceiveCallBack, null); }
这个服务器端专门用来与特定客户端链接的socket开始接收来自客户端的数据,就是上面的代码。上面的msg就是message,等下面再说。上面的ReceiveCallBack就是一个委托AsyncCallback ,接下来咱们说一说委托和回调函数,这两个比较重要的函数。先说回调
回调函数就是一个经过函数指针调用的函数。若是你把函数的指针(地址)做为参数传递给另外一个函数,当这个指针被用来调用其所指向的函数时,咱们就说这是回调函数。回调函数不是由该函数的实现直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
好了接下来就是委托,委托就是指针。在上面代码中调用这个ReceiveCallBack变量,咱们在前面写一个相同的方法:
private void ReceiveCallBack(IAsyncResult ar) { try { //获得从客户端传来的数据的个数 int count = clientSocket.EndReceive(ar); //而后进行解析 msg.ReadMessage(count, OnProcessDataCallBack); Start(); } catch (Exception e) { Console.WriteLine(e); } }
这个方法就是委托,咱们用这个变量就是调用这个方法,与C语言中的指针是相同的做用。当clientsocket完成对来自客户端的数据额接收的时候咱们首先获得传来的字节数组的个数,看看它到底有多少个。而后就是解析,由于不解析的话咱们是不能用的,传来的都是二进制。就经过msg.read message方法调用,就是message。以下图:clietmanager
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; } } //读数据讲过这个方法的解析,而后经过processDataCallBack,由于用的是委托,因此就会传给clientmanager中的OnProcessDataCallBack public void ReadMessage(int newDataAmount, Action<ActionCode, string> processDataCallBack) { startIndex += newDataAmount; while (true) { //若是数据长度不足4的话,会返回 if (startIndex <= 4) return; //获得传来数据的长度,由于toint32一次只会解析前四个字节,这样就知道了数组中还有几个数据 int count = BitConverter.ToInt32(data, 0); if ((startIndex - 4) >= count) { ActionCode actionCode = (ActionCode)BitConverter.ToInt32(data, 4); //索引减4就是真正的数据长度,就接受数据 string s = Encoding.UTF8.GetString(data, 8, count); Console.WriteLine("解析出来的数据为:" + s); //将数据进行更新 Array.Copy(data, count + 4, data, 0, startIndex - 4 - count); startIndex -= (count + 4); processDataCallBack(actionCode, s); } else { break; } } }
上面的代码就是解析数据的过程,它的方法中也是有一个委托的,当咱们将数据处理好了,就要返回这个委托processDataCallBack,而这个方法指向的就是下面的:
clientManager的:
private void OnProcessDataCallBack(ActionCode actionCode ,string data) { //接收来自message的方法完成一些操做 //接着就是一些处理响应的方法 gameFacade.HandleOnReqone(actionCode, data); }
会调用GameFacade的:
public void HandleOnReqone(ActionCode actionCode, string data) { requestMng.HandleOnReqone(actionCode, data); }
接着调用reqestmanagar,由于这些都是公共方法:
//处理request的方法 //处理响应的方法 public void HandleOnReqone(ActionCode actionCode, string data) { BaseRequest request = requestDict.TryGet<ActionCode, BaseRequest>(actionCode); if (request == null) { Debug.LogWarning("没法处理" + actionCode); return; } request.OnRespone(data);//进行响应的方法 }
接着咱们要写一些方法在requestmanager,由于这些都是基本方法:
public RequestManager(GameFacade gameFacade) : base(gameFacade) { } //在这里建立一个字典,用来管理全部的request 状态---基础类 private Dictionary<ActionCode, BaseRequest> requestDict = new Dictionary<ActionCode, BaseRequest>(); //添加request的方法 public void AddRequest(ActionCode actionCode, BaseRequest baseRequest) { requestDict.Add(actionCode, baseRequest); } //删除的方法 public void RemoveRequest(ActionCode actionCode) { requestDict.Remove(actionCode); }
由于gamefacade是中介者,其余的都是经过它来访问manager的因此:
//GameFrcade做为中介者 //在这里调用全部manager的方法 //添加request的方法 //在这里说明一下,由于处理以后就会返回,那么格式为数据长度:actioncode:数据 public void AddRequest(ActionCode actionCode, BaseRequest baseRequest) { requestMng.AddRequest(actionCode, baseRequest); } //删除request的方法 public void RemoveRequest(ActionCode actionCode) { requestMng.RemoveRequest(actionCode); }
接着咱们还要写一下requestmanager这个管理类:
//处理request的方法 //处理响应的方法 public void HandleOnReqone(ActionCode actionCode, string data) { BaseRequest request = requestDict.TryGet<ActionCode, BaseRequest>(actionCode); if (request == null) { Debug.LogWarning("没法处理" + actionCode); return; } request.OnRespone(data);//进行响应的方法 }
接着request.Onrespone进行处理,就是下面的图,可是这个是一个基类,在之后咱们会建立不少request的类,会调用他们的方法进行使用。
//获得状态 private RequestCode requestCode = RequestCode.None; private ActionCode actionCode = ActionCode.None; public virtual void Awake() { //将本身加入到requestmanager的中requestDcit中 GameFacade.Instance.AddRequest(actionCode, this); } public virtual void SendRequest() { }//发起请求 public virtual void OnRespone(string data) { }//进行响应 //当整个生命周期结束时自动调用 private void OnDestroy() { //将本身从requestmanager中的requestDict中删除 GameFacade.Instance.RemoveRequest(actionCode); }
最后忘记了咱们还有在client manager,写一个向服务器端发送数据的方法,数据先要变成二进制
//客户端向服务器端发送要求的函数 //在一些request类中进行调用 public void SendRequest(RequestCode requestCode, ActionCode actionCode, string data) { //获得字节数组 byte[] bytes = Message.PackData(requestCode, actionCode, data); //向socket发送字节数据 clientSocket.Send(bytes); }
接着是message中的方法:
//重载方法,这个是客户端向服务器端发送数据须要requestcode //格式:数据长度:requestcode:actioncode:数据 public static byte[] PackData(RequestCode requestData,ActionCode ActionData, string data) { byte[] requestCodeBytes = BitConverter.GetBytes((int)requestData); byte[] actionCodeBytes = BitConverter.GetBytes((int)ActionData); byte[] dataBtyes = Encoding.UTF8.GetBytes(data); int dataAmount = requestCodeBytes.Length + actionCodeBytes.Length + dataBtyes.Length; byte[] dataAmountBytes = BitConverter.GetBytes(dataAmount); return dataAmountBytes.Concat(requestCodeBytes).Concat(actionCodeBytes).Concat(dataBtyes).ToArray(); }
这个是一个重载方法,系统会匹配的,就是转换成二进制的方法。好了咱们今天的就结束了。