Unity網(wǎng)絡(luò)服務(wù)器搭建【中高級(jí)】

前言

眾所周知歼争,網(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é)果圖


    結(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)注。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末诺凡,一起剝皮案震驚了整個(gè)濱河市东揣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腹泌,老刑警劉巖嘶卧,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異凉袱,居然都是意外死亡芥吟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門专甩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钟鸵,“玉大人,你說我怎么就攤上這事涤躲⌒恚” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵篓叶,是天一觀的道長烈掠。 經(jīng)常有香客問我,道長缸托,這世上最難降的妖魔是什么左敌? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮俐镐,結(jié)果婚禮上矫限,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好叼风,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布取董。 她就那樣靜靜地躺著,像睡著了一般无宿。 火紅的嫁衣襯著肌膚如雪茵汰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天孽鸡,我揣著相機(jī)與錄音蹂午,去河邊找鬼。 笑死彬碱,一個(gè)胖子當(dāng)著我的面吹牛豆胸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播巷疼,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼晚胡,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了嚼沿?” 一聲冷哼從身側(cè)響起估盘,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎伏尼,沒想到半個(gè)月后忿檩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡爆阶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年燥透,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辨图。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡班套,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出故河,到底是詐尸還是另有隱情吱韭,我是刑警寧澤,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布鱼的,位于F島的核電站理盆,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏凑阶。R本人自食惡果不足惜猿规,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望宙橱。 院中可真熱鬧姨俩,春花似錦蘸拔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至张遭,卻和暖如春邓萨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背帝璧。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國打工先誉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留湿刽,地道東北人的烁。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像诈闺,于是被迫代替她去往敵國和親渴庆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理雅镊,服務(wù)發(fā)現(xiàn)襟雷,斷路器,智...
    卡卡羅2017閱讀 134,599評(píng)論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法仁烹,類相關(guān)的語法耸弄,內(nèi)部類的語法,繼承相關(guān)的語法卓缰,異常的語法计呈,線程的語...
    子非魚_t_閱讀 31,581評(píng)論 18 399
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,790評(píng)論 0 11
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程,因...
    小菜c閱讀 6,358評(píng)論 0 17
  • 終端執(zhí)行brew install redis 來安裝redis 安裝完成后征唬,執(zhí)行 redis-server 啟動(dòng)r...
    hukx_michael閱讀 1,355評(píng)論 0 2