最近接触的项目,是一个棋牌游戏,棋牌游戏需要在前端进行一些操作,然后向服务器发送数据包,在服务器接收到客户端的数据后,会在服务端进行一系列的判断之后,然后发送给客户端一个返回数据,客户端接收到这个返回数据后,需要作出一系列的响应。那么,就针对于这一整个服务器<--->客户端的通讯过程,看看是如何来实现的。

1. 报文(消息结构体)的商定。

 客户端向服务器发送消息,客户端接收服务器的消息,都是一系列的数据,那么这些数据到底是代表了什么,应该以什么样的方式解读?这些东西,在刚开始开发的时候,客户端与服务器就需要定好相关的内容,这个东西呢,也就是消息报文啦!在消息报文中,会定义一些常用的东西:

  a、游戏状态

  b、游戏通讯协议定义

  c、游戏具体的数据包结构

     /// <summary>
/// 游戏状态定义
/// </summary>
public enum GameState : byte
{
GS_WAIT_SETGAME = , GS_WAIT_AGREE = , GS_NOTE_STATE = ,
} /// <summary>
/// 游戏通讯协议定义
/// </summary>
public struct ProtocolID
{
public const int S_C_IS_SUPER_USER = ;//超端用户消息
public const int C_S_SUPER_SET = ;//超端设置
public const int S_C_SUPER_SET_RESULT = ;//超端设置结果
} /// <summary>
///游戏基础数据
/// </summary>
[System.Serializable]
[StructLayout(LayoutKind.Sequential, Pack = ClientPlat.PlatFormConfig.HallPack)]
public struct GameStationBase
{
/// <summary>
///上庄列表
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = GameConst.PlayerCount)]
public byte[] byZhuangList;
/// <summary>
/// 路子信息
/// </summary>
[MarshalAs(UnmanagedType.ByValArray, SizeConst = GameConst.GameMaxCount)]
public LuziData[] TLuziData; public int iXiaZhuTime; /// 下注时间
public int iKaiPaiTime; /// 开牌时间
public int iFreeTime; /// 空闲时间
public int iShowWinTime; /// 显示中奖时间
public int iNtStation; //庄家位置
public int iNtPlayCount; //庄家坐庄次数
public long i64NtMoney; //庄家金币
public long i64NtWinMoney; //庄家输赢情况
public long i64UserWin; //个人输赢
public long i64MyMoney; //个人金币数 -从服务端发送过去
public long i64UserMaxNote; ///玩家最大下注数
public long i64ShangZhuangLimit; /// 上庄需要的最少金币 }

说明:消息报文需要严格遵循前后关系,如果客户端与服务器消息报文顺序不一致,那么会导致数据混乱。

2. 消息报文确定以后,需要根据消息报文脚本中确定的ProtocolID来定义对应的事件,而且这些事件都会实现某一个接口,方便事件中心触发。

  public class GameStationBase_Event : IEvent
{
public Enum EventType
{
get
{
return DataEventID.GameStationBase;
}
} public GameStationBase data; public GameStationBase_Event(GameStationBase _data)
{
data = _data;
} public virtual string ToSring()
{
string msg = string.Format("EventType:{0}", DataEventID.GameStationBase.ToString());
return msg;
} public void DestroySelf()
{
}
}

  a、从上面的代码可以看到,这个GameStationBase_Event 类实现了接口IEvent,并且实现了它的DestroySelf方法,这样的好处后面点儿会讲到。

  b、GameStationBase_Event 有一个public的枚举类型EventType 变量,并且直接返回的就是 消息报文中的“游戏通讯协议定义”,这样一来,GameStationBase_Event 就与“游戏通讯协议定义”中的每一个枚举一一对应。

  c、GameStationBase_Event还有一个 GameStationBase 类型的变量data,同时还有一个有参的构造器,直接初始化变量data,这样一来,GameStationBase_Event 就与“游戏具体的数据包结构”一一对应了。

  废话了这么多,其实也就是一句话,一个 事件类,对应着一个游戏通讯协议与一个消息结构体,并且实现了一个通用的接口IEvent。

3、既然消息该如何发送,该如何接受解析已经定义好了,那么接下来是不是就该发送接受消息了呢?没错,接下来,就开始定义我们向服务器发送消息时该做些什么,接收到服务器的消息时,我们又该做些什么。

  接下来,顶一个消息处理脚本,叫做 GameLogicTable,至于为什么叫这个,我也不知道 - -!

  

  d、其它的东西,比如来个静态的实例啊什么的,见代码:

  public class GameLogicTable : GameLogicBase
{ //声明一个静态实例
private static GameLogicTable instance;
//公有的静态实例获取方法
public static GameLogicTable Instance
{
get
{
if (instance == null)
{
instance = new GameLogicTable(, );
}
return instance;
} } /// <summary>
/// 通讯协议(服务器==》客户端)
/// </summary>
/// <param name="data"></param>
/// <param name="bAssistantID"></param>
private void ProcessGameLogic(byte[] data, uint bAssistantID)
{
//Debug.LogError("========数据接受处理=======>>" + bAssistantID.ToString());
switch (bAssistantID)
{
case ProtocolID.S_C_APPLY_ZHUANG_RESULT:
{
S_C_ApplyZhuangResult ndata = new S_C_ApplyZhuangResult();
ndata = (S_C_ApplyZhuangResult)HNNetToolKit.Instance.BytesToStruct(data, ndata.GetType());
ApplyZhuangResult_Event evt = new ApplyZhuangResult_Event(ndata);
EventCenter.Instance.TriggerEvent(evt);
}
break;
default:
break;
}
}
/// <summary>
/// 初始化游戏
/// </summary>
/// <param name="data">Data.</param>
public void InitGameState(byte[] data)
{ GameState gs = (GameState)base.GameStatus;
//Debug.LogError("=========数据接收=========>>"+ gs.ToString()); switch (gs)
{
case GameState.GS_WAIT_SETGAME://无庄等待状态
case GameState.GS_WAIT_AGREE:
case GameState.GS_WAIT_NEXT:
{
GameStationBase pState = new GameStationBase();
if (!HNNetToolKit.Instance.AssertSize(data.Length, pState.GetType())) return;
pState = (GameStationBase)HNNetToolKit.Instance.BytesToStruct(data, pState.GetType());
GameStationBase_Event evt = new GameStationBase_Event(pState);
EventCenter.Instance.TriggerEvent(evt);
}
break;
}
}
/// <summary>
/// 通讯协议(客户端==》服务端)
/// </summary>
#region //超端设置
public void SuperSet(SuperUserSetData node)
{
SendData(ProtocolID.C_S_SUPER_SET,node);
}

  a、客户端--->服务端:向客户端发送一个消息ID,然后附上对应结构的数据node。

  b、服务端--->客户端:根据接收到的消息ID,来做出对应的处理:

    1.新建一个对应消息ID的数据报文 ndata;

    2.将接受到的消息data的内容赋值给ndata;

    3.新建一个对应消息ID的 事件类,并且将ndata作为参数传递给构造器。

    4.使用事件中心,触发事件。(为什么不在这里处理,还要添加事件呢?)

4. 现在虽然定义好了收到消息与发送消息的行为,那么现在还差一个东西,那就是接受到服务器消息时,触发事件的EventCenter,这个EventCenter的作用很简单,就是让需要监听服务器消息的地方可以注册监听,然后当服务器发送对应消息时,就触发事件即可。那么这个EventCenter是如何设计的呢?

  a、首先,增加一个Dictionary<enum,IEventHandlerManger> 类型的 eventHandlers,用来保存消息ID与对应 要触发的 方法。

  b、然后,增加一个HNSafeQueue<IEvent> 类型eventQueue,用来保存需要触发的事件列表。

  c、写一个public的Addlistener(enum,IEventHandlerManger)方法,用于事件的注册

  d、写一个public的TriggerEvent(IEvent eve)方法,用于触发事件。在这个TriggerEvent方法中,其实就是将eve传入到eventQueue队列中,等待被触发。

  e、写一个BroadcastEvent()方法,该方法就是将eventQueue队列中的eve事件一个个取出,然后调用封装好的事件触发方法触发事件回调。

  e、在该脚本的Updata() 方法中,一直调用BroadcastEvent()方法。

  f、其它一些逻辑处理,例如初始化啦、清空啦,删除事件注册啦等等,这里就不一一介绍了。

 public class EventCenter : HNBehaviourSingleton<EventCenter>, IEventCenter
{
/// <summary>
/// 保存事件监听处理列表,对应说明a
/// </summary>
private Dictionary<Enum, IEventHandlerManger> eventHandlers = new Dictionary<Enum, IEventHandlerManger>(); /// <summary>
/// 事件队列,对应说明b
/// </summary>
private HNSafeQueue<IEvent> eventQueue = new HNSafeQueue<IEvent>(); //对应说明f
public void Update()
{
BoardCastEvent();
} //广播事件方法,对应说明中的e
public void BoardCastEvent()
{
if ( eventQueue.Count < )
{
return;
}
IEvent evt = eventQueue.Dequeue();
BoardCastEvent(evt);
}
//触发事件方法,对应说明中的d
public void TriggerEvent(IEvent evt)
{
string msg = string.Format("TriggerEvent :{0}", evt.ToSring());
HNLogger.Log(msg);
this.eventQueue.Enqueue(evt);
} //注册事件监听方法
public bool AddEventListener(Enum eventType, EventHandle eventHandle, bool isPermanently = false)
{
bool flag = false;
if ( !this.eventHandlers.ContainsKey(eventType) )
{
this.eventHandlers[eventType] = new EventReceiver();
}
flag = this.eventHandlers[eventType].AddHandler(eventHandle);
}
return flag;
}

5.到了这里,其实就可以知道为什么在 定义事件类的时候,需要让事件类来实现IEvent接口了,因为当接收到服务器消息时,会通过EventCenter的 TriggerEvent(IEvent eve)方法将接受到的数据转化为对应的事件类,然后加入到eventQueue队列中,而加入

eventQueue队列,则需要是IEvent接口类型的方可,所以定义事件类型的时候必须要实现IEvent接口方可。

最后附上一张关系图:

Unity中接收服务器消息并广播事件的实现的更多相关文章

  1. Unity中使用C#实现UDP广播

    没有系统的学习过网络,想做联机游戏还真是费劲,想做在局域网内实现自动搜索服务器的功能,然后就想到了使用UDP进行广播,把服务器的信息广播给每一个玩家. Socket udpSocket = new S ...

  2. 关于Unity中从服务器下载资源压缩包AssetBundle的步骤

    AssetBundle 1: 在Unity中,能为用户存储资源的一种压缩格式的打包集合,他可以存任意一种Unity引擎可以识别的资源: 模型,音频,纹理图,动画, 开发者自定义的二进制文件; 2: 这 ...

  3. C#使用ProtocolBuffer(ProtoBuf)进行Unity中的Socket通信

    首先来说一下本文中例子所要实现的功能: 基于ProtoBuf序列化对象 使用Socket实现时时通信 数据包的编码和解码 下面来看具体的步骤: 一.Unity中使用ProtoBuf 导入DLL到Uni ...

  4. Unity中嵌入网页插件Embedded Browser2.1.0

    背景 最近刚换了工作,新公司不是做手游的,一开始有点抵触,总觉得不是做游戏自己就是跨行了,认为自己不对口,但是慢慢发现在这可以学的东西面很广,所以感觉又到了打怪升级的时候了,老子就在这进阶了. 一进公 ...

  5. ZeroMQ接口函数之 :zmq_recvmsg – 从一个socket上接收一个消息帧

    ZeroMQ 官方地址 :http://api.zeromq.org/4-1:zmq-recvmsg zmq_recvmsg(3)         ØMQ Manual - ØMQ/4.1.0 Nam ...

  6. 【Unity游戏开发】用C#和Lua实现Unity中的事件分发机制EventDispatcher

    一.简介 最近马三换了一家大公司工作,公司制度规范了一些,因此平时的业余时间多了不少.但是人却懒了下来,最近这一个月都没怎么研究新技术,博客写得也是拖拖拉拉,周六周天就躺尸在家看帖子.看小说,要么就是 ...

  7. 关于Unity中NGUI的Checkbox复选框、Slider滑动条和Button的6种触发回调事件的方式

    Checkbox复选框 1.创建一个NGUI背景Sprite1节点 2.打开NGUI---->Open---->Prefab Toolbar---->选择一个复选框节点,拖拽到背景节 ...

  8. 关于Unity中的Input输入事件

    截获鼠标,键盘的消息 监听事件我们都是在Update里面监听的. Unity的虚拟轴打开:Edit-->Project Settings-->Input,打开的各个Name就是双引号里面要 ...

  9. Java开发微信公众号(四)---微信服务器post消息体的接收及消息的处理

    在前几节文章中我们讲述了微信公众号环境的搭建.如何接入微信公众平台.以及微信服务器请求消息,响应消息,事件消息以及工具处理类的封装:接下来我们重点说一下-微信服务器post消息体的接收及消息的处理,这 ...

随机推荐

  1. .Net Core中简单使用MongoDB

    MongoDB 是由C++语言编写的,是一个基于分布式且面向文档存储的开源数据库系统. 下载地址: https://www.mongodb.com/download-center/community ...

  2. JavaScript动画实例:炸开的小球

    1.炸开的小球 定义一个小球对象类Ball,它有6个属性:圆心坐标(x,y).小球半径radius.填充颜色color.圆心坐标水平方向的变化量speedX.圆心坐标垂直方向的变化量speedY. B ...

  3. JS DOM笔记

    js的组成     ECMAScript:JS的语法     DOM:页面文档对象模型     BOM:浏览器对象模型     web APIs     是浏览器提供的一套操作浏览器功能和页面元素的A ...

  4. 内存总是不够?HBase&GeoMesa配置优化了解一下

    概况: 生产环境HBase集群内存经常处于高位(90%),而且GC之后也是内存依然处于高位,经分析内存全部由集群的regionserver进程所持有,,经常重启之后,大概3-4天就会保持在高位.由上述 ...

  5. java 静态导入、可变参数、集合嵌套

    一 静态导入 在导包的过程中我们可以直接导入静态部分,这样某个类的静态成员就可以直接使用了. 在源码中经常会出现静态导入. 静态导入格式: import static XXX.YYY;   导入后YY ...

  6. 2020-05-18:MYSQL为什么用B+树做索引结构?平时过程中怎么加的索引?

    福哥答案2020-05-18:此答案来自群员:因为4.0成型那个年代,B树体系大量用于文件存储系统,甚至当年的Longhorn的winFS都是基于b树做索引,开源而且好用的也就这么个体系了.B+树的磁 ...

  7. 获取异常具体信息 尤其是运行时异常例如NullPointerException 比e.getMessage()更详细

    ///打印异常信息 尤其是运行时异常 比getMessage()更详细public static String getMessageInfo(Exception e){ OutputStream op ...

  8. 关于vector的erase删除操作的两种不同方法,在linux与visual studio的实现讨论

    关于vector的erase删除操作的两种不同方法,在linux与visual studio的实现讨论 1.前言: 最近在做某一个题时,用到了vector的删除操作,利用的是erase()函数删除符合 ...

  9. JavaScript基础-02

    1. 六种数据类型: string字符串:number数值:boolean布尔值:null空值:undefined 未定义:object对象 基本数据类型(值类型): string字符串:number ...

  10. Linux内核之 进程管理

    正如上一篇我们提到过,进程是Linux系统中仅次于文件的基本抽象概念.正在运行的进程不仅仅是二进制代码,而是数据.资源.状态和虚拟的计算机组成.我们今天主要介绍进程的概念,组成,运行状态和生命周期等. ...