美文网首页Unity3d相关(M)unity网络征服Unity3d
Unity网络服务器搭建【中高级】

Unity网络服务器搭建【中高级】

作者: 欣羽馨予 | 来源:发表于2016-01-22 15:21 被阅读3699次

    前言

    众所周知,网络游戏中,服务器的搭建尤为重要,无论是授权服务器,还是非授权服务器,它都承担着很大一部分的数据处理。今天,给大家分享一个中高级的小服务器搭建,当然也是在Unity上实现的,可以迁移为服务,外部程序等。其中加入了对象的序列化与反序列化,数据包的封包与拆包,细细品味,别有一番滋味。

    • 服务器基础搭建

        using UnityEngine;
        using System.Collections;
        using System.Net;
        using System.Net.Sockets;
        using System.Collections.Generic;
        using System;
        using System.IO;
      
        //实时消息处理委托
        public delegate void NetEventHandler (string msg);
      
        public class NetUtility
        {
            //单例
            public static readonly NetUtility Instance = new NetUtility ();
            //消息回调
            private NetEventHandler ReceiveCallback;
            //服务器Tcp
            private TcpListener tcpServer;
            //客户端Tcp
            private TcpClient tcpClient;
            //缓冲区
            private byte[] buffer;
            //缓存数据组
            private List<byte> cache;
            //网络节点
            private IPEndPoint serverIPEndPoint;
      
            /// <summary>
            /// 设置网络节点
            /// </summary>
            /// <param name="ep">网络节点.</param>
            public void SetIpAddressAndPort (IPEndPoint ep)
            {
                //只写网络节点
                serverIPEndPoint = ep;
            }
            /// <summary>
            /// 设置委托
            /// </summary>
            /// <param name="handler">消息委托.</param>
            public void SetDelegate (NetEventHandler handler)
            {
                //只写赋值回调
                ReceiveCallback = handler;
            }
            /// <summary>
            /// Initializes a new instance of the <see cref="NetUtility"/> class.
            /// </summary>
            private NetUtility ()
            {
                //服务器实例
                tcpServer = new TcpListener (IPAddress.Any, 23456);
                //客户端实例
                tcpClient = new TcpClient (AddressFamily.InterNetwork);
                //缓冲区初始化
                buffer = new byte[1024];
                //缓存数据组实例
                cache = new List<byte> ();
                //默认网络节点
                serverIPEndPoint = new IPEndPoint (IPAddress.Parse ("127.0.0.1"), 23456);
            }
      
    • 服务器部分

        #region Server Part:
        /// <summary>
        /// 开启服务器
        /// </summary>
        public void ServerStart ()
        {
            //开启服务器
            tcpServer.Start (10);
            //服务器开启提示
            ReceiveCallback ("Server Has Init!");
            //开始异步接受客户端的连接请求
            tcpServer.BeginAcceptTcpClient (AsyncAccept, null);
        }
        /// <summary>
        /// 异步连接回调
        /// </summary>
        /// <param name="ar">Ar.</param>
        void AsyncAccept (System.IAsyncResult ar)
        {
            //接受到客户端的异步连接请求
            tcpClient = tcpServer.EndAcceptTcpClient (ar);
            //有新的客户端连接提示
            ReceiveCallback ("Accept Client :" + tcpClient.Client.RemoteEndPoint.ToString ());
            //异步接收消息
            tcpClient.Client.BeginReceive (buffer, 0, 1024, SocketFlags.None, AsyncReceive, tcpClient.Client);
            //异步接受客户端请求尾递归
            tcpServer.BeginAcceptTcpClient (AsyncAccept, null);
        }
        /// <summary>
        /// 异步接收消息回调
        /// </summary>
        /// <param name="ar">Ar.</param>
        void AsyncReceive (System.IAsyncResult ar)
        {
            //获取消息套接字
            Socket workingClient = ar.AsyncState as Socket;
            //完成接收
            int msgLength = workingClient.EndReceive (ar);
            //如果接收到了数据
            if (msgLength > 0) {
                //消息接收提示
                ReceiveCallback ("ReceiveData : " + msgLength + "bytes");
                //临时缓冲区
                byte[] tempBuffer = new byte[msgLength];
                //拷贝数据到临时缓冲区
                Buffer.BlockCopy (buffer, 0, tempBuffer, 0, msgLength);
                //数据放到缓存数据组队尾
                cache.AddRange (tempBuffer); 
                //拆包解析
                byte[] result = LengthDecode (ref cache);
                //如果已经接收完全部数据
                if (result != null) {
                    //开始反序列化数据
                    NetModel resultModel = DeSerialize (result);
                    //TODO:Object Processing!
                    //数据对象结果提示
                    ReceiveCallback ("Object Result IP : " + resultModel.senderIp);
                    ReceiveCallback ("Object Result Content : " + resultModel.content);
                    ReceiveCallback ("Object Result Time : " + resultModel.time);
                }
                //消息未接收全,继续接收
                tcpClient.Client.BeginReceive (buffer, 0, 1024, SocketFlags.None, AsyncReceive, tcpClient.Client);
            }
      
        }
        #endregion  
      
    • 客户端部分

        #region Client Part
        /// <summary>
        /// 客户端连接
        /// </summary>
        public void ClientConnnect ()
        {
            //连接到服务器
            tcpClient.Connect (serverIPEndPoint);
            //连接到服务器提示
            ReceiveCallback ("Has Connect To Server : " + serverIPEndPoint.Address.ToString ());
        }
        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="model">Model.</param>
        public void SendMsg (NetModel model)
        {
            //将数据对象序列化
            buffer = Serialize (model);
            //将序列化后的数据加字节头
            buffer = LengthEncode (buffer);
            //拆分数据,多次发送
            for (int i = 0; i < buffer.Length/1024 + 1; i++) {
                //满发送,1KB
                int needSendBytes = 1024;
                //最后一次发送,剩余字节
                if (i == buffer.Length / 1024) {
                    //计算剩余字节
                    needSendBytes = buffer.Length - i * 1024;
                }
                //发送本次数据
                tcpClient.GetStream ().Write (buffer, i * 1024, needSendBytes);
            }
        }
        #endregion
      
    • 公共方法

        #region Public Function
        /// <summary>
        /// 数据加字节头操作
        /// </summary>
        /// <returns>数据结果.</returns>
        /// <param name="data">源数据.</param>
        byte[] LengthEncode (byte[] data)
        {
            //内存流实例
            using (MemoryStream ms = new MemoryStream()) {
                //二进制流写操作实例
                using (BinaryWriter bw = new BinaryWriter(ms)) {
                    //先写入字节长度
                    bw.Write (data.Length);
                    //再写入所有数据
                    bw.Write (data);
                    //临时结果
                    byte[] result = new byte[ms.Length];
                    //将写好的流数据放入临时结果
                    Buffer.BlockCopy (ms.GetBuffer (), 0, result, 0, (int)ms.Length);
                    //返回临时结果
                    return result;
                }
            }
        }
        /// <summary>
        /// 数据解析,拆解字节头,获取数据.
        /// </summary>
        /// <returns>源数据.</returns>
        /// <param name="cache">缓存数据.</param>
        byte[] LengthDecode (ref List<byte> cache)
        {
            //如果字节数小于4,出现异常
            if (cache.Count < 4)
                return null;
            //内存流实例
            using (MemoryStream ms = new MemoryStream(cache.ToArray())) {
                //二进制流读操作实例
                using (BinaryReader br = new BinaryReader(ms)) {
                    //先读取数据长度,一个int值
                    int realMsgLength = br.ReadInt32 ();
                    //如果未接收全数据,下次继续接收
                    if (realMsgLength > ms.Length - ms.Position) {
                        return null;
                    }
                    //接收完,读取所有数据
                    byte[] result = br.ReadBytes (realMsgLength);
                    //清空缓存
                    cache.Clear ();
                    //返回结果
                    return result;
                }
            }
        }
        /// <summary>
        /// 序列化数据.
        /// </summary>
        /// <param name="mod">数据对象.</param>
        private byte[] Serialize (NetModel mod)
        {
            try {
                //内存流实例
                using (MemoryStream ms = new MemoryStream()) {
                    //ProtoBuf协议序列化数据对象
                    ProtoBuf.Serializer.Serialize<NetModel> (ms, mod);
                    //创建临时结果数组
                    byte[] result = new byte[ms.Length];
                    //调整游标位置为0
                    ms.Position = 0;
                    //开始读取,从0到尾
                    ms.Read (result, 0, result.Length);
                    //返回结果
                    return result;
                }
            } catch (Exception ex) {
                
                Debug.Log ("Error:" + ex.ToString ());
                return null;
            }
        }
        
        /// <summary>
        /// 反序列化数据.
        /// </summary>
        /// <returns>数据对象.</returns>
        /// <param name="data">源数据.</param>
        private NetModel DeSerialize (byte[] data)
        {
            try {
                //内存流实例
                using (MemoryStream ms = new MemoryStream(data)) {
                    //调整游标位置
                    ms.Position = 0;
                    //ProtoBuf协议反序列化数据
                    NetModel mod = ProtoBuf.Serializer.Deserialize<NetModel> (ms);
                    //返回数据对象
                    return mod;
                    
                }
            } catch (Exception ex) {
                Debug.Log ("Error: " + ex.ToString ());
                return null;
            }
        }
        #endregion
      
    • 服务器实现

        using UnityEngine;
        using System.Collections;
        using System.IO;
        using System;
      
        /// <summary>
        /// 服务器实现.
        /// </summary>
        public class ServerDemo : MonoBehaviour
        {
            //临时消息接收
            string currentMsg = "";
            Vector2 scrollViewPosition;
      
            void Start ()
            {
                scrollViewPosition = Vector2.zero;
                //消息委托
                NetUtility.Instance.SetDelegate ((string msg) => {
                    Debug.Log (msg);
                    currentMsg += msg + "\r\n";
                });
                //开启服务器
                NetUtility.Instance.ServerStart ();
            }
      
            void OnGUI ()
            {
                scrollViewPosition = GUILayout.BeginScrollView (scrollViewPosition, GUILayout.Width (300), GUILayout.Height (300));
                //消息展示
                GUILayout.Label (currentMsg);
                GUILayout.EndScrollView ();
            }
        }
      
    • 客户端实现

        using UnityEngine;
        using System.Collections;
        using System.Text;
        using System;
        /// <summary>
        /// 客户端实现
        /// </summary>
        public class ClientDemo : MonoBehaviour
        {
            //待解析地址
            public string wwwAddress = "";
      
            void Start ()
            {
                //消息处理
                NetUtility.Instance.SetDelegate ((string msg) => {
                    Debug.Log (msg + "\r\n");
                });
                //连接服务器
                NetUtility.Instance.ClientConnnect ();
                //开启协程
                StartCoroutine (ServerStart ());
            }
      
            IEnumerator ServerStart ()
            {
                //加载网页数据
                WWW www = new WWW (wwwAddress);
                yield return www;
                //编码获取内容
                string content = UTF8Encoding.UTF8.GetString (www.bytes);
                //内容测试
                Debug.Log (content);
                //待发送对象
                NetModel nm = new NetModel ();
                //消息体
                nm.senderIp = "127.0.0.1";
                nm.content = content;
                nm.time = DateTime.Now.ToString ();
                //发送数据对象
                NetUtility.Instance.SendMsg (nm);
            }
        }
      
    • 结果图


      结果图
    • Demo下载:链接: http://pan.baidu.com/s/1mhgUALy 密码: u36k

    结束语

    文中的ProtoBuf是跨平台的,可以跨平台传输对象。本文使用的是传输层的协议,有兴趣的同学可以使用网络层协议Socket实现,本文后续还会有网络游戏传输协议的内容,同学们随时关注。

    相关文章

      网友评论

        本文标题:Unity网络服务器搭建【中高级】

        本文链接:https://www.haomeiwen.com/subject/kdxrkttx.html