前言
众所周知,网络游戏中,服务器的搭建尤为重要,无论是授权服务器,还是非授权服务器,它都承担着很大一部分的数据处理。今天,给大家分享一个中高级的小服务器搭建,当然也是在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实现,本文后续还会有网络游戏传输协议的内容,同学们随时关注。
网友评论