赞
踩
前几篇内容,分别阐述了Unity中实现Tcp通讯的客户端、服务端的实现以及引入ProtoBuf进行数据序列化的方式。
这篇来写一下心跳机制。
在第二篇内容中,对于客户端是否断开连接,是通过判断接受到的数据流是否是空来判断连接是否断开。
代码是这样的:
- int length = m_Socket.EndReceive(ir);
- if(length < 1)
- {
- IPEndPoint endPoint = m_Socket.RemoteEndPoint as IPEndPoint;
- Console.WriteLine("客户端:" + endPoint.Address.ToString() + "已断开连接");
- Close();
- return;
- }
显然用这种方式是不足以说明客户端真的已经断开连接的
因此这里就需要引入心跳机制来解决这个断开问题。
所谓心跳机制,就是服务端每隔一段时间向客户端发送一个空包
若客户端不能在规定时间内做出应答则认为连接已断开。
而且心跳机制这个名字也非常具有象征意义,它就像人的心脏跳动一样,按照一定频率不断的去确认客户端是否还“活着”。
人的心脏如果不跳了,那么人肯就是死了,而客户端如果不能做出应答,也就像人的心脏停止跳动一样,“死了”。
下面就在第二篇的代码的基础上实现心跳机制。
既然是按照一定频率来发送空包,肯定就要使用计时器去触发了,这里直接构造一个System.Threading.Timer的计时器对象,每隔15000毫秒(15秒)触发一次
m_HeartBitTimer = new Timer(HeartBit, 0, 0, 15000);
上面说过,心跳需要每隔一段时间触发一次,因此这里需要采用时间戳来计算触发时间。
时间戳就是从过去到现在的一段时间,使用两个时间戳相减即可得到这中间流逝的时间。
比如服务器启动时间是2020年6月9日14点30分0秒,那么:
从服务器启动到当前所经过的时间 = 从1970年1月1日0分0秒到当前的时间 - 从1970年1月1日0分0秒到2020年6月9日14点30分0秒。
若心跳每隔15秒触发一次,则这个差值大于等于15的时候就开始执行心跳逻辑。
采用时间戳的好处就是,不需要实时累计,在需要的时候计算一下即可得到精确的差值。
而实时累加由于精度问题,往往会产生一定误差,且需要不断累计比较繁琐。
- private void HeartBit(object state)
- {
- TimeSpan ts = DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0);
- if (m_TimeStamp == 0) m_TimeStamp = ts.TotalSeconds;
-
- if(ts.TotalSeconds - m_TimeStamp > DIS_CONNECT_TIME)
- {
- if (!m_IsCheckHeart)
- {
- m_IsCheckHeart = true;
- m_TimeStamp = ts.TotalSeconds;
- Send(1, new byte[0]);
- }
- else
- {
- Close();
- }
- }
- }
这里DateTime.Now - new DateTime(1970, 1, 1, 0, 0, 0, 0)即是从1970年1月1日0点0分0秒开始到目前为止的一段时间,使用秒做单位
m_TimeStamp是一个标志位,它记录的是一次心跳发生之前的时间,ts.ToalSecondes - m_TimeStamp也就是当前时间和一次心跳发生前的时间的差值,即从一次心跳之前到现在过去了多久。 DIS_CONNECT_TIME就是一个触发时间的常量,这里定义为15秒。
因为这里把编号为1的包作为心跳包,所以在收包的时候要对编号为1的包进行特殊处理:收到1号包的时候把时间戳归0,等待下一次心跳触发。
- private void CheckReceiveBuffer(object state)
- {
- lock (m_ReceiveQueue)
- {
- if (m_ReceiveQueue.Count < 1) return;
- byte[] buffer = m_ReceiveQueue.Dequeue();
- byte[] msgContent = new byte[buffer.Length - 2];
- ushort msgCode = 0;
-
- using (MemoryStream ms = new MemoryStream(buffer))
- {
- byte[] msgCodeBuffer = new byte[2];
- ms.Read(msgCodeBuffer, 0, msgCodeBuffer.Length);
- msgCode = BitConverter.ToUInt16(msgCodeBuffer, 0);
- ms.Read(msgContent, 0, msgContent.Length);
- }
-
- if (msgCode == 1)//若收到编号为1的心跳包,则把时间戳归0,等待下一次心跳触发
- {
- m_IsCheckHeart = false;
- m_TimeStamp = 0;
- }
- else
- {
- text content = ProtoBufUtil.BytesToObject<text>(msgContent, 0, msgContent.Length);
- Console.WriteLine("消息编号:" + msgCode + ",内容:" + content.content);
- }
- }
- }
好了,心跳到这里也写完了。
Unity Tcp部分我写了4篇文章,算是把整个流程说了大概。
总的来说,这也是对我自己关于这部分知识的一个梳理。
接下来我会写一些关于unity中的行为树,状态机,UI框架,资源管理框架,优化等内容的文章。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。