上篇介绍了传输层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网络通信工具实现完成,下一篇文章介绍测试案例!