项目实训--Unity多人游戏开发(四、继续完善后端结构框架+数据库)

本期工作进展

socket通信框架。
controller。
数据库。

具体内容

在这里插入图片描述
Server.cs:
启动Socket服务。并且维护了一个连接上的Client链表。

private List<Client> clientList;//可能哈希表更好一点

        void StartAccept()
        {
            serverSocket.BeginAccept(AcceptCallback, null);
        }

        void AcceptCallback(IAsyncResult iar)
        {
            Console.WriteLine("new player connected...");
            Socket client = serverSocket.EndAccept(iar);
            clientList.Add(new Client(client));//添加到链表
            StartAccept();
        }

Client.cs:
代表一个客户端,具有私有变量(socket、message)

class Client
    {
        private Socket socket;
        private Message msg;
        public Client(Socket socket)
        {
            this.socket = socket;
            msg = new Message();
            SendNoticeAutomatically();
            StartReceive();//调用startReceive接收消息
        }

        void StartReceive()
        {
            socket.BeginReceive(msg.Buffer, msg.StartIndex, msg.RemainSize, SocketFlags.None, ReceiveCallback, null);
        }

        void ReceiveCallback(IAsyncResult iar)//回调
        {
            try
            {
                Console.WriteLine("收到了socket内容");
                if (socket == null || socket.Connected == false) return;
                int len = socket.EndReceive(iar);
                if(len == 0)//收到信息长度为0则代表对面断开连接
                    return;
                    //msg负责消息读取,调用readBuffer读取消息,顺便在内部调用函数解析消息
                msg.ReadBuffer(len, this);
                StartReceive();//调用startReceive继续接收消息
            }
            catch(Exception e)
            {
                Console.WriteLine("收取socket内容时出错:" + e.ToString());
            }
        }

        public void Send(MainPack messagePack)//通过message打包然后发送到客户端
        {
            socket.Send(msg.ParcelPack(messagePack));
        }

        void SendNoticeAutomatically()//emm不重要
        {
            //***********模拟收到了公告请求
            MainPack mainPack = new MainPack();
            mainPack.RequestCode = RequestCode.NoticeRequest;
            mainPack.ActionCode = ActionCode.ReceiveNotice;
            Server.controllerManager.HandleRequest(mainPack, this);
        }
    }

Message.cs:
负责消息的读取、打包

class Message
    {
        private byte[] buffer = new byte[1024];//缓存区

        private int startindex;//buffer存到了第几位,用于解决粘包问题

        public byte[] Buffer
        {
            get
            {
                return buffer;
            }
        }

        public int StartIndex
        {
            get
            {
                return startindex;
            }
        }

        public int RemainSize
        {
            get
            {
                return buffer.Length - startindex;
            }
        }

        public Message()
        {
        }

		//解决粘包机制,详细讲解在前一篇文章
        public void ReadBuffer(int len, Client client)
        {
            startindex += len;
            if (startindex <= 4)
                return;
            int count = BitConverter.ToInt32(Buffer, 0);//读取包头获取消息长度count
            while(true)//可能没必要while??
            {
                if (startindex >= (count + 4))//从0到startindex至少包含一个完整的消息,提取出来并把之后的消息内容复制到最开始防止buffer超界。
                {
                    Console.WriteLine("socket信息够一个包长了,handleRequest");
                    //这是protocolBuffers提供的函数,可以由字节数组转为实际信息
                    MainPack msgPack = (MainPack)MainPack.Descriptor.Parser.ParseFrom(Buffer, 4, count);
                    startindex -= (count + 4);
                    Array.Copy(buffer, count + 4, buffer, 0, startindex);

                    //转去处理消息
                    Server.controllerManager.HandleRequest(msgPack, client);
                }
                else
                {
                    break;
                }
            }
        }
		
		//打包消息返回字节数组准备通过socket发送
		//也涉及粘包机制的问题,别忘了在开头封装上信息长度
        public byte[] ParcelPack(MainPack messagePack)
        {
            byte[] packBytes = messagePack.ToByteArray();
            byte[] head = BitConverter.GetBytes(packBytes.Length);
            return head.Concat(packBytes).ToArray();
        }
    }

Controller方面:
接收到消息后会先找到对应的controller。
而具体怎么找则是在Server类里的静态变量controllerManager通过RequestCode映射到我们需要的controller。
在这里插入图片描述
ControllerManager.cs:

class ControllerManager
    {
	    //字典,存储所有Controller
        public Dictionary<RequestCode, BaseController> controllerDic = new Dictionary<RequestCode, BaseController>();//此类单例或者静态,通过此类对象访问此数据结构

        public ControllerManager()//构造函数,将所有的controller放进dic
        {
            UserController userController = new UserController();
            controllerDic.Add(userController.GetRequestCode, userController);
            NoticeController noticeController = new NoticeController();
            controllerDic.Add(noticeController.GetRequestCode, noticeController);
            RecordController recordController = new RecordController();
            controllerDic.Add(recordController.GetRequestCode, recordController);
        }

		//这里是Message类读取消息后转到这里执行
        //通过对pack的requestcode分别调用对应controller的处理方法
        public void HandleRequest(MainPack msgPack,Client client)
        {
            Console.WriteLine("执行HandleRequest找controller");
            //通过requestCode获取对应controller
            if(controllerDic.TryGetValue(msgPack.RequestCode, out BaseController controller))
            {
                string methodName = msgPack.ActionCode.ToString();
                //通过反射得到方法,方法对应ActionCode
                MethodInfo method = controller.GetType().GetMethod(methodName);
                if(method == null)
                {
                    Console.WriteLine("method not found!!!!!!");
                    return;
                }
                object[] param = new object[] { msgPack };
                object result = method.Invoke(controller, param);

                if(result != null)//返回数据不为空,发送给客户端
                {   
                    client.Send(result as MainPack);
                }
            }
            else
            {
                Console.WriteLine("controller not found!!!");
                return;
            }
        }
    }

具体的controller只演示一个吧:
UserController.cs:
按照约定,其RequestCode对应UserRequest。

class UserController:BaseController
    {
        private UserDao userDao;

        public UserController()
        {
            requestCode = RequestCode.UserRequest;
            userDao = new UserDao();
        }

        //注册
        public MainPack Register(MainPack messagePack)
        {
            Console.WriteLine("Usercontroller的注册方法");
            bool result = userDao.Register(messagePack.LoginPack.Account, messagePack.LoginPack.Password);
            if(result)
            {
                messagePack.ReturnCode = ReturnCode.Succeed;
            }
            else
            {
                messagePack.ReturnCode = ReturnCode.Fail;
            }
            return messagePack;
        }

        public MainPack Login(MainPack messagePack)
        {
            bool result = userDao.Login(messagePack.LoginPack.Account, messagePack.LoginPack.Password);
            if (result)
            {
                messagePack.ReturnCode = ReturnCode.Succeed;
            }
            else
            {
                messagePack.ReturnCode = ReturnCode.Fail;
            }
            return messagePack;
        }
		
		//查看个人基本信息
        public MainPack CheckMyInfo(MainPack mainPack)
        {
            mainPack = userDao.CheckMyInfo(mainPack);
            return mainPack;
        }

        //查看我的游戏信息
        public MainPack CheckMyGameInfo(MainPack mainPack)
        {
            mainPack = userDao.CheckMyGameInfo(mainPack);
            return mainPack;
        }

数据库方面可以通过VS的nuget包管理安装mysql对应的dll。
在这里插入图片描述
Dao层:
同样只演示UserDao:
UserDao.cs:
这种字符串拼接方式的sql指令,对于sql注入问题十分的危险。
建议采取参数化拼接并添加前后端校验。
具体的数据库API这里暂时不提供了,最基础的操作使用都比较简单,并且有jdbc基础的很容易看懂。而且IDE有提示,当时自己没怎么搜教程自己摸索写的结果确实能执行。

class UserDao
    {
        public UserDao()
        {
            //account\password\registertime\coins
        }

        //向数据库发起注册
        public bool Register(string account, string password)
        {
            Console.WriteLine("dao的注册方法");
            Conn conn = new Conn();
            string sql = "select * from user where `account` = '"+ account + "'";
            MySqlCommand mySqlCommand = new MySqlCommand(sql,conn.connection);

            MySqlDataReader reader = mySqlCommand.ExecuteReader();

            Console.WriteLine("准备执行sql语句");
            //检查有没有已经注册
            if (!reader.Read())//没有注册,可以注册
            {
                reader.Close();//!!!
                Console.WriteLine("此账号可以注册");
                DateTime datetime = DateTime.Now;
                sql = "insert into user(`account`,`password`,`registertime`,`coins`) VALUES ('" + account + "','" + password + "','"+ Tools.TimeToString(datetime) +"',0)";
                mySqlCommand = new MySqlCommand(sql, conn.connection);
                Console.WriteLine("准备插入注册信息");
                try
                {
                    if (mySqlCommand.ExecuteNonQuery() == 1)//注册成功
                    {
                        Console.WriteLine("插入成功");
                        conn.CloseConn();
                        return true;
                    }
                    conn.CloseConn();
                    return false;
                }
                catch(Exception e)
                {
                    Console.WriteLine(e.Message);
                    conn.CloseConn();
                    return false;
                }
            }
            else
            {
                Console.WriteLine("this account has been registered, please change one");
                conn.CloseConn();
                return false;
            }
        }

        //登录
        public bool Login(string account, string password)
        {
            Conn conn = new Conn();
            string sql = "select * from `socketGame`.`user` where `account` = '" + account + "'";
            MySqlCommand mySqlCommand = new MySqlCommand(sql, conn.connection);

            MySqlDataReader reader = mySqlCommand.ExecuteReader();
            //查询账号
            if (!reader.Read())//没有此账号
            {
                Console.WriteLine("account not exist");
                return false;
            }
            else//存在,对比密码
            {
                if (reader.GetString("password").Equals(password))
                {
                    conn.CloseConn();
                    return true;
                }
                conn.CloseConn();
                return false;
            }
        }

        //查个人信息
        public MainPack CheckMyInfo(MainPack mainPack)
        {
            Conn conn = new Conn();
            string sql = "select * from user where `account` = '" + mainPack.LoginPack.Account + "'";
            MySqlCommand mySqlCommand = new MySqlCommand(sql, conn.connection);

            MySqlDataReader reader = mySqlCommand.ExecuteReader();
            if (reader.Read())
            {
                mainPack.LoginPack.RegTime = reader.GetString(2);//0123
                mainPack.ReturnCode = ReturnCode.Succeed;
            }
            else
            {
                mainPack.ReturnCode = ReturnCode.Fail;
            }

            conn.CloseConn();
            return mainPack;
        }

        //查个人战绩
        public MainPack CheckMyGameInfo(MainPack mainPack)
        {
            Conn conn = new Conn();
            string sql = "select * from gamerecord where `account` = '" + mainPack.LoginPack.Account + "' and `gameID` = '"+ Const.GetGameIntId(mainPack.RecordPack.GameId) +"'";
            MySqlCommand mySqlCommand = new MySqlCommand(sql, conn.connection);

            MySqlDataReader reader = mySqlCommand.ExecuteReader();
            Console.WriteLine("---userDao执行查询个人战绩信息后");
            if (reader.Read())
            {
                Console.WriteLine("读到个人战绩信息");
                if(mainPack.RecordPack!=null)
                {
                    mainPack.RecordPack.WinNum = reader.GetInt32(3);
                    mainPack.RecordPack.AllNum = reader.GetInt32(4);
                }
                else
                {
                    RecordPack recordPack = new RecordPack();
                    recordPack.WinNum = reader.GetInt32(3);
                    recordPack.AllNum = reader.GetInt32(4);
                    mainPack.RecordPack = recordPack;
                }
                mainPack.ReturnCode = ReturnCode.Succeed;
            }
            else
            {
                mainPack.ReturnCode = ReturnCode.Fail;
            }
            
            conn.CloseConn();
            return mainPack;
        }
    }

总结

继上期解决粘包机制后。
本期继续完善protocolBuffer与后端socket通信框架的融合,接收消息后转到controller处理、随后返回给客户端消息。
写好了数据库大致的增删改查。
其中后端校验与try-catch代码块可能存在一点缺陷。等前端对应部分开发完毕后进行测试时完善。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值