Unity网络编程四:客户端与服务端进行数据传输(Unity登录系统的实现)

前言:

在前面几篇文章中介绍了服务器端的搭建以及客户端连接到服务器端的相关实现过程,接下来就需需要实现在客户端与服务器端之间的数据传输功能

本次使用制作Unity登录界面案例来学习使用Unity服务端与客户端进行数据传输,相关代码已经上传Github,可以下载查看:Unity-Socket

客户端:

UI搭建

首先需要Canvas中添加两个输入框。可以通过Hierarchy面板右键选择UI然后选择新建两个输入框Input Field来创建,完成后分别命名为UserNamePassword,来表示账号密码数据的传入框。为了能过提示玩家的输入,可以在添加后修改子元素Placeholder中的Text组件中的文字来完成提示文字的修改

然后添加Button作为数据提交按钮,通过监控按钮的点击事件来触发用户登录

效果图:
在这里插入图片描述
注意事项:

为了确保输入账号密码格式符合标准,可以在Input Field中选择Content Type中选择合适的输入内容的类型,来限制用户的输入格式,账号选择Name,而密码则可以选择Password
在这里插入图片描述

脚本


脚本思路:

  • 首先获取玩家输入的账号密码,可以通过使用InputField.text方法来获取输入框输入的内容,然后对获取的账号密码进行判断是否满足条件,如果满足条件者将账号密码两个内容合并

  • 接下来就是要连接到对应的服务器,然后将合并的账号密码转换为字节流发送给服务器

  • 最后客户端会接受服务端返回的结果,如果结果显示存在账号密码,则登录成功,如果显示账号密码错误,则登录失败,重新执行上面的步骤


第一步,定义处理输入数据方法

在按钮提交时,首先要确保两个输入框的内容不为空,然后将两个字符串合并为一个并返回为字符串,这样方便传输

我们定义一个函数getInputDate()来实现这个方法,最终返回账号密码的合并字符串(中间用空格隔开),但是在返回字符串之前要判断两个输入框是否为空,如果为空,则返回为null,不为空则返回合并后的字符串:

    //账号密码输入框
    public InputField userNameInput;
    public InputField passwordInput;
    /// <summary>
    /// 获取输入框内容
    /// </summary>
    public string getInputDate()
    {
    
    
        if (userNameInput.text != "" && passwordInput.text != "")
        {
    
    
            //获取输入框文本,并剔除掉空格
            string userName = userNameInput.text.Replace(" ","");
            string password = passwordInput.text.Replace(" ", "");
            //将两个字符串合并
            string strSend = userName + " " + password;
            //测试
            Debug.Log("合并字符串为:" + strSend);
            return strSend;
        }
        return null;
    }

第二步,定义将输入发送给服务器端方法

这一步是客户端与服务器端连接的关键,需要先使用之前创建的服务器连接类来创建一个连接,然后将数据发送给服务器端

具体操作细节为:定义一个函数,将上一步执行返回的字符串(不为空的情况下)做为参数,然后要实现在第一次调用该函数时,使用Socket套接字将客户端连接到服务器,为了实现仅仅第一次调用连接,使用if语句进行判断,最后将合并的账号密码转发为字节流发送给服务器:

	//ConnServer 为Unity网络编程二中连接服务器定义的类(可以查看该文章获取源码)
    private ConnServer connServer;
    private Socket socket;
    private bool isConn = false;

    /// <summary>
    /// 向服务器发送数据
    /// </summary>
    /// <param name="strSend"></param>
    public Socket sendDateToServer(string strSend)
    {
    
       
        byte[] bytes=Encoding.UTF8.GetBytes(strSend);
        //第一次调用该方法时创建一个连接socket
        if (isConn==false)
        {
    
    
            connServer = new ConnServer("127.0.0.1", 30000);           
            socket = connServer.conn();            
            isConn = true;            
        }       
        //向服务器发送数据
        socket.Send(bytes);
        Debug.Log("数据发送成功");
        return socket;
    }

第三步,接收数据库处理完数据之后的返回值

当我们发送非空的账号密码给服务器后,服务器会执行数据库查询,查询的结果有下面几种,通过枚举的方式将查询结果转换为对应字符串(我们实际应用可以用更短的方式传输)返回给客户端,具体字符串代表含义为:

  • succes: 查询账号密码存在
  • userNameError:查询账号不存在,即用户名错误
  • passwordError:查询账号存在,但是密码不存在,即密码错误
  • error:这种情况代表未知错误,统一写为服务器连接错误

而在客户端定义getReturnDate()方法,是为了接收这样的字符串,其实现方式为:

定义参数,接收之前与服务器建立连接的socket,然后在方法中,使用接收方法Receive()接收传回来的字节流,并将其转换为字符串返回。使用try catch监控错误,如果过程出错,返回error,统一按照服务器连接错误处理:

/// <summary>
    ///  获取服务器的返回结果
    /// </summary>
    /// <param name="socket"></param>
    /// <returns></returns>
    public string getReturnDate(Socket socketDemo)
    {
    
    
        byte[] bytes;
        string strGet = null;
        int maxBuffer = 1024;
        byte[] buffer = new byte[maxBuffer];
        try
        {
    
    
            int length = socketDemo.Receive(buffer);
            strGet = Encoding.UTF8.GetString(buffer, 0, length);
            Debug.Log("收到服务器传回内容:" + strGet);
            return strGet;
        }
        catch (Exception e)
        {
    
    

            Debug.Log(" 获取服务器内容失败:错误为:" + e.ToString());
        }
        return "error";
    }

第四步,定义处理服务器返回结果的方法

我们在第三步接收了服务器返回的数据库查询结果,在客户端需要进行处理分析,完成客户端的逻辑操作:

  • 接受到succes:场景跳转
  • 接收到userNameError:提示"输入账号错误"
  • 接收到passwordError:提示"输入密码错误"
  • 接收到error:提示"服务器连接错误,请重新尝试"

但是为了测试,统一打印日志来显示查询结果:

    public void showStatus(string strGet)
    {
    
    
        switch(strGet)
        {
    
    
            case "succes":

                Debug.Log("查询成功");
                break;
            case "userNameError":
                Debug.Log("输入用户名错误");
                break;
            case "passwordError":
                Debug.Log("输入密码错误");
                break;
            default:
                Debug.Log("服务器连接错误");
                break;
        }
    }

第五步,定义提交按钮监控事件方法

这一步是为了写一个绑定在提交按钮上的方法,并将前面的方法执行,完成登录系统的所有客户端步骤

    public void loginButton()
    {
    
    
        //获取连个输入框输入并合并返回一个字符串
        string strSend = getInputDate();
        if (strSend != null)
        {
    
    
            //如果返回的字符串不为空,则将其发送给服务器端
            Socket socket = sendDateToServer(strSend);
            //接受服务器端返回的结果
            string strReturn = getReturnDate(socket);
            //处理服务端返回的结果
            showStatus(strReturn);
        }
        else
        {
    
    
            Debug.Log("账号或密码为空");
        }
    }

第六步登录脚本绑定登录按钮

第一,首先将脚本添加到场景中的物体上,本测试选择新建一个空物体命名为Scripts,然后将脚本拖入到该物体

第二,接下来需要给脚本拖入需要的两个输入框:
在这里插入图片描述

第三,为按钮绑定对应loginButton()方法,这样就完成了客户端的程序设计

在这里插入图片描述


服务器端

服务器端创建一个脚本也命名为Login,做为实现登录功能的服务器端的脚本

第一步,获取客户端发送过来的账号密码

首先创建一个函数GetReceive()做为一个获取客户端传来数据的接受方法,并定义一个参数socket作为连接到服务器的一个Socket的监听方法

首先使用SocketReceive() 获取字节流,并通过一定方法转换为字符串,最后返回

/// <summary>
        /// 获取客户端发送来的账号密码
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        public string GetReceive(Socket socket)
        {
    
    
            string strGet = null;
            int maxBuffer = 1024;
            byte[] buffer = new byte[maxBuffer];
            try
            {
    
                    
                int length = socket.Receive(buffer);             
                strGet = Encoding.UTF8.GetString(buffer, 0, length);
                Console.WriteLine("收到内容:"+strGet);
                return strGet;               
            }
            catch (Exception e)
            {
    
    
                Console.WriteLine(" 获取内容失败:错误为:" + e.ToString());
            }
            return null;
        }

第二步,查询数据库账号密码

数据库的查询首先需要连接数据库,在"Unity网络编程二"中些了关于数据库的连接类ConnDB,这里会直接调用,如果不理解,可以查看之前的文章

  • 首先创建函数FindDate(),并接受字符串参数,即从客户端接受的参数,接受后,首先需要对该字符串进行处理,因为该字符串是由账号密码合并而成,需要使用Split()方法将其分解
  • 获取账号后,通过MySqlCommand()执行sql命令,通过账号完成查询,并通过ExecuteReader()完成对于查询数据的获取,最终获取查询后的密码
  • 通过系列判断,以及查询的密码和客户端传来的密码进行对比,然后得出结果,这样就可以得到系列的结果(用字符串表示),然后返回

而数据库中Login表的结构为:
在这里插入图片描述

具体的代码为:

		//输入连接字符串需要的参数
 		private string hostDB = "localhost";
        private string portDB = "3306";
        private string userDB = "root";
        private string passwordDB = ""; //数据库密码未作展示
        private string dateNameDB = "day05";

        /// <summary>
        /// 查询数据库,判断字符串是否存在
        /// </summary>
        /// <param name="strGet"></param>
        /// <returns></returns>
        private string FindDate(string strGet)
        {
    
    
            if(strGet==null)
            {
    
    
                Console.WriteLine("获取服务器字符串为空");
                return "null";
            }

            //处理输入的账号密码字符串
            string[] strGets = strGet.Split(" ");
            string userName = strGets[0];
            string password = strGets[1];

            //创建sql语言并执行查询
            try
            {
    
    
                
                ConnDB connDB = new ConnDB(hostDB, portDB, userDB, passwordDB, dateNameDB);
                MySqlConnection conn = connDB.openDate();

                string sql = string.Format("select * from Login where username=\"{0}\"", userName);
                MySqlCommand cmd = new MySqlCommand(sql, conn);

                //对查询的返回结果进行读取
                MySqlDataReader reader = cmd.ExecuteReader();
                if (reader.Read()) //判断查询结果是否为空
                {
    
    
                    if(reader[0].Equals(password))
                    {
    
    
                        Console.WriteLine("成功查询到账号密码");
                        return "succes";
                    }
                    else
                    {
    
    
                        Console.WriteLine("有查询结果,但是与输入不同,判短输入密码错误");
                        return "passwordError";
                    }
                }
                else
                {
    
    
                    Console.WriteLine("查询为空,账号不存在");
                    return "userNameError";
                }               
            }
            catch (Exception e)
            {
    
    

                Console.WriteLine("连接数据库报错:"+e.ToString());
            }
            Console.WriteLine("查询过程出错");
            return "error";
        }

第三步,向客户端返回结果

这一步这是将刚刚数据库查询完的结果返回给连接进来的客户端,代码简单,直接观看即可:

       	/// <summary>
        /// 向客户端返回查询结果
        /// </summary>
        private void SendDate(Socket socket,string strSend)
        {
    
    
            byte[] bytes = Encoding.UTF8.GetBytes(strSend);
            socket.Send(bytes);
        }

第四步,在函数的构造函数中调取方法

首先需要创建一个socket,作为服务器的Socket,这里世界使用之前Unity网络编程一里面的创建服务器的类来创建一个Socket,并启动

完成启动后,则需要使用Accept()监控连接进行的socket, 并使用循环监控客户端发来的数据并进行系列处理后返回:

//创建socket连接所用服务器地址和端口号
        private string ip = "127.0.0.1";
        private int port = 30000;
        /// <summary>
        /// 方便在实例化时直接执行相关操作
        /// </summary>
        public LogIn()
        {
    
    
            CreateServer server = new CreateServer(ip, port);
            Socket socket = server.StartSocket();
            Socket clider = socket.Accept();

            while (true)
            {
    
                  
                string strGet = GetReceive(clider);              
                string strSend = FindDate(strGet);
                Console.WriteLine(strSend);
                SendDate(clider, strSend);
            }
		}

第五步,在主函数中实例化该登录类,并进行测试

我们在Main()函数中直接实例化login即可,然后启动该项目,同时在Unity客户端中启动项目,然后输入账号密码进行测试,判断程序运行是否有误

总结

通过Socket来实现前后端的数据交互需要在客户端和服务器端分别完成数据的发送和接受,同时客户端处理客户端的逻辑,而服务端处理服务端的逻辑,这样的优势是减小数据的传输量

猜你喜欢

转载自blog.csdn.net/xinzhilinger/article/details/113245628