021-直接利用Socket/TCP開發網絡遊戲四

今天的部分依舊是很詳細。爲何詳細呢?是由於這個部分我仍是有點矇蔽的,因此就要再一次將思路整理一下,看看到底講了什麼,也許在接下來的部分中我都會說的很詳細,由於我很懵逼。數組

咱們在前面的部分是將個個部分都說了一遍,接下來就開始真正的項目開發。咱們在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();
    }

這個是一個重載方法,系統會匹配的,就是轉換成二進制的方法。好了咱們今天的就結束了。

相關文章
相關標籤/搜索