UN2L7——实现心跳消息

心跳消息

所谓心跳消息,就是在长连接中,客户端和服务端之间定期发送的一种特殊的数据包,用于通知对方自己还在线,以确保长连接的有效性
由于其发送的时间间隔往往是固定的持续的,就像是心跳一样一直存在,所以我们称之为心跳消息

心跳消息是长连接项目中必备的一套逻辑规则,通过它可以帮助我们在服务器端及时的释放掉失效的socket
可以有效避免当客户端非正常关闭时,服务器端不能及时判断连接已断开

为什么需要心跳消息

  1. 避免非正常关闭客户端时,服务器无法正常收到关闭连接消息,通过心跳消息我们可以自定义超时判断,
    如果超时没有收到客户端消息,证明客户端已经断开连接
  2. 避免客户端长期不发送消息,防火墙或者路由器会断开连接,我们可以通过心跳消息一直保持活跃状态

实现心跳消息

首先声明一个不包括消息体的心跳消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class HeartMessage : BaseMessage
{
public override int GetBytesNum()
{
return 8;
}

public override byte[] Writeing()
{
int index = 0;
byte[] bytes = new byte[GetBytesNum()];
WriteInt(bytes, GetID(), ref index);
WriteInt(bytes, 0, ref index);
return bytes;
}

public override int Reading(byte[] bytes, int BeginIndex = 0)
{
return 0;
}

public override int GetID()
{
return 999;
}
}
  • 客户端,主要功能:定时发送消息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //客户端 NetManager类内部
    private HeartMessage heartMsg = new HeartMessage();

    private void Awake()
    {
    instance = this;
    DontDestroyOnLoad(gameObject);
    InvokeRepeating(nameof(SendHeartMsg), 0, SEND_HEART_MSG_TIME); //客户端循环定时给服务端发送心跳消息
    }

    private void SendHeartMsg()
    {
    if (isConnented)
    Send(heartMsg);
    }
  • 服务器,主要功能:不停检测上次收到某客户端消息的时间,如果超时则认为连接已经断开

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    //服务器端 ClientSocket类内部
    //超时时间
    private static int TIME_OUT_TIME = 10;

    public void Receive()
    {
    if (!Connented)
    {
    Program.socket.AddDelSocket(this);
    return;
    }
    try
    {
    if (socket.Available > 0)
    {
    byte[] result = new byte[1024 * 5];
    int receiveNum = socket.Receive(result);
    HandleReceiveMsg(result, receiveNum);
    }
    //间隔一段时间,检测一次超时,如果超时,就主动断开该客户端的连接
    CheckTimeOut();
    }
    catch (Exception e)
    {
    Console.WriteLine($"从客户端{clientID}接收消息失败:{e.Message}");
    //如果解析错误,也认为要把这个socket断开
    Program.socket.AddDelSocket(this);
    }
    }

    //间隔一段时间,检测一次超时,如果超时,就主动断开该客户端的连接
    private void CheckTimeOut()
    {
    //如果接收到心跳消息的时间超过了超时时间,就断开连接
    if (frontTime != -1
    && DateTime.Now.Ticks / TimeSpan.TicksPerSecond - frontTime >= TIME_OUT_TIME)
    {
    Program.socket.AddDelSocket(this);
    }
    }