【Unity】 在Unity中实现Tcp通讯(4)———心跳机制

前几篇内容,分别阐述了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;
}

显然用这种方式是不足以说明客户端真的已经断开连接的

因此这里就需要引入心跳机制来解决这个断开问题。

一.心跳机制

所谓心跳机制,就是服务端每隔一段时间向客户端发送一个空包

若客户端不能在规定时间内做出应答则认为连接已断开。

而且心跳机制这个名字也非常具有象征意义,它就像人的心脏跳动一样,按照一定频率不断的去确认客户端是否还“活着”。

人的心脏如果不跳了,那么人肯就是死了,而客户端如果不能做出应答,也就像人的心脏停止跳动一样,“死了”。

下面就在第二篇的代码的基础上实现心跳机制。

二.实现心跳机制

1.心跳计时器

既然是按照一定频率来发送空包,肯定就要使用计时器去触发了,这里直接构造一个System.Threading.Timer的计时器对象,每隔15000毫秒(15秒)触发一次

m_HeartBitTimer = new Timer(HeartBit, 0, 0, 15000);

2.使用时间戳来计算心跳间隔时间

上面说过,心跳需要每隔一段时间触发一次,因此这里需要采用时间戳来计算触发时间。

时间戳就是从过去到现在的一段时间,使用两个时间戳相减即可得到这中间流逝的时间。

比如服务器启动时间是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秒。

3.收包处理

因为这里把编号为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框架,资源管理框架,优化等内容的文章。

猜你喜欢

转载自blog.csdn.net/s_GQY/article/details/106635913