020-直接利用Socket/TCP開發網絡遊戲三

今天咱們開始本案例的第三部分,由於這個部分很是重要,我將會講的很是詳細,是對前面的總結。數據庫

開局一張圖,其他全靠編。這個圖片是客戶端向服務器端發送數據的所有過程。接下來咱們將採用順序結構。數組

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的方法就能夠向客戶端發送數據。好了,寫了這麼多,以上就是數據接收的所有部分了。下一節繼續。

相關文章
相關標籤/搜索