直接用Socket TCP开发网络游戏(二)

01,如何解决SQL的注入问题

用这种方法的话就不会出现那种恶意输入的情况,代码实现如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MySql.Data.MySqlClient;
namespace MySql数据库操作
{
    class Program
    {
        static void Main(string[] args)
        {
            string connStr = "Database=test007;Data Source=127.0.0.1;port=3306;User Id=root;Password=root";

            MySqlConnection conn = new MySqlConnection(connStr);

            conn.Open();
            #region 查询

            #region 读取多条
            //MySqlCommand cmd = new MySqlCommand("select * from user ", conn);

            #endregion
            #region 读取一条
            //  MySqlCommand cmd = new MySqlCommand("select * from user where id = 1", conn);

            #endregion

            //MySqlDataReader reader = cmd.ExecuteReader();

            #region 读取一条
            // if (reader.HasRows) //得到一个值指示是否MySqlDataReader包含一个或多个行。
            // {
            //reader.Read(); //表示读取一条消息,多次读取多次调用
            //string username = reader.GetString("username");
            //string passwore = reader.GetString("password");
            //Console.WriteLine(username + " " + passwore);

            //  }
            #endregion
            #region 读取多条
            //while (reader.Read())
            //{
            //    string username = reader.GetString("username");
            //    string passwore = reader.GetString("password");
            //    Console.WriteLine(username + " " + passwore);
            //}

            #endregion


            //reader.Close();

            #endregion
            #region 插入
            string username = "zain"; string password = "lcker; delete form user";  ///用户组拼 有bug 用户可以恶意输入sql语句 下节课再解决

            MySqlCommand cmd = new MySqlCommand("insert into user set username=@un, password=@pwd", conn);

            cmd.Parameters.AddWithValue("un", username);  //添加参数 。。根据字符查找 cmd中的 字符 并替换成vaule
            cmd.Parameters.AddWithValue("pwd", password);

            cmd.ExecuteNonQuery();//执行插入  并返回插入的行数
           
            #endregion
            conn.Close();
            Console.ReadKey();
        }
    }
}

02,数据库数据的更新和删除


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MySql.Data.MySqlClient;
namespace MySql数据库操作
{
    class Program
    {
        static void Main(string[] args)
        {
            string connStr = "Database=test007;Data Source=127.0.0.1;port=3306;User Id=root;Password=root";

            MySqlConnection conn = new MySqlConnection(connStr);

            conn.Open();
            #region 查询

            #region 读取多条
            //MySqlCommand cmd = new MySqlCommand("select * from user ", conn);

            #endregion
            #region 读取一条
            //  MySqlCommand cmd = new MySqlCommand("select * from user where id = 1", conn);

            #endregion

            //MySqlDataReader reader = cmd.ExecuteReader();

            #region 读取一条
            // if (reader.HasRows) //得到一个值指示是否MySqlDataReader包含一个或多个行。
            // {
            //reader.Read(); //表示读取一条消息,多次读取多次调用
            //string username = reader.GetString("username");
            //string passwore = reader.GetString("password");
            //Console.WriteLine(username + " " + passwore);

            //  }
            #endregion
            #region 读取多条
            //while (reader.Read())
            //{
            //    string username = reader.GetString("username");
            //    string passwore = reader.GetString("password");
            //    Console.WriteLine(username + " " + passwore);
            //}

            #endregion


            //reader.Close();

            #endregion
            #region 插入
            //string username = "zain"; string password = "lcker; delete form user";  ///用户组拼 有bug 用户可以恶意输入sql语句 下节课再解决

            //MySqlCommand cmd = new MySqlCommand("insert into user set username=@un, password=@pwd", conn);

            //cmd.Parameters.AddWithValue("un", username);  //根据字符查找 cmd中的 字符 并替换成vaule
            //cmd.Parameters.AddWithValue("pwd", password);

            //cmd.ExecuteNonQuery();//执行插入  并返回插入的行数
            ////Console.WriteLine(cmd.ExecuteNonQuery()); 
            #endregion

            #region 删除

            //MySqlCommand cmd = new MySqlCommand("delete from user where id =@id", conn);
            //cmd.Parameters.AddWithValue("id", 2);

            //cmd.ExecuteNonQuery(); //执行


            #endregion
            #region 更新

            MySqlCommand cmd = new MySqlCommand("update  user set password=@pwd where id=3", conn);
            cmd.Parameters.AddWithValue("pwd", "a");

            cmd.ExecuteNonQuery(); //执行


            #endregion
            conn.Close();
            Console.ReadKey();
        }
    }
}

03,服务器端分成架构

    Server :             用来创建Socket 监听客户端的连接
    ConnHelper:  工具类 用来连接数据库 建立MySqlConnection
    Controller:      处理客户端的请求,客户端的请求发送到server端 ,server会调用相应的controller进行处理
    Model:            是和数据库中的表是对应着的  一个Model类对应一个数据库表
    DAO :            用来操作数据库的

04,学习小提示和项目的目录结构的创建

架构的话大概就是这样

05,创建Server类 开启接收客户端的连接

代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;

namespace GameServer.Server
{
    class Server
    {
        private IPEndPoint ipEndPoint;

        private Socket serverSocket;
        public Server()
        {

        }
        public Server(string IpStr, int port)
        {
            SetIpAndPort(IpStr, port);
        }
        public void  SetIpAndPort(string IpStr, int port)
        {
            ipEndPoint = new IPEndPoint(IPAddress.Parse(IpStr), port);
        }
        public void Start()
        {
             serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(ipEndPoint);
            serverSocket.Listen(0);
            serverSocket.BeginAccept(AcceptCallBack, null);
        }
        
        public void AcceptCallBack(IAsyncResult ar)
        {
            Socket clientSocket = serverSocket.EndAccept(ar);



        }

    }
}

06,创建Client类 处理跟客户端的数据通信

准备用message做桥梁: 创建客户端 服务器也需要改一下代码

客户端代码实现如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace GameServer.Server
{
    class Client
    {
        private Socket clientSocket;
        private Server server;

        public Client()
        {
        }
        public Client(Socket clientSocket, Server server)
        {
            this.clientSocket = clientSocket;
            this.server = server;
        }
        public void Start()
        {
            clientSocket.BeginReceive(null, 0, 0,SocketFlags.None,AsyncCallback,null);

        }
        public void AsyncCallback(IAsyncResult ar){
            try
            {
                int count = clientSocket.EndReceive(ar);

                if (count == 0)
                {
                    Close();
                }
                clientSocket.BeginReceive(null, 0, 0, SocketFlags.None, AsyncCallback, null);


                //TODO 处理接收到的数据
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Close();
            }
            
        }

        private void Close()
        {
            if (clientSocket!=null)
            
            clientSocket.Close();

            server.RemoveClient(this);
        }
    }
}

服务器代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;

namespace GameServer.Server
{
    class Server
    {
        private IPEndPoint ipEndPoint;

        private Socket serverSocket;

        private List<Client> clientList;

        public Server()
        {

        }
        public Server(string IpStr, int port)
        {
            SetIpAndPort(IpStr, port);
        }
        public void  SetIpAndPort(string IpStr, int port)
        {
            ipEndPoint = new IPEndPoint(IPAddress.Parse(IpStr), port);
        }
        public void Start()
        {
             serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(ipEndPoint);
            serverSocket.Listen(0);
            serverSocket.BeginAccept(AcceptCallBack, null);
        }
        
        public void AcceptCallBack(IAsyncResult ar)
        {
            Socket clientSocket = serverSocket.EndAccept(ar);

            Client client = new Client(clientSocket,this);
            client.Start();
            clientList.Add(client);//添加到list集合

        }
        public void RemoveClient(Client client)
        {
            lock (clientList) //锁定移除
            {
                clientList.Remove(client);
            }
           
        }
    }
}

07,创建Message处理客户端的消息解析;

Message类代码实现如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GameServer.Server
{
    class Message
    {
        private byte[] data = new byte[1024]; //这个数组用来存取我们读取到的数据,让消息的长度最少能存的下1024 ,如果说最大的消息数组存不下的话 就没办法完整的读取这条消息了
        //如果消息不完整 我们不处理。我们会读取下一条
        private int startIndex = 0;//标识位,表示现在数据存储在什么位置了 ,如果没数据 sartIndex从0开始,如果存了10个之后 这个startIndex就等于10;
                                   //再来数据的话就从10开始存,同时也代表了存了多少个字节的数据。

        /// <summary>
        /// 这个数组用来存取我们读取到的数据
        /// </summary>
        public byte[] Data
        {
            get { return data; }
        }
        /// <summary>
        /// 代表存储了多少个字节的数据  开始索引
        /// </summary>
        public int StartIndex
        {
            get { return startIndex; }
        }
        /// <summary>
        /// 剩余的空间
        /// </summary>
        public int RemaniSize
        {
            get { return data.Length - startIndex; }
        }
        /// <summary>
        /// 更新了多少数据
        /// </summary>
        /// <param name="count"></param>
        //public void AddCount(int count)
        //{
        //    startIndex += count;
        //}
        /// <summary>
        /// 解析数据或者叫做读取数据  newDataAmount数量
        /// </summary>
        public void ReadMessage(int newDataAmount)
        {
            startIndex += newDataAmount;


            while (true)
            {
                if (startIndex <= 4) return;
                int count = BitConverter.ToInt32(data, 0);//这个只占前四个字节  ,变成int 就是数据长度
                if ((startIndex - 4) >= count) //(startIndex-4)是剩余数据的长度 ,大于count 就说明数据是完整的
                {
                    string s = Encoding.UTF8.GetString(data, 4, count);
                    Console.WriteLine("解析出来一条数据 :" + s);
                    Array.Copy(data, count + 4, data, 0, startIndex - 4 - count);

                    startIndex -= (count + 4); //移动完了之后更新startIndex
                }
                else
                {
                    break;
                }
            }

          
        }

      
    }
}

Client类代码稍微做了修改:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace GameServer.Server
{
    class Client
    {
        private Socket clientSocket;
        private Server server;
        private Message messgae = new Message();
        public Client()
        {
        }
        public Client(Socket clientSocket, Server server)
        {
            this.clientSocket = clientSocket;
            this.server = server;
        }
        public void Start()
        {
            clientSocket.BeginReceive(messgae.Data, messgae.StartIndex, messgae.RemaniSize,SocketFlags.None,AsyncCallback,null);

        }
        public void AsyncCallback(IAsyncResult ar){
            try
            {
                int count = clientSocket.EndReceive(ar);

                if (count == 0)
                {
                    Close();
                }
                messgae.ReadMessage(count);

                Start();


                //TODO 处理接收到的数据
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Close();
            }

        }

        private void Close()
        {
            if (clientSocket!=null)
            
            clientSocket.Close();

            server.RemoveClient(this);
        }
    }
}

服务器类没做变化:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;

namespace GameServer.Server
{
    class Server
    {
        private IPEndPoint ipEndPoint;

        private Socket serverSocket;

        private List<Client> clientList;

        public Server()
        {

        }
        public Server(string IpStr, int port)
        {
            SetIpAndPort(IpStr, port);
        }
        public void  SetIpAndPort(string IpStr, int port)
        {
            ipEndPoint = new IPEndPoint(IPAddress.Parse(IpStr), port);
        }

        public void Start()
        {
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(ipEndPoint);
            serverSocket.Listen(0);
            serverSocket.BeginAccept(AcceptCallBack, null);
        }
        
        public void AcceptCallBack(IAsyncResult ar)
        {
            Socket clientSocket = serverSocket.EndAccept(ar); //

            Client client = new Client(clientSocket,this);
            client.Start();
            clientList.Add(client); //添加到list集合

        }
        public void RemoveClient(Client client)
        {
            lock (clientList) //锁定移除
            {
                clientList.Remove(client);
            }
           
        }
    }
}

08,开发Controller控制层

代码如下:


然后 再新建一个类库 Common 新建两个类 每个类都改为枚举类型 分别为ActionCode,RequesCode。

using System;
using System.Collections.Generic;
using System.Text;

namespace Common
{
    public enum ActionCode
    {
        None,
    }
}

using System;
using System.Collections.Generic;
using System.Text;
namespace Common
{
    public  enum RequesCode
    {
        None,
    }
}

在Controller文件夹下创建一个新的类 抽象类 BaseController   把刚才的类库添加引用到BaseController,和命名空间

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
namespace GameServer.Controller
{
    abstract  class BaseController
    {
        RequesCode requesCode = RequesCode.None;


        public virtual void DefaultHandle() { }


    }
}

09,客户端和服务器端的请求发起处理流程


10,创建ControllerMessager管理所有的控制器


Clinet 直接和controllerManager直接打交道的话 耦合性太高,所以要通过中介(Server)来打交道;ControllerManager只让Server使用,其他都跟server交互;server就相当于一个中介

 服务器端要持有一个ControllerMessager 

ControllerManager 代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;

namespace GameServer.Controller
{
    /// <summary>
    /// 用来管理当前服务器端有哪些COntroller
    /// </summary>
    class ControllerManager
    {
        private Dictionary<RequesCode, BaseController> controllerDic = new Dictionary<RequesCode, BaseController>();


        public ControllerManager()
        {
            Init();
        }

        /// <summary>
        /// 初始化所有的Controller
        /// </summary>
        void Init()
        {

        }

    }
}

 


Server服务器代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using GameServer.Controller;

namespace GameServer.Server
{
    class Server
    {
        private IPEndPoint ipEndPoint;

        private Socket serverSocket;

        private List<Client> clientList;

        private ControllerManager controllerManager = new ControllerManager();

        public Server()
        {

        }
        public Server(string IpStr, int port)
        {
            SetIpAndPort(IpStr, port);
        }
        public void  SetIpAndPort(string IpStr, int port)
        {
            ipEndPoint = new IPEndPoint(IPAddress.Parse(IpStr), port);
        }

        public void Start()
        {
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(ipEndPoint);
            serverSocket.Listen(0);
            serverSocket.BeginAccept(AcceptCallBack, null);
        }
        
        public void AcceptCallBack(IAsyncResult ar)
        {
            Socket clientSocket = serverSocket.EndAccept(ar); //

            Client client = new Client(clientSocket,this);
            client.Start();
            clientList.Add(client); //添加到list集合

        }
        public void RemoveClient(Client client)
        {
            lock (clientList) //锁定移除
            {
                clientList.Remove(client);
            }
           
        }
    }
}


11,通过ControllerManager进行请求的分发处理


先完善BaseController :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
namespace GameServer.Controller
{
    abstract  class BaseController
    {
        RequesCode requesCode = RequesCode.None;


        public RequesCode RequestCode
        {
            get { return requesCode; }
        }


        /// <summary>
        /// 用来处理默认的请求 当我们没有指定ActionCode的时候会执行
        /// </summary>data 是发送的数据
        public virtual string  DefaultHandle(string data) { return null;  }


    }
}

修改ControllerManager的代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
using System.Reflection;
namespace GameServer.Controller
{
    /// <summary>
    /// 用来管理当前服务器端有哪些COntroller
    /// </summary>
    class ControllerManager
    {
        private Dictionary<RequesCode, BaseController> controllerDic = new Dictionary<RequesCode, BaseController>();


        public ControllerManager()
        {
            Init();
        }

        /// <summary>
        /// 初始化所有的Controller
        /// </summary>
        void Init()
        {
            //TODO
            DefaultController defaultController = new DefaultController();
            controllerDic.Add(defaultController.RequestCode, defaultController);

        }
        /// <summary>
        /// 处理请求
        /// </summary> 通过requesCode 找到controller ,然后再actionCode 找到里面的方法
        public void HandelRequset(RequesCode requesCode,ActionCode actionCode,string data)
        {
            BaseController controller;
       bool isGet=     controllerDic.TryGetValue(requesCode, out controller);

            if (isGet==false)
            {
                Console.WriteLine("无法得到"+requesCode+"所对应的Controller,无法处理请求");return;

            }
            string methodName = Enum.GetName(typeof(ActionCode), actionCode);

           MethodInfo mi =    controller.GetType().GetMethod(methodName);

            if (mi==null)
            {
                Console.WriteLine("[警告]在controller]"+controller.GetType()+"]中没有对应的处理方法"); return;
            }
            object[] parameters = new object[] {data };
            ///o是是否要给客户端相应  
            object o=   mi.Invoke(controller, parameters); //调用controller方法 带data参数


        }

    }
}

在controller文件夹新建DefaultController类:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GameServer.Controller
{
    class DefaultController:BaseController  // 当RequesCode没有指定的时候调用
    {
    }
}

结构图如下图

12,客户端请求相应的处理:

修改Server类 实例化出来一个ControllerManager:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using GameServer.Controller;
using Common;

namespace GameServer.Servers
{
    class Server
    {
        private IPEndPoint ipEndPoint;

        private Socket serverSocket;

        private List<Client> clientList;

        private ControllerManager controllerManager;

        public Server()
        {

        }
        public Server(string IpStr, int port)
        {
            controllerManager = new ControllerManager(this);
            SetIpAndPort(IpStr, port);
        }
        public void  SetIpAndPort(string IpStr, int port)
        {
            ipEndPoint = new IPEndPoint(IPAddress.Parse(IpStr), port);
        }

        public void Start()
        {
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(ipEndPoint);
            serverSocket.Listen(0);
            serverSocket.BeginAccept(AcceptCallBack, null);
        }
        
        public void AcceptCallBack(IAsyncResult ar)
        {
            Socket clientSocket = serverSocket.EndAccept(ar); //

            Client client = new Client(clientSocket,this);
            client.Start();
            clientList.Add(client); //添加到list集合

        }
        public void RemoveClient(Client client)
        {
            lock (clientList) //锁定移除
            {
                clientList.Remove(client);
            }
           
        }
        /// <summary>
        /// 给Clinet客户端发起相应
        /// </summary>
        /// <param name="clinet"></param>
        /// <param name="requestCode"></param>
        /// <param name="data"></param>
        public  void SentResponse(Client clinet,RequesCode requestCode,string data)
        {
            //TODO给客户端相应
        }
    }
}

Message类不做任何变化:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GameServer.Servers
{
    class Message
    {
        private byte[] data = new byte[1024]; //这个数组用来存取我们读取到的数据,让消息的长度最少能存的下1024 ,如果说最大的消息数组存不下的话 就没办法完整的读取这条消息了
        //如果消息不完整 我们不处理。我们会读取下一条
        private int startIndex = 0;//标识位,表示现在数据存储在什么位置了 ,如果没数据 sartIndex从0开始,如果存了10个之后 这个startIndex就等于10;
                                   //再来数据的话就从10开始存,同时也代表了存了多少个字节的数据。

        /// <summary>
        /// 这个数组用来存取我们读取到的数据
        /// </summary>
        public byte[] Data
        {
            get { return data; }
        }
        /// <summary>
        /// 代表存储了多少个字节的数据  开始索引
        /// </summary>
        public int StartIndex
        {
            get { return startIndex; }
        }
        /// <summary>
        /// 剩余的空间
        /// </summary>
        public int RemaniSize
        {
            get { return data.Length - startIndex; }
        }
        /// <summary>
        /// 更新了多少数据
        /// </summary>
        /// <param name="count"></param>
        //public void AddCount(int count)
        //{
        //    startIndex += count;
        //}
        /// <summary>
        /// 解析数据或者叫做读取数据  newDataAmount数量
        /// </summary>
        public void ReadMessage(int newDataAmount)
        {
            startIndex += newDataAmount;


            while (true)
            {
                if (startIndex <= 4) return;
                int count = BitConverter.ToInt32(data, 0);//这个只占前四个字节  ,变成int 就是数据长度
                if ((startIndex - 4) >= count) //(startIndex-4)是剩余数据的长度 ,大于count 就说明数据是完整的
                {
                    string s = Encoding.UTF8.GetString(data, 4, count);
                    Console.WriteLine("解析出来一条数据 :" + s);
                    Array.Copy(data, count + 4, data, 0, startIndex - 4 - count);

                    startIndex -= (count + 4); //移动完了之后更新startIndex
                }
                else
                {
                    break;
                }
            }

          
        }

      
    }
}

client类不做变化:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace GameServer.Servers
{
    class Client
    {
        private Socket clientSocket;
        private Server server;
        private Message messgae = new Message();
        public Client()
        {
        }
        public Client(Socket clientSocket, Server server)
        {
            this.clientSocket = clientSocket;
            this.server = server;
        }
        public void Start()
        {
            clientSocket.BeginReceive(messgae.Data, messgae.StartIndex, messgae.RemaniSize,SocketFlags.None,AsyncCallback,null);

        }
        public void AsyncCallback(IAsyncResult ar){
            try
            {
                int count = clientSocket.EndReceive(ar);

                if (count == 0)
                {
                    Close();
                }
                messgae.ReadMessage(count);

                Start();


                //TODO 处理接收到的数据
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Close();
            }

        }

        private void Close()
        {
            if (clientSocket!=null)
            
            clientSocket.Close();

            server.RemoveClient(this);
        }
    }
}

DefaultController类没变化:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace GameServer.Controller
{
    class DefaultController:BaseController  // 当RequesCode没有指定的时候调用
    {
    }
}

ControllerManager类变化:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
using System.Reflection;
using GameServer.Servers;
namespace GameServer.Controller
{
    /// <summary>
    /// 用来管理当前服务器端有哪些COntroller
    /// </summary>
    class ControllerManager
    {
        private Dictionary<RequesCode, BaseController> controllerDic = new Dictionary<RequesCode, BaseController>();

        private Server server;

        public ControllerManager(Server server)
        {
            this.server = server;
            InitController();
        }

        /// <summary>
        /// 初始化所有的Controller
        /// </summary>
        void InitController()
        {
            //TODO
            DefaultController defaultController = new DefaultController();
            controllerDic.Add(defaultController.RequestCode, defaultController);

        }
        /// <summary>
        /// 处理请求
        /// </summary> 通过requesCode 找到controller ,然后再actionCode 找到里面的方法
        public void HandelRequset(RequesCode requesCode,ActionCode actionCode,string data ,Client client)
        {
            BaseController controller;
       bool isGet=     controllerDic.TryGetValue(requesCode, out controller);

            if (isGet==false)
            {
                Console.WriteLine("无法得到"+requesCode+"所对应的Controller,无法处理请求");return;

            }
            string methodName = Enum.GetName(typeof(ActionCode), actionCode);

           MethodInfo mi =    controller.GetType().GetMethod(methodName);

            if (mi==null)
            {
                Console.WriteLine("[警告]在controller]"+controller.GetType()+"]中没有对应的处理方法"); return;
            }
            object[] parameters = new object[] {data,client,server };
            ///o是是否要给客户端相应  
            object o=   mi.Invoke(controller, parameters); //调用controller方法 带data参数
            if (o==null||string.IsNullOrEmpty(o as string))
            {
                return;
            }
            server.SentResponse(client, requesCode, o as string);
        }

    }
}

BaseController类也做了一些修改:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
using GameServer.Servers;

namespace GameServer.Controller
{
    abstract  class BaseController
    {
        RequesCode requesCode = RequesCode.None;


        public RequesCode RequestCode
        {
            get { return requesCode; }
        }


        /// <summary>
        /// 用来处理默认的请求 当我们没有指定ActionCode的时候会执行
        /// </summary>data 是发送的数据
        public virtual string  DefaultHandle(string data,Client clinet ,Server server) { return null;  }


    }
}

Common类库也没变化 :

using System;
using System.Collections.Generic;
using System.Text;
namespace Common
{
    public  enum RequesCode
    {
        None,
    }
}

using System;
using System.Collections.Generic;
using System.Text;

namespace Common
{
    public enum ActionCode
    {
        None,
    }

}

13,如何把客户端消息的解析和传递给ControllerManager进行处理

修改Message类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
namespace GameServer.Servers
{
    class Message
    {
        private byte[] data = new byte[1024]; //这个数组用来存取我们读取到的数据,让消息的长度最少能存的下1024 ,如果说最大的消息数组存不下的话 就没办法完整的读取这条消息了
        //如果消息不完整 我们不处理。我们会读取下一条
        private int startIndex = 0;//标识位,表示现在数据存储在什么位置了 ,如果没数据 sartIndex从0开始,如果存了10个之后 这个startIndex就等于10;
                                   //再来数据的话就从10开始存,同时也代表了存了多少个字节的数据。

        /// <summary>
        /// 这个数组用来存取我们读取到的数据
        /// </summary>
        public byte[] Data
        {
            get { return data; }
        }
        /// <summary>
        /// 代表存储了多少个字节的数据  开始索引
        /// </summary>
        public int StartIndex
        {
            get { return startIndex; }
        }
        /// <summary>
        /// 剩余的空间
        /// </summary>
        public int RemaniSize
        {
            get { return data.Length - startIndex; }
        }
        /// <summary>
        /// 更新了多少数据
        /// </summary>
        /// <param name="count"></param>
        //public void AddCount(int count)
        //{
        //    startIndex += count;
        //}
        /// <summary>
        /// 解析数据或者叫做读取数据  newDataAmount数量
        /// </summary>
        public void ReadMessage(int newDataAmount,Action<RequesCode,ActionCode,string> processDataCallback)
        {
            startIndex += newDataAmount;


            while (true)
            {
                if (startIndex <= 4) return;
                int count = BitConverter.ToInt32(data, 0);//这个只占前四个字节  ,变成int 就是数据长度
                if ((startIndex - 4) >= count) //(startIndex-4)是剩余数据的长度 ,大于count 就说明数据是完整的
                {
                    //string s = Encoding.UTF8.GetString(data, 4, count);
                    //Console.WriteLine("解析出来一条数据 :" + s);
                    RequesCode requestCode=(RequesCode)   BitConverter.ToInt32(data, 4); ///解析出来的是ResourceCode
                    ActionCode actionCode = (ActionCode)BitConverter.ToInt32(data, 8); ///解析出来的是ActionCode
                    string s = Encoding.UTF8.GetString(data, 12,count-8);

                    processDataCallback(requestCode, actionCode, s);

                    Array.Copy(data, count + 4, data, 0, startIndex - 4 - count);

                    startIndex -= (count + 4); //移动完了之后更新startIndex
                }
                else
                {
                    break;
                }
            }

          
        }

      
    }
}
通过委托回调给Clinet,client回调给Server,server再回调给ControllerManager
Clinet:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using Common;

namespace GameServer.Servers
{
    class Client
    {
        private Socket clientSocket;
        private Server server;
        private Message messgae = new Message();
        public Client()
        {
        }
        public Client(Socket clientSocket, Server server)
        {
            this.clientSocket = clientSocket;
            this.server = server;
        }
        public void Start()
        {
            clientSocket.BeginReceive(messgae.Data, messgae.StartIndex, messgae.RemaniSize,SocketFlags.None,AsyncCallback,null);

        }
        public void AsyncCallback(IAsyncResult ar){
            try
            {
                int count = clientSocket.EndReceive(ar);

                if (count == 0)
                {
                    Close();
                }

                messgae.ReadMessage(count,OnProcessMessage);

                Start();


                //TODO 处理接收到的数据
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Close();
            }

        }

        private void Close()
        {
            if (clientSocket!=null)
            
            clientSocket.Close();

            server.RemoveClient(this);
        }

        private void OnProcessMessage(RequesCode requesCode, ActionCode actionCode, string data)
        {
            server.HandleRequest(requesCode, actionCode, data, this);
        }
    }
}
Server:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using GameServer.Controller;
using Common;

namespace GameServer.Servers
{
    class Server
    {
        private IPEndPoint ipEndPoint;

        private Socket serverSocket;

        private List<Client> clientList;

        private ControllerManager controllerManager;

        public Server()
        {

        }
        public Server(string IpStr, int port)
        {
            controllerManager = new ControllerManager(this);
            SetIpAndPort(IpStr, port);
        }
        public void  SetIpAndPort(string IpStr, int port)
        {
            ipEndPoint = new IPEndPoint(IPAddress.Parse(IpStr), port);
        }

        public void Start()
        {
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(ipEndPoint);
            serverSocket.Listen(0);
            serverSocket.BeginAccept(AcceptCallBack, null);
        }
        
        public void AcceptCallBack(IAsyncResult ar)
        {
            Socket clientSocket = serverSocket.EndAccept(ar); //

            Client client = new Client(clientSocket,this);
            client.Start();
            clientList.Add(client); //添加到list集合

        }
        public void RemoveClient(Client client)
        {
            lock (clientList) //锁定移除
            {
                clientList.Remove(client);
            }
           
        }
        /// <summary>
        /// 给Clinet客户端发起相应
        /// </summary>
        /// <param name="clinet"></param>
        /// <param name="requestCode"></param>
        /// <param name="data"></param>
        public  void SentResponse(Client clinet,RequesCode requestCode,string data)
        {
            //TODO给客户端相应
        }

        /// <summary>
        /// Manager跟server交互 server跟Clinet交互
        /// </summary>
        /// <param name="requesCode"></param>
        /// <param name="actionCode"></param>
        /// <param name="data"></param>
        /// <param name="client"></param>
        public  void HandleRequest(RequesCode requesCode, ActionCode actionCode, string data, Client client)
        {
            controllerManager.HandelRequset(requesCode, actionCode, data, client);
        }
    }
}

14,数据的打包和数据的发送到客户端

Message:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Common;
namespace GameServer.Servers
{
    class Message
    {
        private byte[] data = new byte[1024]; //这个数组用来存取我们读取到的数据,让消息的长度最少能存的下1024 ,如果说最大的消息数组存不下的话 就没办法完整的读取这条消息了
        //如果消息不完整 我们不处理。我们会读取下一条
        private int startIndex = 0;//标识位,表示现在数据存储在什么位置了 ,如果没数据 sartIndex从0开始,如果存了10个之后 这个startIndex就等于10;
                                   //再来数据的话就从10开始存,同时也代表了存了多少个字节的数据。

        /// <summary>
        /// 这个数组用来存取我们读取到的数据
        /// </summary>
        public byte[] Data
        {
            get { return data; }
        }
        /// <summary>
        /// 代表存储了多少个字节的数据  开始索引
        /// </summary>
        public int StartIndex
        {
            get { return startIndex; }
        }
        /// <summary>
        /// 剩余的空间
        /// </summary>
        public int RemaniSize
        {
            get { return data.Length - startIndex; }
        }
        /// <summary>
        /// 更新了多少数据
        /// </summary>
        /// <param name="count"></param>
        //public void AddCount(int count)
        //{
        //    startIndex += count;
        //}
        /// <summary>
        /// 解析数据或者叫做读取数据  newDataAmount数量
        /// </summary>
        public void ReadMessage(int newDataAmount,Action<RequesCode,ActionCode,string> processDataCallback)
        {
            startIndex += newDataAmount;


            while (true)
            {
                if (startIndex <= 4) return;
                int count = BitConverter.ToInt32(data, 0);//这个只占前四个字节  ,变成int 就是数据长度
                if ((startIndex - 4) >= count) //(startIndex-4)是剩余数据的长度 ,大于count 就说明数据是完整的
                {
                    //string s = Encoding.UTF8.GetString(data, 4, count);
                    //Console.WriteLine("解析出来一条数据 :" + s);
                    RequesCode requestCode=(RequesCode)   BitConverter.ToInt32(data, 4); ///解析出来的是ResourceCode
                    ActionCode actionCode = (ActionCode)BitConverter.ToInt32(data, 8); ///解析出来的是ActionCode
                    string s = Encoding.UTF8.GetString(data, 12,count-8);

                    processDataCallback(requestCode, actionCode, s);

                    Array.Copy(data, count + 4, data, 0, startIndex - 4 - count);

                    startIndex -= (count + 4); //移动完了之后更新startIndex
                }
                else
                {
                    break;
                }
            }

          
        }

      



        /// <summary>
        /// 用于组合包装
        /// </summary>
        /// <param name="requsetData"></param>
        /// <param name="data"></param>
        /// <returns></returns>
        public static byte[] PakeDaa(RequesCode requsetData,string data)
        {
            byte[] requsetCodeBytes = BitConverter.GetBytes((int)requsetData);

            byte[] dataBytes = Encoding.UTF8.GetBytes(data);

            int dataAmount = requsetCodeBytes.Length + dataBytes.Length;
            byte[] dataAmountBytes = BitConverter.GetBytes(dataAmount);
       return      dataAmountBytes.Concat(requsetCodeBytes).Concat(dataBytes).ToArray();


        }
    }
}

Clinet 类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using Common;

namespace GameServer.Servers
{
    class Client
    {
        private Socket clientSocket;
        private Server server;
        private Message messgae = new Message();
        public Client()
        {
        }
        public Client(Socket clientSocket, Server server)
        {
            this.clientSocket = clientSocket;
            this.server = server;
        }
        public void Start()
        {
            clientSocket.BeginReceive(messgae.Data, messgae.StartIndex, messgae.RemaniSize,SocketFlags.None,AsyncCallback,null);

        }
        public void AsyncCallback(IAsyncResult ar){
            try
            {
                int count = clientSocket.EndReceive(ar);

                if (count == 0)
                {
                    Close();
                }

                messgae.ReadMessage(count,OnProcessMessage);

                Start();


                //TODO 处理接收到的数据
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Close();
            }

        }

        private void Close()
        {
            if (clientSocket!=null)
            
            clientSocket.Close();

            server.RemoveClient(this);
        }

        private void OnProcessMessage(RequesCode requesCode, ActionCode actionCode, string data)
        {
            server.HandleRequest(requesCode, actionCode, data, this);
        }

      
        public void Send(RequesCode requsetCode,string data)
        {
            byte[] bytes = Message.PakeDaa(requsetCode, data);
            clientSocket.Send(bytes);
        }
    }
}

Server类:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using GameServer.Controller;
using Common;

namespace GameServer.Servers
{
    class Server
    {
        private IPEndPoint ipEndPoint;

        private Socket serverSocket;

        private List<Client> clientList;

        private ControllerManager controllerManager;

        public Server()
        {

        }
        public Server(string IpStr, int port)
        {
            controllerManager = new ControllerManager(this);
            SetIpAndPort(IpStr, port);
        }
        public void  SetIpAndPort(string IpStr, int port)
        {
            ipEndPoint = new IPEndPoint(IPAddress.Parse(IpStr), port);
        }

        public void Start()
        {
            serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            serverSocket.Bind(ipEndPoint);
            serverSocket.Listen(0);
            serverSocket.BeginAccept(AcceptCallBack, null);
        }
        
        public void AcceptCallBack(IAsyncResult ar)
        {
            Socket clientSocket = serverSocket.EndAccept(ar); //

            Client client = new Client(clientSocket,this);
            client.Start();
            clientList.Add(client); //添加到list集合

        }
        public void RemoveClient(Client client)
        {
            lock (clientList) //锁定移除
            {
                clientList.Remove(client);
            }
           
        }
        /// <summary>
        /// 给Clinet客户端发起相应
        /// </summary>
        /// <param name="clinet"></param>
        /// <param name="requestCode"></param>
        /// <param name="data"></param>
        public  void SentResponse(Client clinet,RequesCode requestCode,string data)
        {
            //TODO给客户端相应
            clinet.Send(requestCode, data);

        }

        /// <summary>
        /// Manager跟server交互 server跟Clinet交互
        /// </summary>
        /// <param name="requesCode"></param>
        /// <param name="actionCode"></param>
        /// <param name="data"></param>
        /// <param name="client"></param>
        public  void HandleRequest(RequesCode requesCode, ActionCode actionCode, string data, Client client)
        {
            controllerManager.HandelRequset(requesCode, actionCode, data, client);
        }
    }
}

15,创建ConnHelper,数据库连接的创建和关闭。



然后门在Tool文件夹下新建ConnHelper类:

并且在mysql数据库中创建一个game01

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MySql.Data.MySqlClient;

namespace GameServer.Tool
{
    /// <summary>
    /// 用来建立跟服务端的连接
    /// </summary>
    class ConnHelper
    {
        public const string CONNECTIONDTRING = "dataSource=127.0.0.1;port=3306;database=game01;user=root;password=root";

        public static MySqlConnection Connect()
        {
            MySqlConnection conn = new MySqlConnection(CONNECTIONDTRING);

            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进行修改:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
using Common;
using MySql.Data.MySqlClient;
using GameServer.Tool;
namespace GameServer.Servers
{
    class Client
    {
        private Socket clientSocket;
        private Server server;
        private Message messgae = new Message();

        private MySqlConnection mysqlConn;
        public Client()
        {
        }
        public Client(Socket clientSocket, Server server)
        {
            this.clientSocket = clientSocket;
            this.server = server;
            mysqlConn = ConnHelper.Connect();
        }
        public void Start()
        {
            clientSocket.BeginReceive(messgae.Data, messgae.StartIndex, messgae.RemaniSize,SocketFlags.None,AsyncCallback,null);

        }
        public void AsyncCallback(IAsyncResult ar){
            try
            {
                int count = clientSocket.EndReceive(ar);

                if (count == 0)
                {
                    Close();
                }

                messgae.ReadMessage(count,OnProcessMessage);

                Start();


                //TODO 处理接收到的数据
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                Close();
            }

        }

        private void Close()
        {
            ConnHelper.CloseConnection(mysqlConn);
            if (clientSocket!=null)
            
            clientSocket.Close();

            server.RemoveClient(this);

        }
        /// <summary>
        /// 消息的解析
        /// </summary>
        /// <param name="requesCode"></param>
        /// <param name="actionCode"></param>
        /// <param name="data"></param>
        private void OnProcessMessage(RequesCode requesCode, ActionCode actionCode, string data)
        {
            server.HandleRequest(requesCode, actionCode, data, this);
        }

      /// <summary>
      /// 消息的发送
      /// </summary>
      /// <param name="requsetCode"></param>
      /// <param name="data"></param>
        public void Send(RequesCode requsetCode,string data)
        {
            byte[] bytes = Message.PakeDaa(requsetCode, data);
            clientSocket.Send(bytes);
        }
    }
}

12,导入开发好的UI框架和目录框架:

资源 :链接: https://pan.baidu.com/s/1hrSJUmS密码: xe7b

新建Unity3D工程 ; SkUIFramework

13,导入游戏素材

导入资源 Res 

14 ,游戏客户端和架构分析

架构图如下:

Access文件夹下新建Scripts文件夹 下面创建GameFacade 然后新建一个Manager文件夹 ,创建BaseManager, 然后修改UIManager,新建 CameraManager AudioManager,PlayerManager,RequestManager ClientManager,都继承自BaseManager 。

BaseManager类如下、;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class BaseManager  {

    /// <summary>
    /// 生命的开始
    /// </summary>
    public virtual void OnInit() { }
    /// <summary>
    /// 生命的结束
    /// </summary>
    public virtual void OnDestory() { }

}

然后在GameFacade 类:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class GameFacade : MonoBehaviour {

    private UIManager uiMng;
    private AudioManager audioMng;
    private PlayerManager playerMng;
    private CameraManager cameraMng;
    private RequestManager requesMng;
    private ClientManager clientMng;
	void Start () {
        InitManager();
    }
	void Update () {
	}
    private void InitManager()
    {
        uiMng = new UIManager();
        audioMng = new AudioManager();
        playerMng = new PlayerManager();
        cameraMng = new CameraManager();
        requesMng = new RequestManager();
        clientMng = new ClientManager();
        uiMng.OnInit();
        audioMng.OnInit();
        playerMng.OnInit();
        cameraMng.OnInit();
        requesMng.OnInit();
        clientMng.OnInit();
    }
    private void DestoryManager()
    {
        uiMng.OnDestory();
        audioMng.OnDestory();
        playerMng.OnDestory();
        cameraMng.OnDestory();
        requesMng.OnDestory();
        clientMng.OnDestory();
    }

    private void OnDestory()
    {
        DestoryManager();
    }
}


13,开发ClinetManager,跟服务器端的连接和关闭

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using UnityEngine;
/// <summary>
/// 这个是用来管理跟服务器端的Socket连接
/// </summary>
public  class ClientManager:BaseManager
{
    public const string IP = "127.0.0.1";
    public const int PORT = 6688;
    private Socket clinetSocket;
    public override void OnInit()
    {
        base.OnInit();

        clinetSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        try
        {
            clinetSocket.Connect(IP, PORT);
            
        }
        catch (Exception e)
        {

            Debug.LogWarning("无法连接到服务器端请检查你的网络"+e);
        }
    }

    public override void OnDestory()
    {
        base.OnDestory();
        
        try
        {
            clinetSocket.Close();
        }
        catch (Exception e)
        {
            Debug.LogWarning("无法关闭跟服务端的连接" + e);
        }
    }
}


请看下集

猜你喜欢

转载自blog.csdn.net/qq_36848370/article/details/78577573