前言
眾所周知歼争,網(wǎng)絡(luò)游戲中,服務(wù)器的搭建尤為重要渗勘,無論是授權(quán)服務(wù)器沐绒,還是非授權(quán)服務(wù)器,它都承擔(dān)著很大一部分的數(shù)據(jù)處理旺坠。今天乔遮,給大家分享一個(gè)中高級(jí)的小服務(wù)器搭建,當(dāng)然也是在Unity上實(shí)現(xiàn)的取刃,可以遷移為服務(wù)蹋肮,外部程序等。其中加入了對(duì)象的序列化與反序列化璧疗,數(shù)據(jù)包的封包與拆包坯辩,細(xì)細(xì)品味,別有一番滋味崩侠。
-
服務(wù)器基礎(chǔ)搭建
using UnityEngine; using System.Collections; using System.Net; using System.Net.Sockets; using System.Collections.Generic; using System; using System.IO; //實(shí)時(shí)消息處理委托 public delegate void NetEventHandler (string msg); public class NetUtility { //單例 public static readonly NetUtility Instance = new NetUtility (); //消息回調(diào) private NetEventHandler ReceiveCallback; //服務(wù)器Tcp private TcpListener tcpServer; //客戶端Tcp private TcpClient tcpClient; //緩沖區(qū) private byte[] buffer; //緩存數(shù)據(jù)組 private List<byte> cache; //網(wǎng)絡(luò)節(jié)點(diǎn) private IPEndPoint serverIPEndPoint; /// <summary> /// 設(shè)置網(wǎng)絡(luò)節(jié)點(diǎn) /// </summary> /// <param name="ep">網(wǎng)絡(luò)節(jié)點(diǎn).</param> public void SetIpAddressAndPort (IPEndPoint ep) { //只寫網(wǎng)絡(luò)節(jié)點(diǎn) serverIPEndPoint = ep; } /// <summary> /// 設(shè)置委托 /// </summary> /// <param name="handler">消息委托.</param> public void SetDelegate (NetEventHandler handler) { //只寫賦值回調(diào) ReceiveCallback = handler; } /// <summary> /// Initializes a new instance of the <see cref="NetUtility"/> class. /// </summary> private NetUtility () { //服務(wù)器實(shí)例 tcpServer = new TcpListener (IPAddress.Any, 23456); //客戶端實(shí)例 tcpClient = new TcpClient (AddressFamily.InterNetwork); //緩沖區(qū)初始化 buffer = new byte[1024]; //緩存數(shù)據(jù)組實(shí)例 cache = new List<byte> (); //默認(rèn)網(wǎng)絡(luò)節(jié)點(diǎn) serverIPEndPoint = new IPEndPoint (IPAddress.Parse ("127.0.0.1"), 23456); }
-
服務(wù)器部分
#region Server Part: /// <summary> /// 開啟服務(wù)器 /// </summary> public void ServerStart () { //開啟服務(wù)器 tcpServer.Start (10); //服務(wù)器開啟提示 ReceiveCallback ("Server Has Init!"); //開始異步接受客戶端的連接請(qǐng)求 tcpServer.BeginAcceptTcpClient (AsyncAccept, null); } /// <summary> /// 異步連接回調(diào) /// </summary> /// <param name="ar">Ar.</param> void AsyncAccept (System.IAsyncResult ar) { //接受到客戶端的異步連接請(qǐng)求 tcpClient = tcpServer.EndAcceptTcpClient (ar); //有新的客戶端連接提示 ReceiveCallback ("Accept Client :" + tcpClient.Client.RemoteEndPoint.ToString ()); //異步接收消息 tcpClient.Client.BeginReceive (buffer, 0, 1024, SocketFlags.None, AsyncReceive, tcpClient.Client); //異步接受客戶端請(qǐng)求尾遞歸 tcpServer.BeginAcceptTcpClient (AsyncAccept, null); } /// <summary> /// 異步接收消息回調(diào) /// </summary> /// <param name="ar">Ar.</param> void AsyncReceive (System.IAsyncResult ar) { //獲取消息套接字 Socket workingClient = ar.AsyncState as Socket; //完成接收 int msgLength = workingClient.EndReceive (ar); //如果接收到了數(shù)據(jù) if (msgLength > 0) { //消息接收提示 ReceiveCallback ("ReceiveData : " + msgLength + "bytes"); //臨時(shí)緩沖區(qū) byte[] tempBuffer = new byte[msgLength]; //拷貝數(shù)據(jù)到臨時(shí)緩沖區(qū) Buffer.BlockCopy (buffer, 0, tempBuffer, 0, msgLength); //數(shù)據(jù)放到緩存數(shù)據(jù)組隊(duì)尾 cache.AddRange (tempBuffer); //拆包解析 byte[] result = LengthDecode (ref cache); //如果已經(jīng)接收完全部數(shù)據(jù) if (result != null) { //開始反序列化數(shù)據(jù) NetModel resultModel = DeSerialize (result); //TODO:Object Processing! //數(shù)據(jù)對(duì)象結(jié)果提示 ReceiveCallback ("Object Result IP : " + resultModel.senderIp); ReceiveCallback ("Object Result Content : " + resultModel.content); ReceiveCallback ("Object Result Time : " + resultModel.time); } //消息未接收全漆魔,繼續(xù)接收 tcpClient.Client.BeginReceive (buffer, 0, 1024, SocketFlags.None, AsyncReceive, tcpClient.Client); } } #endregion
-
客戶端部分
#region Client Part /// <summary> /// 客戶端連接 /// </summary> public void ClientConnnect () { //連接到服務(wù)器 tcpClient.Connect (serverIPEndPoint); //連接到服務(wù)器提示 ReceiveCallback ("Has Connect To Server : " + serverIPEndPoint.Address.ToString ()); } /// <summary> /// 發(fā)送消息 /// </summary> /// <param name="model">Model.</param> public void SendMsg (NetModel model) { //將數(shù)據(jù)對(duì)象序列化 buffer = Serialize (model); //將序列化后的數(shù)據(jù)加字節(jié)頭 buffer = LengthEncode (buffer); //拆分?jǐn)?shù)據(jù),多次發(fā)送 for (int i = 0; i < buffer.Length/1024 + 1; i++) { //滿發(fā)送,1KB int needSendBytes = 1024; //最后一次發(fā)送有送,剩余字節(jié) if (i == buffer.Length / 1024) { //計(jì)算剩余字節(jié) needSendBytes = buffer.Length - i * 1024; } //發(fā)送本次數(shù)據(jù) tcpClient.GetStream ().Write (buffer, i * 1024, needSendBytes); } } #endregion
-
公共方法
#region Public Function /// <summary> /// 數(shù)據(jù)加字節(jié)頭操作 /// </summary> /// <returns>數(shù)據(jù)結(jié)果.</returns> /// <param name="data">源數(shù)據(jù).</param> byte[] LengthEncode (byte[] data) { //內(nèi)存流實(shí)例 using (MemoryStream ms = new MemoryStream()) { //二進(jìn)制流寫操作實(shí)例 using (BinaryWriter bw = new BinaryWriter(ms)) { //先寫入字節(jié)長度 bw.Write (data.Length); //再寫入所有數(shù)據(jù) bw.Write (data); //臨時(shí)結(jié)果 byte[] result = new byte[ms.Length]; //將寫好的流數(shù)據(jù)放入臨時(shí)結(jié)果 Buffer.BlockCopy (ms.GetBuffer (), 0, result, 0, (int)ms.Length); //返回臨時(shí)結(jié)果 return result; } } } /// <summary> /// 數(shù)據(jù)解析淌喻,拆解字節(jié)頭僧家,獲取數(shù)據(jù). /// </summary> /// <returns>源數(shù)據(jù).</returns> /// <param name="cache">緩存數(shù)據(jù).</param> byte[] LengthDecode (ref List<byte> cache) { //如果字節(jié)數(shù)小于4雀摘,出現(xiàn)異常 if (cache.Count < 4) return null; //內(nèi)存流實(shí)例 using (MemoryStream ms = new MemoryStream(cache.ToArray())) { //二進(jìn)制流讀操作實(shí)例 using (BinaryReader br = new BinaryReader(ms)) { //先讀取數(shù)據(jù)長度,一個(gè)int值 int realMsgLength = br.ReadInt32 (); //如果未接收全數(shù)據(jù)八拱,下次繼續(xù)接收 if (realMsgLength > ms.Length - ms.Position) { return null; } //接收完阵赠,讀取所有數(shù)據(jù) byte[] result = br.ReadBytes (realMsgLength); //清空緩存 cache.Clear (); //返回結(jié)果 return result; } } } /// <summary> /// 序列化數(shù)據(jù). /// </summary> /// <param name="mod">數(shù)據(jù)對(duì)象.</param> private byte[] Serialize (NetModel mod) { try { //內(nèi)存流實(shí)例 using (MemoryStream ms = new MemoryStream()) { //ProtoBuf協(xié)議序列化數(shù)據(jù)對(duì)象 ProtoBuf.Serializer.Serialize<NetModel> (ms, mod); //創(chuàng)建臨時(shí)結(jié)果數(shù)組 byte[] result = new byte[ms.Length]; //調(diào)整游標(biāo)位置為0 ms.Position = 0; //開始讀取,從0到尾 ms.Read (result, 0, result.Length); //返回結(jié)果 return result; } } catch (Exception ex) { Debug.Log ("Error:" + ex.ToString ()); return null; } } /// <summary> /// 反序列化數(shù)據(jù). /// </summary> /// <returns>數(shù)據(jù)對(duì)象.</returns> /// <param name="data">源數(shù)據(jù).</param> private NetModel DeSerialize (byte[] data) { try { //內(nèi)存流實(shí)例 using (MemoryStream ms = new MemoryStream(data)) { //調(diào)整游標(biāo)位置 ms.Position = 0; //ProtoBuf協(xié)議反序列化數(shù)據(jù) NetModel mod = ProtoBuf.Serializer.Deserialize<NetModel> (ms); //返回?cái)?shù)據(jù)對(duì)象 return mod; } } catch (Exception ex) { Debug.Log ("Error: " + ex.ToString ()); return null; } } #endregion
-
服務(wù)器實(shí)現(xiàn)
using UnityEngine; using System.Collections; using System.IO; using System; /// <summary> /// 服務(wù)器實(shí)現(xiàn). /// </summary> public class ServerDemo : MonoBehaviour { //臨時(shí)消息接收 string currentMsg = ""; Vector2 scrollViewPosition; void Start () { scrollViewPosition = Vector2.zero; //消息委托 NetUtility.Instance.SetDelegate ((string msg) => { Debug.Log (msg); currentMsg += msg + "\r\n"; }); //開啟服務(wù)器 NetUtility.Instance.ServerStart (); } void OnGUI () { scrollViewPosition = GUILayout.BeginScrollView (scrollViewPosition, GUILayout.Width (300), GUILayout.Height (300)); //消息展示 GUILayout.Label (currentMsg); GUILayout.EndScrollView (); } }
-
客戶端實(shí)現(xiàn)
using UnityEngine; using System.Collections; using System.Text; using System; /// <summary> /// 客戶端實(shí)現(xiàn) /// </summary> public class ClientDemo : MonoBehaviour { //待解析地址 public string wwwAddress = ""; void Start () { //消息處理 NetUtility.Instance.SetDelegate ((string msg) => { Debug.Log (msg + "\r\n"); }); //連接服務(wù)器 NetUtility.Instance.ClientConnnect (); //開啟協(xié)程 StartCoroutine (ServerStart ()); } IEnumerator ServerStart () { //加載網(wǎng)頁數(shù)據(jù) WWW www = new WWW (wwwAddress); yield return www; //編碼獲取內(nèi)容 string content = UTF8Encoding.UTF8.GetString (www.bytes); //內(nèi)容測試 Debug.Log (content); //待發(fā)送對(duì)象 NetModel nm = new NetModel (); //消息體 nm.senderIp = "127.0.0.1"; nm.content = content; nm.time = DateTime.Now.ToString (); //發(fā)送數(shù)據(jù)對(duì)象 NetUtility.Instance.SendMsg (nm); } }
-
結(jié)果圖
Demo下載:鏈接: http://pan.baidu.com/s/1mhgUALy 密碼: u36k
結(jié)束語
文中的ProtoBuf是跨平臺(tái)的肌稻,可以跨平臺(tái)傳輸對(duì)象清蚀。本文使用的是傳輸層的協(xié)議,有興趣的同學(xué)可以使用網(wǎng)絡(luò)層協(xié)議Socket實(shí)現(xiàn)爹谭,本文后續(xù)還會(huì)有網(wǎng)絡(luò)游戲傳輸協(xié)議的內(nèi)容枷邪,同學(xué)們隨時(shí)關(guān)注。