游戏实时网络通信实现【Socket与FlatBuffer协议】(四)

40 篇文章 3 订阅

上篇介绍了传输层Socket的扩展封装,本篇介绍游戏项目应用层对网络的封装。注意的是,使用网络通信协议是FlatBuffer。如果读者不太熟悉该协议,请阅读笔者关于该协议的博客。1)服务器连接的建立与关闭;2)客户端消息的拼接、序列化以及发送;3)服务器返回的消息的监听、反序列化以及派送。

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using FlatBuffers;
using MyGame.Sample;
/*
 * Author:W
 * 网络工具【应用层】【Socket套接字与FlatBuffer协议】
 * 1)服务器连接的建立与关闭
 * 2)客户端消息的拼接、序列化以及发送
 * 3)服务器返回的消息的监听、反序列化以及派送
 */
namespace W.NetWork
{
	public class NetHelper
	{
        #region 单例
        private static NetHelper instance;

		public static NetHelper Instance
		{
			get {
				if (instance == null)
					instance = new NetHelper();
				return instance;
			}
		}

		NetHelper()
		{
			RegisterCheckMsgRecvHandler();
		}
        #endregion
       
        #region 服务器消息回调
        /// <summary>
        /// 接收来自服务器端消息的回调处理
        /// </summary>
        private Dictionary<int, Action<byte[]>> msgRecvFuncDict = new Dictionary<int, Action<byte[]>>(128);

		/// <summary>
		/// 注册服务器消息处理回调
		/// </summary>
		/// <param name="msgId"></param>
		/// <param name="msgCallBack"></param>
		public void AddMsgListener(int msgId, Action<byte[]> msgCallBack)
		{
			if (!msgRecvFuncDict.ContainsKey(msgId))
				msgRecvFuncDict[msgId] = msgCallBack;
		}

		/// <summary>
		/// 注销服务器消息的监听
		/// </summary>
		/// <param name="msgId"></param>
		public void RemoveMsgListener(int msgId)
		{
			if (msgRecvFuncDict.ContainsKey(msgId))
				msgRecvFuncDict.Remove(msgId);
		}
        #endregion

        #region 服务器错误码回调
        /// <summary>
        /// 服务器端错误码注册回调处理
        /// </summary>
        private Dictionary<int, Action<int>> serverErrorCodeDict = new Dictionary<int, Action<int>>(128);

		/// <summary>
		/// 服务器错误码回调注册
		/// </summary>
		/// <param name="msgId"></param>
		/// <param name="msgCallBack"></param>
		public void AddServerErrorCodeListener(int msgId, Action<int> msgCallBack)
		{
			if (!serverErrorCodeDict.ContainsKey(msgId))
				serverErrorCodeDict[msgId] = msgCallBack;
		}

		/// <summary>
		/// 服务器错误码回调注销
		/// </summary>
		/// <param name="msgId"></param>
		public void RemoveServerErrorCodeListener(int msgId)
		{
			if (serverErrorCodeDict.ContainsKey(msgId))
				serverErrorCodeDict.Remove(msgId);
		}

		/// <summary>
		/// 服务器默认错误回调
		/// </summary>
		private Action<int> serverErrorDefaultFunc = null;

		public void RegisterServerErrorDefaultFunc(Action<int> msgDefaultCallBack)
		{
			serverErrorDefaultFunc = msgDefaultCallBack;
		}

        #endregion

        #region Socket套接字相关
        /// <summary>
        /// socket
        /// </summary>
        private GameSocket gameSocket = null;

		/// <summary>
		/// 最新连接的服务器IP
		/// </summary>
		private string latestIP;
		/// <summary>
		/// 最新连接的服务器端口
		/// </summary>
		private int latestPort;

		/// <summary>
		/// 连接服务器
		/// </summary>
		/// <param name="ip"></param>
		/// <param name="port"></param>
		public void ConnectToServer(string ip, int port)
		{
			if (gameSocket == null)
				gameSocket = new GameSocket();

			latestIP = ip;
			latestPort = port;
			gameSocket.ConnectToServer(ip, port);
		}

		/// <summary>
		/// 服务器连接关闭
		/// </summary>
		public void CloseServer()
		{
			if (gameSocket.IsConnected)
			{
				gameSocket.CloseServer(false);
				gameSocket = null;
			}
		}

		/// <summary>
		/// 服务器连接状态
		/// </summary>
		public bool IsConnectedToServer
		{
			get
			{
				if (gameSocket != null)
				{
					return gameSocket.IsConnected;
				}
				else
				{
					return false;
				}
			}
		}
        #endregion


        #region 消息处理相关

        #region 服务器返回消息检测处理
        /// <summary>
        /// 设定游戏每帧最多处理的服务器消息的数量
        /// </summary>
        private const int MaxMsgDealedNum = 10;

		/// <summary>
		/// 注册服务器返回消息的帧检测处理
		/// </summary>
		private void RegisterCheckMsgRecvHandler()
		{			
			GameManager.Instance.RegisterFrameCallBack(OnServerMsgRecvCheck);
		}

		/// <summary>
		/// 注销服务器返回消息的帧检测处理
		/// </summary>
		private void UnRegisterCheckMsgRecvHandler()
		{
			GameManager.Instance.UnRegisterFrameCallBack(OnServerMsgRecvCheck);
		}

		/// <summary>
		/// 服务器返回消息帧检查处理
		/// </summary>
		private void OnServerMsgRecvCheck()
		{
			if (IsConnectedToServer)
			{
				DealWithServerMsg();
			}

			//心跳检测
			CheckTick();
		}

		/// <summary>
		/// 处理服务器发回的消息
		/// </summary>
		private void DealWithServerMsg()
		{
			for (int i = 0; i < MaxMsgDealedNum; i++)
			{
				byte[] serverMsg = gameSocket.GetReceiveMsg();

				if (serverMsg != null)
					ParseAndHandleMsg(serverMsg);

			}
		}

		/// <summary>
		/// 消息解析,并通知该消息
		/// 【根据使用的协议如Json/ProtocoBuf/FlatBuffer来解析】
		/// </summary>
		private void ParseAndHandleMsg(byte[] package)
		{
			var body = Resp.GetRootAsResp(new ByteBuffer(package));

			//业务状态码. 0正常, >0失败
			if (body.Ret != 0)
			{
				Debug.LogError("服务器发来错误码,id是" + body.Ret);

				//错误码处理 NotDo
				Action<int> serverErrorFunc;
				if (serverErrorCodeDict.TryGetValue(body.Code, out serverErrorFunc))
				{
					serverErrorFunc(body.Ret);
				}
				else
				{
					if (serverErrorDefaultFunc != null)
					{
						serverErrorDefaultFunc(body.Ret);
					}
					else
					{
						Debug.LogError(body.Code + "错误码没有处理监听函数");
					}
				}
			}
			else
			{
				Action<byte[]> RecvMsgFunc;
				if (msgRecvFuncDict.TryGetValue(body.Code, out RecvMsgFunc))
				{
					RecvMsgFunc(body.GetMsgArray());
				}
				else
				{
					Debug.LogError(body.Code + "消息没有处理监听函数");
				}
			}
		}


		/// <summary>
		/// 心跳检测处理
		/// </summary>
		private void CheckTick()
		{

		}

		#endregion

		#region 向服务器发送消息处理
		/// <summary>
		/// 用于拼接基础消息
		/// </summary>
		private FlatBufferBuilder msgBuilder = new FlatBufferBuilder(512);
		private ByteArray sendMsgInfo = new ByteArray(512);

		/// <summary>
		/// 自增ID
		/// </summary>
		private int serverID = 1;

		/// <summary>
		/// 消息发送
		/// 【根据使用的协议如Json/ProtocoBuf/FlatBuffer来序列化】
		/// </summary>
		/// <param name="msgId"></param>
		/// <param name="msgBody"></param>
		public void SendMsgToServer(int msgId,byte[] msgBody)
		{
			//消息拼接:
			VectorOffset body = Req.CreateMsgVector(msgBuilder,msgBody);
			Req.StartReq(msgBuilder);
			Req.AddId(msgBuilder,serverID++);
			Req.AddCode(msgBuilder, msgId);
			Req.AddMsg(msgBuilder,body);
			var msgEnd = Req.EndReq(msgBuilder);
			msgBuilder.Finish(msgEnd.Value);
			byte[] msgInfo = msgBuilder.SizedByteArray();

			sendMsgInfo.Reset();
			sendMsgInfo.WriteInt(msgInfo.Length);
			sendMsgInfo.WriteBytes(msgInfo);

			SendMsgPack(sendMsgInfo.Buffer);

			msgBuilder.Clear();
		}

		/// <summary>
		/// 向服务器发送消息
		/// </summary>
		/// <param name="package"></param>
		private void SendMsgPack(byte[] package)
		{
			if (package == null || gameSocket == null) return;

			gameSocket.SendPack(package);
		}
        #endregion

        #endregion
    }
}

测试的协议文件

namespace MyGame.Sample;

// 基础协议

// 请求
table Req {
    // 消息id
    id:int;
    // 协议号
    code:int;
    // 请求参数
    msg:[ubyte];
}

// 响应
table Resp {
    // 消息id. > 0 表示有对应的客户端请求, = 0 表示是服务器主动推送
    id:int;
    // 协议号
    code:int;
     // 业务状态码. 0正常, >0失败
    ret:int;
    // 返回信息
    msg:[ubyte];
}

root_type Req;
root_type Resp;
namespace MyGame.Sample;
  
table Weapon {
  name:string;
  damage:short;
}
 
root_type Weapon;

转化后形成的C#版协议文件

// <auto-generated>
//  automatically generated by the FlatBuffers compiler, do not modify
// </auto-generated>

namespace MyGame.Sample
{

using global::System;
using global::System.Collections.Generic;
using global::FlatBuffers;

public struct Req : IFlatbufferObject
{
  private Table __p;
  public ByteBuffer ByteBuffer { get { return __p.bb; } }
  public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_2_0_0(); }
  public static Req GetRootAsReq(ByteBuffer _bb) { return GetRootAsReq(_bb, new Req()); }
  public static Req GetRootAsReq(ByteBuffer _bb, Req obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); }
  public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); }
  public Req __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }

  public int Id { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int)0; } }
  public int Code { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int)0; } }
  public byte Msg(int j) { int o = __p.__offset(8); return o != 0 ? __p.bb.Get(__p.__vector(o) + j * 1) : (byte)0; }
  public int MsgLength { get { int o = __p.__offset(8); return o != 0 ? __p.__vector_len(o) : 0; } }
#if ENABLE_SPAN_T
  public Span<byte> GetMsgBytes() { return __p.__vector_as_span<byte>(8, 1); }
#else
  public ArraySegment<byte>? GetMsgBytes() { return __p.__vector_as_arraysegment(8); }
#endif
  public byte[] GetMsgArray() { return __p.__vector_as_array<byte>(8); }

  public static Offset<MyGame.Sample.Req> CreateReq(FlatBufferBuilder builder,
      int id = 0,
      int code = 0,
      VectorOffset msgOffset = default(VectorOffset)) {
    builder.StartTable(3);
    Req.AddMsg(builder, msgOffset);
    Req.AddCode(builder, code);
    Req.AddId(builder, id);
    return Req.EndReq(builder);
  }

  public static void StartReq(FlatBufferBuilder builder) { builder.StartTable(3); }
  public static void AddId(FlatBufferBuilder builder, int id) { builder.AddInt(0, id, 0); }
  public static void AddCode(FlatBufferBuilder builder, int code) { builder.AddInt(1, code, 0); }
  public static void AddMsg(FlatBufferBuilder builder, VectorOffset msgOffset) { builder.AddOffset(2, msgOffset.Value, 0); }
  public static VectorOffset CreateMsgVector(FlatBufferBuilder builder, byte[] data) { builder.StartVector(1, data.Length, 1); for (int i = data.Length - 1; i >= 0; i--) builder.AddByte(data[i]); return builder.EndVector(); }
  public static VectorOffset CreateMsgVectorBlock(FlatBufferBuilder builder, byte[] data) { builder.StartVector(1, data.Length, 1); builder.Add(data); return builder.EndVector(); }
  public static void StartMsgVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(1, numElems, 1); }
  public static Offset<MyGame.Sample.Req> EndReq(FlatBufferBuilder builder) {
    int o = builder.EndTable();
    return new Offset<MyGame.Sample.Req>(o);
  }
};

public struct Resp : IFlatbufferObject
{
  private Table __p;
  public ByteBuffer ByteBuffer { get { return __p.bb; } }
  public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_2_0_0(); }
  public static Resp GetRootAsResp(ByteBuffer _bb) { return GetRootAsResp(_bb, new Resp()); }
  public static Resp GetRootAsResp(ByteBuffer _bb, Resp obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); }
  public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); }
  public Resp __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }

  public int Id { get { int o = __p.__offset(4); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int)0; } }
  public int Code { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int)0; } }
  public int Ret { get { int o = __p.__offset(8); return o != 0 ? __p.bb.GetInt(o + __p.bb_pos) : (int)0; } }
  public byte Msg(int j) { int o = __p.__offset(10); return o != 0 ? __p.bb.Get(__p.__vector(o) + j * 1) : (byte)0; }
  public int MsgLength { get { int o = __p.__offset(10); return o != 0 ? __p.__vector_len(o) : 0; } }
#if ENABLE_SPAN_T
  public Span<byte> GetMsgBytes() { return __p.__vector_as_span<byte>(10, 1); }
#else
  public ArraySegment<byte>? GetMsgBytes() { return __p.__vector_as_arraysegment(10); }
#endif
  public byte[] GetMsgArray() { return __p.__vector_as_array<byte>(10); }

  public static Offset<MyGame.Sample.Resp> CreateResp(FlatBufferBuilder builder,
      int id = 0,
      int code = 0,
      int ret = 0,
      VectorOffset msgOffset = default(VectorOffset)) {
    builder.StartTable(4);
    Resp.AddMsg(builder, msgOffset);
    Resp.AddRet(builder, ret);
    Resp.AddCode(builder, code);
    Resp.AddId(builder, id);
    return Resp.EndResp(builder);
  }

  public static void StartResp(FlatBufferBuilder builder) { builder.StartTable(4); }
  public static void AddId(FlatBufferBuilder builder, int id) { builder.AddInt(0, id, 0); }
  public static void AddCode(FlatBufferBuilder builder, int code) { builder.AddInt(1, code, 0); }
  public static void AddRet(FlatBufferBuilder builder, int ret) { builder.AddInt(2, ret, 0); }
  public static void AddMsg(FlatBufferBuilder builder, VectorOffset msgOffset) { builder.AddOffset(3, msgOffset.Value, 0); }
  public static VectorOffset CreateMsgVector(FlatBufferBuilder builder, byte[] data) { builder.StartVector(1, data.Length, 1); for (int i = data.Length - 1; i >= 0; i--) builder.AddByte(data[i]); return builder.EndVector(); }
  public static VectorOffset CreateMsgVectorBlock(FlatBufferBuilder builder, byte[] data) { builder.StartVector(1, data.Length, 1); builder.Add(data); return builder.EndVector(); }
  public static void StartMsgVector(FlatBufferBuilder builder, int numElems) { builder.StartVector(1, numElems, 1); }
  public static Offset<MyGame.Sample.Resp> EndResp(FlatBufferBuilder builder) {
    int o = builder.EndTable();
    return new Offset<MyGame.Sample.Resp>(o);
  }
  public static void FinishRespBuffer(FlatBufferBuilder builder, Offset<MyGame.Sample.Resp> offset) { builder.Finish(offset.Value); }
  public static void FinishSizePrefixedRespBuffer(FlatBufferBuilder builder, Offset<MyGame.Sample.Resp> offset) { builder.FinishSizePrefixed(offset.Value); }
};


}
// <auto-generated>
//  automatically generated by the FlatBuffers compiler, do not modify
// </auto-generated>

namespace MyGame.Sample
{

using global::System;
using global::System.Collections.Generic;
using global::FlatBuffers;

public struct Weapon : IFlatbufferObject
{
  private Table __p;
  public ByteBuffer ByteBuffer { get { return __p.bb; } }
  public static void ValidateVersion() { FlatBufferConstants.FLATBUFFERS_2_0_0(); }
  public static Weapon GetRootAsWeapon(ByteBuffer _bb) { return GetRootAsWeapon(_bb, new Weapon()); }
  public static Weapon GetRootAsWeapon(ByteBuffer _bb, Weapon obj) { return (obj.__assign(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); }
  public void __init(int _i, ByteBuffer _bb) { __p = new Table(_i, _bb); }
  public Weapon __assign(int _i, ByteBuffer _bb) { __init(_i, _bb); return this; }

  public string Name { get { int o = __p.__offset(4); return o != 0 ? __p.__string(o + __p.bb_pos) : null; } }
#if ENABLE_SPAN_T
  public Span<byte> GetNameBytes() { return __p.__vector_as_span<byte>(4, 1); }
#else
  public ArraySegment<byte>? GetNameBytes() { return __p.__vector_as_arraysegment(4); }
#endif
  public byte[] GetNameArray() { return __p.__vector_as_array<byte>(4); }
  public short Damage { get { int o = __p.__offset(6); return o != 0 ? __p.bb.GetShort(o + __p.bb_pos) : (short)0; } }

  public static Offset<MyGame.Sample.Weapon> CreateWeapon(FlatBufferBuilder builder,
      StringOffset nameOffset = default(StringOffset),
      short damage = 0) {
    builder.StartTable(2);
    Weapon.AddName(builder, nameOffset);
    Weapon.AddDamage(builder, damage);
    return Weapon.EndWeapon(builder);
  }

  public static void StartWeapon(FlatBufferBuilder builder) { builder.StartTable(2); }
  public static void AddName(FlatBufferBuilder builder, StringOffset nameOffset) { builder.AddOffset(0, nameOffset.Value, 0); }
  public static void AddDamage(FlatBufferBuilder builder, short damage) { builder.AddShort(1, damage, 0); }
  public static Offset<MyGame.Sample.Weapon> EndWeapon(FlatBufferBuilder builder) {
    int o = builder.EndTable();
    return new Offset<MyGame.Sample.Weapon>(o);
  }
  public static void FinishWeaponBuffer(FlatBufferBuilder builder, Offset<MyGame.Sample.Weapon> offset) { builder.Finish(offset.Value); }
  public static void FinishSizePrefixedWeaponBuffer(FlatBufferBuilder builder, Offset<MyGame.Sample.Weapon> offset) { builder.FinishSizePrefixed(offset.Value); }
};


}

至此整个TCP网络通信工具实现完成,下一篇文章介绍测试案例!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Data菌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值