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类:

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类,用与基本配置。

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类:

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进行了调整,因为我们对服务器端与客户端之间的数据传输进行了一下的规定:

 从图中我们将左边的作为客户端,右边的作为服务器端。客户端向服务器端发送数据分为四个部分:数据长度: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的方法就可以向客户端发送数据。好了,写了这么多,以上就是数据接收的全部部分了。下一节继续。

猜你喜欢

转载自www.cnblogs.com/jake-caiee/p/9897293.html
今日推荐