精华内容
下载资源
问答
  • 这是一个用python 写的利用udp 发送心跳包的程序,最初是为一个使用树莓派运行的机器人系统实时状态监控做的。服务端用的是C#,整个监控程序运行非常稳定,现把心跳部份公开希望能混点分数 ^_^
  • 心跳包实现

    2018-04-27 11:49:36
    C/S实操项目中很核心的一部分代码,可独立运行,希望能帮到大家
  • winform socket通讯和心跳包,
  • Java心跳包功能TCP实现

    2017-11-07 11:37:16
    Java心跳包功能的实现,tcp协议的,包含客户端和服务端
  • Nodejs简易心跳包

    2017-02-09 18:39:34
    http://blog.csdn.net/gzy11/article/details/54949531 此篇文章示列代码
  • 基于TCP协议的心跳包

    2018-09-19 16:03:42
    将资料client.c文件的IP地址,修改成虚拟机当前环境下的IP地址即可实现,开发板与虚拟机之间的信息交互
  • 实操项目中切割下来的一部分,功能完整可实现,希望能帮到大家。
  • 代码调试成功,适用于Linux环境,可实现服务器与客户端定时交互,判断网络是否掉线,定时间隔可修改客户端itemval相关参数
  • Java心跳包功能实现

    2015-11-18 19:36:30
    实现了心跳包的接收 和 发送功能 代码包含客户端和服务器端
  • socket 长连接 简单例子,适合初学的朋友,里面有多线程 实现的,包括心跳包,数据分为两部分传送,首先双方约定用一个4字节的数组告诉对方要传送数据的长度,然后在写入数据,这样长连接的时候,双方可以知道对方...
  • TCP通信心跳包源码C语言实现,基于Linux平台写的,可以整合到自己的代码中去。
  • 心跳包

    千次阅读 2019-03-07 21:32:52
    心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定义的命令字,按照一定的时间间隔发送,类似于心跳,所以叫做心跳包。    用来判断对方(设备,进程或其它网元)是否正常运行,采用定时...


    心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定义的命令字,按照一定的时间间隔发送,类似于心跳,所以叫做心跳包。

       
    用来判断对方(设备,进程或其它网元)是否正常运行,采用定时发送简单的通讯包,如果在指定时间段内未收到对方响应,则判断对方已经离线。用于检测TCP的异常断开。基本原因是服务器端不能有效的判断客户端是否在线,也就是说,服务器无法区分客户端是长时间在空闲,还是已经掉线的情况。所谓的心跳包就是客户端定时发送简单的信息给服务器端告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息则视客户端断开。

    比如有些通信软件长时间不使用,要想知道它的状态是在线还是离线就需要心跳包,定时发包收包。发包方:可以是客户也可以是服务端,看哪边实现方便合理,一般是客户端。服务器也可以定时发心跳下去。一般来说,出于效率的考虑,是由客户端主动向服务器端发包,而不是服务器向客户端发。客户端每隔一段时间发一个包,使用TCP的,用send发,使用UDP的,用sendto发,服务器收到后,就知道当前客户端还处于“活着”的状态,否则,如果隔一定时间未收到这样的包,则服务器认为客户端已经断开,进行相应的客户端断开逻辑处理。

    服务器实现心跳机制的两种策略

    大部分CS的应用需要心跳机制。心跳机制一般在Server和Client都要实现,两者实现原理基本一样。Client不关心性能,怎么做都行。

    如果应用是基于TCP的,可以简单地通过SO_KEEPALIVE实现心跳。TCP在设置的KeepAlive定时器到达时向对端发一个检测TCP segment,如果没收到ACK或RST,尝试几次后,就认为对端已经不存在,最后通知应用程序。这里有个缺点是,Server主动发出检测包,对性能有点影响。

    应用自己实现

    Client启动一个定时器,不断发心跳;

    Server收到心跳后,给个回应;

    Server启动一个定时器,判断Client是否存在,判断方法这里列两种:时间差和简单标志。

    1. 时间差策略

    收到一个心跳后,记录当前时间(记为recvedTime)。

    判断定时器时间到达,计算多久没收到心跳的时间(T)=当前时间 - recvedTime(上面记录的时间)。如果T大于某个设定值,就可以认为Client超时了。

    2. 简单标志

    收到一个心跳后,设置连接标志为true;

    判断定时器时间到达,查看所有的标志,false的,认为对端超时了;true的将其设成false。

    上面这种方法比上面简单一些,但检测某个Client是否离线的误差有点大。

    您还有心跳吗?超时机制分析

    问题描述

    在C/S模式中,有时我们会长时间保持一个连接,以避免频繁地建立连接,但同时,一般会有一个超时时间,在这个时间内没发起任何请求的连接会被断开,以减少负载,节约资源。并且该机制一般都是在服务端实现,因为client强制关闭或意外断开连接,server端在此刻是感知不到的,如果放到client端实现,在上述情况下,该超时机制就失效了。本来这问题很普通,不太值得一提,但最近在项目中看到了该机制的一种糟糕的实现,故在此深入分析一下。

    问题分析及解决方案

    服务端一般会保持很多个连接,所以,一般是创建一个定时器,定时检查所有连接中哪些连接超时了。此外我们要做的是,当收到客户端发来的数据时,怎么去刷新该连接的超时信息?

    最近看到一种实现方式是这样做的:

    
     
    1. public class Connection {
    2. private long lastTime;
    3. public void refresh () {
    4. lastTime = System.currentTimeMillis();
    5. }
    6. public long getLastTime () {
    7. return lastTime;
    8. }
    9. //......
    10. }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在每次收到客户端发来的数据时,调用refresh方法。

    然后在定时器里,用当前时间跟每个连接的getLastTime()作比较,来判定超时:

    
     
    1. public class TimeoutTask extends TimerTask{
    2. public void run () {
    3. long now = System.currentTimeMillis();
    4. for(Connection c: connections){
    5. if(now - c.getLastTime()> TIMEOUT_THRESHOLD)
    6. ; //timeout, do something
    7. }
    8. }
    9. }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    看到这,可能不少读者已经看出问题来了,那就是内存可见性问题,调用refresh方法的线程跟执行定时器的线程肯定不是一个线程,那run方法中读到的lastTime就可能是旧值,即可能将活跃的连接判定超时,然后被干掉。

    有读者此时可能想到了这样一个方法,将lastTime加个volatile修饰,是的,这样确实解决了问题,不过,作为服务端,很多时候对性能是有要求的,下面来看下在我电脑上测出的一组数据,测试代码如下,供参考

    
     
    1. public class PerformanceTest {
    2. private static long i;
    3. private volatile static long vt;
    4. private static final int TEST_SIZE = 10000000;
    5. public static void main (String[] args) {
    6. long time = System.nanoTime();
    7. for ( int n = 0; n < TEST_SIZE; n++)
    8. vt = System.currentTimeMillis();
    9. System. out.println(-time + (time = System.nanoTime()));
    10. for ( int n = 0; n < TEST_SIZE; n++)
    11. i = System.currentTimeMillis();
    12. System. out.println(-time + (time = System.nanoTime()));
    13. for ( int n = 0; n < TEST_SIZE; n++)
    14. synchronized (PerformanceTest.class) {
    15. }
    16. System. out.println(-time + (time = System.nanoTime()));
    17. for ( int n = 0; n < TEST_SIZE; n++)
    18. vt++;
    19. System. out.println(-time + (time = System.nanoTime()));
    20. for ( int n = 0; n < TEST_SIZE; n++)
    21. vt = i;
    22. System. out.println(-time + (time = System.nanoTime()));
    23. for ( int n = 0; n < TEST_SIZE; n++)
    24. i = vt;
    25. System. out.println(-time + (time = System.nanoTime()));
    26. for ( int n = 0; n < TEST_SIZE; n++)
    27. i++;
    28. System. out.println(-time + (time = System.nanoTime()));
    29. for ( int n = 0; n < TEST_SIZE; n++)
    30. i = n;
    31. System. out.println(-time + (time = System.nanoTime()));
    32. }
    33. }
    • 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

    测试一千万次,结果是(耗时单位:纳秒,包含循环本身的时间): 
    238932949 volatile写+取系统时间 
    144317590 普通写+取系统时间 
    135596135 空的同步块(synchronized) 
    80042382 volatile变量自增 
    15875140 volatile写 
    6548994 volatile读 
    2722555 普通自增 
    2949571 普通读写

    从上面的数据看来,volatile写+取系统时间的耗时是很高的,取系统时间的耗时也比较高,跟一次无竞争的同步差不多了,接下来分析下如何优化该超时时机。

    首先:同步问题是肯定得考虑的,因为有跨线程的数据操作;另外,取系统时间的操作比较耗时,能否不在每次刷新时都取时间?因为刷新调用在高负载的情况下很频繁。如果不在刷新时取时间,那又该怎么去判定超时?

    我想到的办法是,在refresh方法里,仅设置一个volatile的boolean变量reset(这应该是成本最小的了吧,因为要处理同步问题,要么同步块,要么volatile,而volatile读在此处是没什么意义的),对时间的掌控交给定时器来做,并为每个连接维护一个计数器,每次加一,如果reset被设置为true了,则计数器归零,并将reset设为false(因为计数器只由定时器维护,所以不需要做同步处理,从上面的测试数据来看,普通变量的操作,时间成本是很低的),如果计数器超过某个值,则判定超时。 下面给出具体的代码:

    
     
    1. public class Connection {
    2. int count = 0;
    3. volatile boolean reset = false;
    4. public void refresh () {
    5. if (reset == false)
    6. reset = true;
    7. }
    8. }
    9. public class TimeoutTask extends TimerTask {
    10. public void run () {
    11. for (Connection c : connections) {
    12. if (c.reset) {
    13. c.reset = false;
    14. c.count = 0;
    15. } else if (++c.count >= TIMEOUT_COUNT)
    16. ; // timeout, do something
    17. }
    18. }
    19. }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    代码中的TIMEOUT_COUNT 等于超时时间除以定时器的周期,周期大小既影响定时器的执行频率,也会影响实际超时时间的波动范围(这个波动,第一个方案也存在,也不太可能避免,并且也不需要多么精确)。

    代码很简洁,下面来分析一下。

    reset加上了volatile,所以保证了多线程操作的可见性,虽然有两个线程都对变量有写操作,但无论这两个线程怎么穿插执行,都不会影响其逻辑含义。

    再说下refresh方法,为什么我在赋值语句上多加了个条件?这不是多了一次volatile读操作吗?我是这么考虑的,高负载下,refresh会被频繁调用,意味着reset长时间为true,那么加上条件后,就不会执行写操作了,只有一次读操作,从上面的测试数据来看,volatile变量的读操作的性能是显著优于写操作的。只不过在reset为false的时候,多了一次读操作,但此情况在定时器的一个周期内最多只会发一次,而且对高负载情况下的优化显然更有意义,所以我认为加上条件还是值得的。

    ————————————- 
    补充一下:一般情况下,也可用特定的心跳包来刷新,而不是每次收到消息都刷新,这样一来,刷新频率就很低了,也就没必要太在乎性能开销。

    转载自: 
    java心跳是怎么回事儿啊? 
    服务器实现心跳机制的两种策略 
    您还有心跳吗?超时机制分析

    展开全文
  • 很多人在写Socket的时候会遇到心跳包响应机制跟主线程消息传输机制如何规避而互不影响,本Demo就解决了此问题,而且简单易懂,都有备注,欢迎下载学习之用。
  • Socket长连接、通信、心跳包、消息回调、Java服务端
  • android心跳包

    热门讨论 2013-11-17 21:30:33
    里面封装了 tcp心跳包机制 欢迎大家学习
  • Socket长连接+心跳包+发送+读取,用到的全在这里了,自己看看哪里不需要的就不要添加了!代码很清晰很明白了!
  • 具有心跳包,长连接,自动重连,发送失败自动重发等功能 电脑与Android需要同属一个局域网 tcpListener设计成只能与一个tcpClient通信(当多个客户端接入时,会自动与前一个接入的客户端断开) tcpClient具有重连,...
  • 此片文章代码示例,http://blog.csdn.net/gzy11/article/details/54972798
  • 心跳包与日志 实际生产环境,我们一般会将程序收到的和发出去的数据包写入日志中,但是无业务信息的心跳包信息是个例外,一般会刻意不写入日志,这是因为心跳包数据一般比较多,如果写入日志会导致日志文件变得很大...

    存在下面两种情形:

    情形一:一个客户端连接服务器以后,如果长期没有和服务器有数据来往,可能会被防火墙程序关闭连接,有时候我们并不想要被关闭连接。例如,对于一个即时通讯软件,如果服务器没有消息时,我们确实不会和服务器有任何数据交换,但是如果连接被关闭了,有新消息来时,我们再也没法收到了,这就违背了“即时通讯”的设计要求。

    情形二:通常情况下,服务器与某个客户端一般不是位于同一个网络,其之间可能经过数个路由器和交换机,如果其中某个必经路由器或者交换器出现了故障,并且一段时间内没有恢复,导致这之间的链路不再畅通,而此时服务器与客户端之间也没有数据进行交换,由于 TCP 连接是状态机,对于这种情况,无论是客户端或者服务器都无法感知与对方的连接是否正常,这类连接我们一般称之为“死链”。

    情形一中的应用场景要求必须保持客户端与服务器之间的连接正常,就是我们通常所说的“保活“。如上文所述,当服务器与客户端一定时间内没有有效业务数据来往时,我们只需要给对端发送心跳包即可实现保活。

    情形二中的死链,只要我们此时任意一端给对端发送一个数据包即可检测链路是否正常,这类数据包我们也称之为”心跳包”,这种操作我们称之为“心跳检测”。顾名思义,如果一个人没有心跳了,可能已经死亡了;一个连接长时间没有正常数据来往,也没有心跳包来往,就可以认为这个连接已经不存在,为了节约服务器连接资源,我们可以通过关闭 socket,回收连接资源。

    根据上面的分析,让我再强调一下,心跳检测一般有两个作用:

    保活

    检测死链

    TCP keepalive 选项

    操作系统的 TCP/IP 协议栈其实提供了这个的功能,即 keepalive 选项。在 Linux 操作系统中,我们可以通过代码启用一个 socket 的心跳检测(即每隔一定时间间隔发送一个心跳检测包给对端),代码如下:

    //on 是 1 表示打开 keepalive 选项,为 0 表示关闭,0 是默认值

    int on = 1;

    setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));

    但是,即使开启了这个选项,这个选项默认发送心跳检测数据包的时间间隔是 7200 秒(2 小时),这时间间隔实在是太长了,不具有实用性。

    我们可以通过继续设置 keepalive 相关的三个选项来改变这个时间间隔,它们分别是 TCP_KEEPIDLE、TCP_KEEPINTVL 和 TCP_KEEPCNT,示例代码如下:

    //发送 keepalive 报文的时间间隔

    int val = 7200;

    setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val));

    //两次重试报文的时间间隔

    int interval = 75;

    setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));

    int cnt = 9;

    setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));

    TCP_KEEPIDLE 选项设置了发送 keepalive 报文的时间间隔,发送时如果对端回复 ACK。则本端 TCP 协议栈认为该连接依然存活,继续等 7200 秒后再发送 keepalive 报文;如果对端回复 RESET,说明对端进程已经重启,本端的应用程序应该关闭该连接。

    如果对端没有任何回复,则本端做重试,如果重试 9 次(TCP_KEEPCNT 值)(前后重试间隔为 75 秒(TCP_KEEPINTVL 值))仍然不可达,则向应用程序返回 ETIMEOUT(无任何应答)或 EHOST 错误信息。

    我们可以使用如下命令查看 Linux 系统上的上述三个值的设置情况:

    [root@iZ238vnojlyZ ~]# sysctl -a | grep keepalive

    net.ipv4.tcp_keepalive_intvl = 75

    net.ipv4.tcp_keepalive_probes = 9

    net.ipv4.tcp_keepalive_time = 7200

    在 Windows 系统设置 keepalive 及对应选项的代码略有不同:

    //开启 keepalive 选项

    const char on = 1;

    setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on);

    // 设置超时详细信息

    DWORD cbBytesReturned;

    tcp_keepalive klive;

    // 启用保活

    klive.onoff = 1;

    klive.keepalivetime = 7200;

    // 重试间隔为10秒

    klive.keepaliveinterval = 1000 * 10;

    WSAIoctl(socket, SIO_KEEPALIVE_VALS, &klive, sizeof(tcp_keepalive), NULL, 0, &cbBytesReturned, NULL, NULL);

    应用层的心跳包机制设计

    由于 keepalive 选项需要为每个连接中的 socket 开启,这不一定是必须的,可能会产生大量无意义的带宽浪费,且 keepalive 选项不能与应用层很好地交互,因此一般实际的服务开发中,还是建议读者在应用层设计自己的心跳包机制。那么如何设计呢?

    从技术来讲,心跳包其实就是一个预先规定好格式的数据包,在程序中启动一个定时器,定时发送即可,这是最简单的实现思路。但是,如果通信的两端有频繁的数据来往,此时到了下一个发心跳包的时间点了,此时发送一个心跳包。这其实是一个流量的浪费,既然通信双方不断有正常的业务数据包来往,这些数据包本身就可以起到保活作用,为什么还要浪费流量去发送这些心跳包呢?所以,对于用于保活的心跳包,我们最佳做法是,设置一个上次包时间,每次收数据和发数据时,都更新一下这个包时间,而心跳检测计时器每次检测时,将这个包时间与当前系统时间做一个对比,如果时间间隔大于允许的最大时间间隔(实际开发中根据需求设置成 15 ~ 45 秒不等),则发送一次心跳包。总而言之,就是在与对端之间,没有数据来往达到一定时间间隔时才发送一次心跳包。

    发心跳包的伪码:

    bool CIUSocket::Send()

    {

    int nSentBytes = 0;

    int nRet = 0;

    while (true)

    {

    nRet = ::send(m_hSocket, m_strSendBuf.c_str(), m_strSendBuf.length(), 0);

    if (nRet == SOCKET_ERROR)

    {

    if (::WSAGetLastError() == WSAEWOULDBLOCK)

    break;

    else

    {

    LOG_ERROR("Send data error, disconnect server:%s, port:%d.", m_strServer.c_str(), m_nPort);

    Close();

    return false;

    }

    }

    else if (nRet 

    {

    //一旦出现错误就立刻关闭Socket

    LOG_ERROR("Send data error, disconnect server:%s, port:%d.", m_strServer.c_str(), m_nPort);

    Close();

    return false;

    }

    m_strSendBuf.erase(0, nRet);

    if (m_strSendBuf.empty())

    break;

    ::Sleep(1);

    }

    {

    //记录一下最近一次发包时间

    std::lock_guard<:mutex> guard(m_mutexLastDataTime);

    m_nLastDataTime = (long)time(NULL);

    }

    return true;

    }

    bool CIUSocket::Recv()

    {

    int nRet = 0;

    char buff[10 * 1024];

    while (true)

    {

    nRet = ::recv(m_hSocket, buff, 10 * 1024, 0);

    if (nRet == SOCKET_ERROR)                //一旦出现错误就立刻关闭Socket

    {

    if (::WSAGetLastError() == WSAEWOULDBLOCK)

    break;

    else

    {

    LOG_ERROR("Recv data error, errorNO=%d.", ::WSAGetLastError());

    //Close();

    return false;

    }

    }

    else if (nRet 

    {

    LOG_ERROR("Recv data error, errorNO=%d.", ::WSAGetLastError());

    //Close();

    return false;

    }

    m_strRecvBuf.append(buff, nRet);

    ::Sleep(1);

    }

    {

    std::lock_guard<:mutex> guard(m_mutexLastDataTime);

    //记录一下最近一次收包时间

    m_nLastDataTime = (long)time(NULL);

    }

    return true;

    }

    void CIUSocket::RecvThreadProc()

    {

    LOG_INFO("Recv data thread start...");

    int nRet;

    //上网方式

    DWORD   dwFlags;

    BOOL    bAlive;

    while (!m_bStop)

    {

    //检测到数据则收数据

    nRet = CheckReceivedData();

    //出错

    if (nRet == -1)

    {

    m_pRecvMsgThread->NotifyNetError();

    }

    //无数据

    else if (nRet == 0)

    {

    long nLastDataTime = 0;

    {

    std::lock_guard<:mutex> guard(m_mutexLastDataTime);

    nLastDataTime = m_nLastDataTime;

    }

    if (m_nHeartbeatInterval > 0)

    {

    //当前系统时间与上一次收发数据包的时间间隔超过了m_nHeartbeatInterval

    //则发一次心跳包

    if (time(NULL) - nLastDataTime >= m_nHeartbeatInterval)

    SendHeartbeatPackage();

    }

    }

    //有数据

    else if (nRet == 1)

    {

    if (!Recv())

    {

    m_pRecvMsgThread->NotifyNetError();

    continue;

    }

    DecodePackages();

    }// end if

    }// end while-loop

    LOG_INFO("Recv data thread finish...");

    }

    同理,检测心跳包的一端,应该是在与对端没有数据来往达到一定时间间隔时才做一次心跳检测。

    心跳检测一端的伪码如下:

    void BusinessSession::send(const char* pData, int dataLength)

    {

    bool sent = TcpSession::send(pData, dataLength);

    //发送完数据更新下发包时间

    updateHeartbeatTime();

    }

    void BusinessSession::handlePackge(char* pMsg, int msgLength, bool& closeSession, std::vector<:string>& vectorResponse)

    {

    //对数据合法性进行校验

    if (pMsg == NULL || pMsg[0] == 0 || msgLength <= 0 || msgLength > MAX_DATA_LENGTH)

    {

    //非法刺探请求,不做任何应答,直接关闭连接

    closeSession = true;

    return;

    }

    //更新下收包时间

    updateHeartbeatTime();

    //省略包处理代码...

    }

    void BusinessSession::updateHeartbeatTime()

    {

    std::lock_guard<:mutex> scoped_guard(m_mutexForlastPackageTime);

    m_lastPackageTime = (int64_t)time(nullptr);

    }

    bool BusinessSession::doHeartbeatCheck()

    {

    const Config& cfg = Singleton::Instance();

    int64_t now = (int64_t)time(nullptr);

    std::lock_guard<:mutex> lock_guard(m_mutexForlastPackageTime);

    if (now - m_lastPackageTime >= cfg.m_nMaxClientDataInterval)

    {

    //心跳包检测,超时,关闭连接

    LOGE("heartbeat expired, close session");

    shutdown();

    return true;

    }

    return false;

    }

    void TcpServer::checkSessionHeartbeat()

    {

    int64_t now = (int64_t)time(nullptr);

    if (now - m_nLastCheckHeartbeatTime >= m_nHeartbeatCheckInterval)

    {

    m_spSessionManager->checkSessionHeartbeat();

    m_nLastCheckHeartbeatTime = (int64_t)time(nullptr);

    }

    }

    void SessionManager::checkSessionHeartbeat()

    {

    std::lock_guard<:mutex> scoped_lock(m_mutexForSession);

    for (const auto& iter : m_mapSessions)

    {

    //这里调用 BusinessSession::doHeartbeatCheck()

    iter.second->doHeartbeatCheck();

    }

    }

    需要注意的是:一般是客户端主动给服务器端发送心跳包,服务器端做心跳检测决定是否断开连接。而不是反过来,从客户端的角度来说,客户端为了让自己得到服务器端的正常服务有必要主动和服务器保持连接状态正常,而服务器端不会局限于某个特定的客户端,如果客户端不能主动和其保持连接,那么就会主动回收与该客户端的连接。当然,服务器端在收到客户端的心跳包时应该给客户端一个心跳应答。

    带业务数据的心跳包

    上面介绍的心跳包是从纯技术的角度来说的,在实际应用中,有时候我们需要定时或者不定时从服务器端更新一些数据,我们可以把这类数据放在心跳包中,定时或者不定时更新。

    这类带业务数据的心跳包,就不再是纯粹技术上的作用了(这里说的技术的作用指的上文中介绍的心跳包起保活和检测死链作用)。

    这类心跳包实现也很容易,即在心跳包数据结构里面加上需要的业务字段信息,然后在定时器中定时发送,客户端发给服务器,服务器在应答心跳包中填上约定的业务数据信息即可。

    心跳包与流量

    通常情况下,多数应用场景下,与服务器端保持连接的多个客户端中,同一时间段活跃用户(这里指的是与服务器有频繁数据来往的客户端)一般不会太多。当连接数较多时,进出服务器程序的数据包通常都是心跳包(为了保活)。所以为了减轻网络代码压力,节省流量,尤其是针对一些 3/4 G 手机应用,我们在设计心跳包数据格式时应该尽量减小心跳包的数据大小。

    心跳包与调试

    如前文所述,对于心跳包,服务器端的逻辑一般是在一定时间间隔内没有收到客户端心跳包时会主动断开连接。在我们开发调试程序过程中,我们可能需要将程序通过断点中断下来,这个过程可能是几秒到几十秒不等。等程序恢复执行时,连接可能因为心跳检测逻辑已经被断开。

    调试过程中,我们更多的关注的是业务数据处理的逻辑是否正确,不想被一堆无意义的心跳包数据干扰实线。

    鉴于以上两点原因,我们一般在调试模式下关闭或者禁用心跳包检测机制。代码大致如下:

    ChatSession::ChatSession(const std::shared_ptr& conn, int sessionid) :

    TcpSession(conn),

    m_id(sessionid),

    m_seq(0),

    m_isLogin(false)

    {

    m_userinfo.userid = 0;

    m_lastPackageTime = time(NULL);

    //这里设置了非调试模式下才开启心跳包检测功能

    #ifndef _DEBUG

    EnableHearbeatCheck();

    #endif

    }

    当然,你也可以将开启心跳检测的开关做成配置信息放入程序配置文件中。

    心跳包与日志

    实际生产环境,我们一般会将程序收到的和发出去的数据包写入日志中,但是无业务信息的心跳包信息是个例外,一般会刻意不写入日志,这是因为心跳包数据一般比较多,如果写入日志会导致日志文件变得很大,且充斥大量无意义的心跳包日志,所以一般在写日志时会屏蔽心跳包信息写入。

    我这里的建议是,可以将心跳包信息是否写入日志做成一个配置开关,一般处于关闭状态,有需要时再开启。例如,对于一个 WebSocket 服务,ping 和 pong 是心跳包数据,下面示例代码按需输出心跳日志信息:

    void BusinessSession::send(std::string_view strResponse)

    {

    bool success = WebSocketSession::send(strResponse);

    if (success)

    {

    bool enablePingPongLog = Singleton::Instance().m_bPingPongLogEnabled;

    //其他消息正常打印,心跳消息按需打印

    if (strResponse != "pong" || enablePingPongLog)

    {

    LOGI("msg sent to client [%s], sessionId: %s, session: 0x%0x, clientId: %s, accountId: %s, frontId: %s, msg: %s",

    getClientInfo(), m_strSessionId.c_str(), (int64_t)this, m_strClientID.c_str(), m_strAccountID.c_str(), BusinessSession::m_strFrontId.c_str(), strResponse.data());

    }

    }

    }

    需要说明的是,以上示例代码使用 C/C++ 语言编写,但是本节介绍的心跳包机制设计思路和注意事项是普适性原理,同样适用于其他编程语言

    展开全文
  • 主要介绍了Java Socket编程心跳包创建实例解析,具有一定借鉴价值,需要的朋友可以参考下
  • netty框架基于http socket websocket及心跳包机制的demo
  • Delphi之TClientSocket和TServerSocket进行TCP长连接通讯,使用KeepALive自动发送心跳包检测断网,并实现断线重连,经测试可以及时检测到拔掉网线断网情况,具有较高的参考价值
  • 心跳包机制设计详解

    2021-03-04 12:53:08
    应用层的心跳包机制设计 由于 keepalive 选项需要为每个连接中的 socket 开启,这不一定是必须的,可能会产生大量无意义的带宽浪费,且 keepalive 选项不能与应用层很好地交互,因此一般实际的服务开发中,还是建议...

    存在下面两种情形:

    情形一:一个客户端连接服务器以后,如果长期没有和服务器有数据来往,可能会被防火墙程序关闭连接,有时候我们并不想要被关闭连接。例如,对于一个即时通讯软件,如果服务器没有消息时,我们确实不会和服务器有任何数据交换,但是如果连接被关闭了,有新消息来时,我们再也没法收到了,这就违背了“即时通讯”的设计要求。

    情形二:通常情况下,服务器与某个客户端一般不是位于同一个网络,其之间可能经过数个路由器和交换机,如果其中某个必经路由器或者交换器出现了故障,并且一段时间内没有恢复,导致这之间的链路不再畅通,而此时服务器与客户端之间也没有数据进行交换,由于 TCP 连接是状态机,对于这种情况,无论是客户端或者服务器都无法感知与对方的连接是否正常,这类连接我们一般称之为“死链”。

    情形一中的应用场景要求必须保持客户端与服务器之间的连接正常,就是我们通常所说的“保活“。如上文所述,当服务器与客户端一定时间内没有有效业务数据来往时,我们只需要给对端发送心跳包即可实现保活。

    情形二中的死链,只要我们此时任意一端给对端发送一个数据包即可检测链路是否正常,这类数据包我们也称之为”心跳包”,这种操作我们称之为“心跳检测”。顾名思义,如果一个人没有心跳了,可能已经死亡了;一个连接长时间没有正常数据来往,也没有心跳包来往,就可以认为这个连接已经不存在,为了节约服务器连接资源,我们可以通过关闭 socket,回收连接资源。

    根据上面的分析,让我再强调一下,心跳检测一般有两个作用:

    • 保活

    • 检测死链

    TCP keepalive 选项

    操作系统的 TCP/IP 协议栈其实提供了这个的功能,即 keepalive 选项。在 Linux 操作系统中,我们可以通过代码启用一个 socket 的心跳检测(即每隔一定时间间隔发送一个心跳检测包给对端),代码如下:

    //on 是 1 表示打开 keepalive 选项,为 0 表示关闭,0 是默认值
    int on = 1;
    setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on));
    

    但是,即使开启了这个选项,这个选项默认发送心跳检测数据包的时间间隔是 7200 秒(2 小时),这时间间隔实在是太长了,不具有实用性。

    我们可以通过继续设置 keepalive 相关的三个选项来改变这个时间间隔,它们分别是 TCP_KEEPIDLE、TCP_KEEPINTVL 和 TCP_KEEPCNT,示例代码如下:

    //发送 keepalive 报文的时间间隔
    int val = 7200;
    setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, &val, sizeof(val));
    
    //两次重试报文的时间间隔
    int interval = 75;
    setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, &interval, sizeof(interval));
    
    int cnt = 9;
    setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, &cnt, sizeof(cnt));
    

    TCP_KEEPIDLE 选项设置了发送 keepalive 报文的时间间隔,发送时如果对端回复 ACK。则本端 TCP 协议栈认为该连接依然存活,继续等 7200 秒后再发送 keepalive 报文;如果对端回复 RESET,说明对端进程已经重启,本端的应用程序应该关闭该连接。

    如果对端没有任何回复,则本端做重试,如果重试 9 次(TCP_KEEPCNT 值)(前后重试间隔为 75 秒(TCP_KEEPINTVL 值))仍然不可达,则向应用程序返回 ETIMEOUT(无任何应答)或 EHOST 错误信息。

    我们可以使用如下命令查看 Linux 系统上的上述三个值的设置情况:

    [root@iZ238vnojlyZ ~]# sysctl -a | grep keepalive
    net.ipv4.tcp_keepalive_intvl = 75
    net.ipv4.tcp_keepalive_probes = 9
    net.ipv4.tcp_keepalive_time = 7200
    

    在 Windows 系统设置 keepalive 及对应选项的代码略有不同:

    //开启 keepalive 选项
    const char on = 1;
    setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, (char *)&on, sizeof(on);
    
    // 设置超时详细信息
    DWORD cbBytesReturned;
    tcp_keepalive klive;
    // 启用保活
    klive.onoff = 1;
    klive.keepalivetime = 7200;
    // 重试间隔为10秒
    klive.keepaliveinterval = 1000 * 10; 
    WSAIoctl(socket, SIO_KEEPALIVE_VALS, &klive, sizeof(tcp_keepalive), NULL, 0, &cbBytesReturned, NULL, NULL);
    

    应用层的心跳包机制设计

    由于 keepalive 选项需要为每个连接中的 socket 开启,这不一定是必须的,可能会产生大量无意义的带宽浪费,且 keepalive 选项不能与应用层很好地交互,因此一般实际的服务开发中,还是建议读者在应用层设计自己的心跳包机制。那么如何设计呢?

    从技术来讲,心跳包其实就是一个预先规定好格式的数据包,在程序中启动一个定时器,定时发送即可,这是最简单的实现思路。但是,如果通信的两端有频繁的数据来往,此时到了下一个发心跳包的时间点了,此时发送一个心跳包。这其实是一个流量的浪费,既然通信双方不断有正常的业务数据包来往,这些数据包本身就可以起到保活作用,为什么还要浪费流量去发送这些心跳包呢?所以,对于用于保活的心跳包,我们最佳做法是,设置一个上次包时间,每次收数据和发数据时,都更新一下这个包时间,而心跳检测计时器每次检测时,将这个包时间与当前系统时间做一个对比,如果时间间隔大于允许的最大时间间隔(实际开发中根据需求设置成 15 ~ 45 秒不等),则发送一次心跳包。总而言之,就是在与对端之间,没有数据来往达到一定时间间隔时才发送一次心跳包。

    发心跳包的伪码:

    bool CIUSocket::Send()
    {
        int nSentBytes = 0;
        int nRet = 0;
        while (true)
        {
            nRet = ::send(m_hSocket, m_strSendBuf.c_str(), m_strSendBuf.length(), 0);
            if (nRet == SOCKET_ERROR)
            {
                if (::WSAGetLastError() == WSAEWOULDBLOCK)
                    break;
                else
                {
                    LOG_ERROR("Send data error, disconnect server:%s, port:%d.", m_strServer.c_str(), m_nPort);
                    Close();
                    return false;
                }
            }
            else if (nRet < 1)
            {
                //一旦出现错误就立刻关闭Socket
                LOG_ERROR("Send data error, disconnect server:%s, port:%d.", m_strServer.c_str(), m_nPort);
                Close();
                return false;
            }
    
            m_strSendBuf.erase(0, nRet);
            if (m_strSendBuf.empty())
                break;
    
            ::Sleep(1);
        }
    
        {
            //记录一下最近一次发包时间
            std::lock_guard<std::mutex> guard(m_mutexLastDataTime);
            m_nLastDataTime = (long)time(NULL);
        }
    
        return true;
    }
    
    
    bool CIUSocket::Recv()
    {
        int nRet = 0;
        char buff[10 * 1024];
        while (true)
        {
    
            nRet = ::recv(m_hSocket, buff, 10 * 1024, 0);
            if (nRet == SOCKET_ERROR)                //一旦出现错误就立刻关闭Socket
            {
                if (::WSAGetLastError() == WSAEWOULDBLOCK)
                    break;
                else
                {
                    LOG_ERROR("Recv data error, errorNO=%d.", ::WSAGetLastError());
                    //Close();
                    return false;
                }
            }
            else if (nRet < 1)
            {
                LOG_ERROR("Recv data error, errorNO=%d.", ::WSAGetLastError());
                //Close();
                return false;
            }
    
            m_strRecvBuf.append(buff, nRet);
    
            ::Sleep(1);
        }
    
        {
            std::lock_guard<std::mutex> guard(m_mutexLastDataTime);
            //记录一下最近一次收包时间
            m_nLastDataTime = (long)time(NULL);
        }
    
        return true;
    }
    
    void CIUSocket::RecvThreadProc()
    {
        LOG_INFO("Recv data thread start...");
    
        int nRet;
        //上网方式 
        DWORD   dwFlags;
        BOOL    bAlive;
        while (!m_bStop)
        {
            //检测到数据则收数据
            nRet = CheckReceivedData();
            //出错
            if (nRet == -1)
            {
                m_pRecvMsgThread->NotifyNetError();
            }
            //无数据
            else if (nRet == 0)
            {           
                long nLastDataTime = 0;
                {
                    std::lock_guard<std::mutex> guard(m_mutexLastDataTime);
                    nLastDataTime = m_nLastDataTime;
                }
    
                if (m_nHeartbeatInterval > 0)
                {
                    //当前系统时间与上一次收发数据包的时间间隔超过了m_nHeartbeatInterval
                    //则发一次心跳包
                    if (time(NULL) - nLastDataTime >= m_nHeartbeatInterval)
                        SendHeartbeatPackage();
                }
            }
            //有数据
            else if (nRet == 1)
            {
                if (!Recv())
                {
                    m_pRecvMsgThread->NotifyNetError();
                    continue;
                }
    
                DecodePackages();
            }// end if
        }// end while-loop
    
        LOG_INFO("Recv data thread finish...");
    }
    

    同理,检测心跳包的一端,应该是在与对端没有数据来往达到一定时间间隔时才做一次心跳检测。

    心跳检测一端的伪码如下:

    void BusinessSession::send(const char* pData, int dataLength)
    {
        bool sent = TcpSession::send(pData, dataLength);
    
        //发送完数据更新下发包时间
        updateHeartbeatTime();      
    }
    void BusinessSession::handlePackge(char* pMsg, int msgLength, bool& closeSession, std::vector<std::string>& vectorResponse)
    {
        //对数据合法性进行校验
        if (pMsg == NULL || pMsg[0] == 0 || msgLength <= 0 || msgLength > MAX_DATA_LENGTH)
        {
            //非法刺探请求,不做任何应答,直接关闭连接
            closeSession = true;
            return;
        }
    
        //更新下收包时间
        updateHeartbeatTime();
    
        //省略包处理代码...
    }
    
    void BusinessSession::updateHeartbeatTime()
    {
        std::lock_guard<std::mutex> scoped_guard(m_mutexForlastPackageTime);
        m_lastPackageTime = (int64_t)time(nullptr);
    }
    
    bool BusinessSession::doHeartbeatCheck()
    {
        const Config& cfg = Singleton<Config>::Instance();
        int64_t now = (int64_t)time(nullptr);
    
        std::lock_guard<std::mutex> lock_guard(m_mutexForlastPackageTime);
        if (now - m_lastPackageTime >= cfg.m_nMaxClientDataInterval)
        {
            //心跳包检测,超时,关闭连接
            LOGE("heartbeat expired, close session");
            shutdown();
            return true;
        }
    
        return false;
    }
    
    void TcpServer::checkSessionHeartbeat()
    {
        int64_t now = (int64_t)time(nullptr);
        if (now - m_nLastCheckHeartbeatTime >= m_nHeartbeatCheckInterval)
        {
            m_spSessionManager->checkSessionHeartbeat();
            m_nLastCheckHeartbeatTime = (int64_t)time(nullptr);
        }      
    }
    
    void SessionManager::checkSessionHeartbeat()
    {   
        std::lock_guard<std::mutex> scoped_lock(m_mutexForSession);
        for (const auto& iter : m_mapSessions)
        {
            //这里调用 BusinessSession::doHeartbeatCheck()
            iter.second->doHeartbeatCheck();
        }  
    }
    

    需要注意的是:一般是客户端主动给服务器端发送心跳包,服务器端做心跳检测决定是否断开连接。而不是反过来,从客户端的角度来说,客户端为了让自己得到服务器端的正常服务有必要主动和服务器保持连接状态正常,而服务器端不会局限于某个特定的客户端,如果客户端不能主动和其保持连接,那么就会主动回收与该客户端的连接。当然,服务器端在收到客户端的心跳包时应该给客户端一个心跳应答。

    带业务数据的心跳包

    上面介绍的心跳包是从纯技术的角度来说的,在实际应用中,有时候我们需要定时或者不定时从服务器端更新一些数据,我们可以把这类数据放在心跳包中,定时或者不定时更新。

    这类带业务数据的心跳包,就不再是纯粹技术上的作用了(这里说的技术的作用指的上文中介绍的心跳包起保活和检测死链作用)。

    这类心跳包实现也很容易,即在心跳包数据结构里面加上需要的业务字段信息,然后在定时器中定时发送,客户端发给服务器,服务器在应答心跳包中填上约定的业务数据信息即可。

    心跳包与流量

    通常情况下,多数应用场景下,与服务器端保持连接的多个客户端中,同一时间段活跃用户(这里指的是与服务器有频繁数据来往的客户端)一般不会太多。当连接数较多时,进出服务器程序的数据包通常都是心跳包(为了保活)。所以为了减轻网络代码压力,节省流量,尤其是针对一些 3/4 G 手机应用,我们在设计心跳包数据格式时应该尽量减小心跳包的数据大小。

    心跳包与调试

    如前文所述,对于心跳包,服务器端的逻辑一般是在一定时间间隔内没有收到客户端心跳包时会主动断开连接。在我们开发调试程序过程中,我们可能需要将程序通过断点中断下来,这个过程可能是几秒到几十秒不等。等程序恢复执行时,连接可能因为心跳检测逻辑已经被断开。

    调试过程中,我们更多的关注的是业务数据处理的逻辑是否正确,不想被一堆无意义的心跳包数据干扰实线。

    鉴于以上两点原因,我们一般在调试模式下关闭或者禁用心跳包检测机制。代码大致如下:

    ChatSession::ChatSession(const std::shared_ptr<TcpConnection>& conn, int sessionid) :
    TcpSession(conn), 
    m_id(sessionid),
    m_seq(0),
    m_isLogin(false)
    {
        m_userinfo.userid = 0;
        m_lastPackageTime = time(NULL);
    
    //这里设置了非调试模式下才开启心跳包检测功能
    #ifndef _DEBUG
        EnableHearbeatCheck();
    #endif
    }
    

    当然,你也可以将开启心跳检测的开关做成配置信息放入程序配置文件中。

    心跳包与日志

    实际生产环境,我们一般会将程序收到的和发出去的数据包写入日志中,但是无业务信息的心跳包信息是个例外,一般会刻意不写入日志,这是因为心跳包数据一般比较多,如果写入日志会导致日志文件变得很大,且充斥大量无意义的心跳包日志,所以一般在写日志时会屏蔽心跳包信息写入。

    我这里的建议是,可以将心跳包信息是否写入日志做成一个配置开关,一般处于关闭状态,有需要时再开启。例如,对于一个 WebSocket 服务,ping 和 pong 是心跳包数据,下面示例代码按需输出心跳日志信息:

    void BusinessSession::send(std::string_view strResponse)
    {   
        bool success = WebSocketSession::send(strResponse);
    
        if (success)
        {
            bool enablePingPongLog = Singleton<Config>::Instance().m_bPingPongLogEnabled;
    
            //其他消息正常打印,心跳消息按需打印
            if (strResponse != "pong" || enablePingPongLog)
            {
                LOGI("msg sent to client [%s], sessionId: %s, session: 0x%0x, clientId: %s, accountId: %s, frontId: %s, msg: %s",
                     getClientInfo(), m_strSessionId.c_str(), (int64_t)this, m_strClientID.c_str(), m_strAccountID.c_str(), BusinessSession::m_strFrontId.c_str(), strResponse.data());
            }
        }
    }
    

    需要说明的是,以上示例代码使用 C/C++ 语言编写,但是本节介绍的心跳包机制设计思路和注意事项是普适性原理,同样适用于其他编程语言

    展开全文
  • socketTCP通信心跳包实例

    千次下载 热门讨论 2012-12-24 14:36:27
    winform的socket通信中连接是否正常,最好通过心跳包来确定,本程序就是一个简单的心跳包实例,简单、易懂~
  • MFC心跳包测试源码.zip

    2019-09-11 08:30:54
    资源含有服务器和客户端源码,在MFC对话框下生成的应用程序,启动连接后,通过心跳包机制,双方互相检测对方是否在线,如测试中有问题,欢迎留言交流!
  • 服务端定时检测所连接客户端是否有给来报到,报到了则判断在线,否则掉线,客户端定时发包给服务端,超过3次未取得响应,则理解为掉线,大概这就是所谓心跳包
  • mina心跳包机制

    2017-05-26 17:51:46
    项目包含有mina的服务端与客户端,客户端发送心跳包,服务端响应心跳包
  • i 这个呢,是解析心跳包,首先我接收了到心跳包,传入了conn。这个时候呢我得想,怎么才能知道客户端离开了呢,于是我定义了一个类叫 UserInfo 里面有一个属性是time用于记录客户端传入得每一次接收他心跳得当前时间...

    技术栈:

    服务端:node.js ,nodejs-websocket ,event
    前端技术:uniapp websocket得api可以查看该文档,https://uniapp.dcloud.io/api/timer

    实现场景:在服务端和客户端如果出现了长连接传输数据的时候,出现了前端断开,服务端没有检测到前端的断开,服务端还保留数据,当客户端再次上线的时候就会出现某些问题。

    还可以出现在,服务端与其他产品的问题,比如说,服务端和音箱。之间的传输数据是TCP
    首先,贴出代码可以先看看,后面一步一步讲解

    服务端

    在这里插入图片描述

    SocketClient.js

    这个js文件中存放是,创建websocket得一些基本操作。用node.js创建websocket很简单。在网上百度一大堆教程,不知道怎么去创建得可以先去百度

    const ws = require("nodejs-websocket");
    const Observer = require('../Observer/Socket.js')
    
    
    
    
    class SocketClient {
        constructor(port){
            this.port = port;
            this.Run()
        }
        Run(){
           //断线由心跳包控制
             ws.createServer(function (conn) {  //在人进来的时候,需要把他的conn连接池和他的身份id给关联对应起来
                 conn.on("text", function (str) {  //接收字符串类型的数据
                })
                 conn.on("close", function (code,res) {
                     for (var i=0;i<UserInfo.length;i++){
                         if(UserInfo[i].conn == conn){
                             UserInfo.splice(i,1)
                             console.log('触发关闭删除:'+ UserInfo.splice(i,1))
                         }
                     }
                 })
                conn.on("error", function (err) {
                    for (var i=0;i<UserInfo.length;i++){
                        if(UserInfo[i].conn == conn){
                            UserInfo.splice(i,1)
                            console.log('发生错误删除:'+ UserInfo.splice(i,1))
                        }
                    }
                })
                conn.on("binary", function (inStream) {
    
                    inStream.on("readable", async function () {
                        var newData = inStream.read();
                        if (newData) {
                            Observer.emit('SocketToApp',conn,newData)
                        }
                    })
                })
            }).listen(this.port);
        }
    }
    
    
    
    
    module.exports = SocketClient;
    

    其次我们在看Socket.js这个文件

    现在涉及到了event模块得使用,这个也很简单。node.js属于事件驱动行,全程异步操作。所以步会event模块得可以先去百度一下了解一下这个东西。

    我们在 SocketClient.js文件中 Observer.emit('SocketToApp',conn,newData) 有这一句话,这句话得意思就是调用Socket.js文件中得方法。 这个在event模块会有讲解得。不明白得可以去看看,其实就是一句话,在监听到客户端给我发二进制数据得时候我调用 SocketToApp 这个方法,传入了 conn,newData 这俩参数而已。conn是链接池,newData是客户端传入得数据

    var Observer = require('../Observer');
    const DataPackage = require('xbtool').LineData
    
    
    
    Observer.on('SocketToApp',function (conn,newData) {
        if(newData[2] == 1){
            console.log(newData)
            let GetString = new DataPackage(newData, 3);   //先送棋盘,再送音箱
            let DeviceStr = GetString.ReadString();
            let item = DeviceStr.split('|')
            let SaveOk = checkUserInfo(item[0],item[1])  //已经存在了
            if(SaveOk == null){
                let curDate= new Date();
                let data = {
                    board:item[0],
                    soundBox:item[1],
                    conn:conn,
                    Time:curDate,
                };
                console.log('新人上线:board:' +item[0]+'  soundBox:'+item[1])
                UserInfo.push(data)
                console.log('所有得设备列表:'+ JSON.stringify(DeviceList))
                let str = ''
                try{
                    for (var i=0;i<DeviceList.board.length;i++){
                        let tmp = DeviceList.board
                        if (tmp[i].deviceId == item[0]){
                            str =utils.WriteInt8(null,1)
                            console.log('音箱在线:')
                        }
                    }
                }catch (e) {
    
                }
                if(str == ''){
                    str = utils.WriteInt8(null,0)
                }
                let b = false
                try {
                    for (var j=0;j<DeviceList.SoundBox.length;j++){
                        let tmp = DeviceList.SoundBox
                        if(tmp[j].deviceId == item[1]){
                            b = true
                            str = uitls.WriteInt8(str,1)
                            console.log('棋盘在线:')
                        }
                    }
                }catch (e) {
    
                }
    
                if(b == false){
                    str = uitls.WriteInt8(str,0)
                }
               str =  utils.WriteCmdToBuffer(str,3);
                conn.sendBinary(str)
            }else {
                console.log('被人挤下线:board:' +UserInfo[SaveOk].board)
                let str15 = utils.WriteCmdToBuffer(null,15);  //挤下线了发送互冲指令
                UserInfo[SaveOk].conn.sendBinary(str15)
                UserInfo[SaveOk].conn = conn
            }
            let str1 = utils.WriteCmdToBuffer(null,2);
            conn.sendBinary(str1)
        }else if(newData[2] == 13){  //处理心跳包
            SolveHeartPackage(conn)
        }else if(newData[2] == 5){
    
        }
    })
    
    function SolveHeartPackage(conn) {
        let curDate= new Date();
        for (var i=0;i<UserInfo.length;i++){
            if(UserInfo[i].conn == conn){
    
                UserInfo[i].Time = curDate
            }
        }
    }
    
    
    
    function checkUserInfo(board,soundBox){
        console.log(board,'棋盘id')
        console.log(soundBox,'音箱id')
    
        for (var i=0;i<UserInfo.length;i++){
            if(UserInfo[i].board == board){
                return i
            }else if(UserInfo[i].soundBox == soundBox){
                return i
            }
        }
        return null
    }
    module.exports = Observer;
    

    首先,我们代码里写的是 cmd == 13得时候去调用心跳包得函数 ,cmd是什么呢?cmd是我自己定义得一种数据格式,等于客户端如果给我发送数据是13开头得那说明他就是心跳数据,如果不是13那就是其他数据。

    function SolveHeartPackage(conn) {
        let curDate= new Date();
        for (var i=0;i<UserInfo.length;i++){
            if(UserInfo[i].conn == conn){
    
                UserInfo[i].Time = curDate
            }
        }
    }
    

    这个呢,是解析心跳包,首先我接收了到心跳包,传入了conn。这个时候呢我得想,怎么才能知道客户端离开了呢,于是我定义了一个类叫 UserInfo 里面有一个属性是time用于记录客户端传入得每一次接收他心跳得当前时间,谁传给我得。我绑定在谁得Userinfo上,Userinfo上绑定了很多信息,在我第一次接收客户端数据得时候,就创建了。 ps:心跳包可不是客户端一链接上就发送得哟。。 好,这个时候我把每个用户都绑定了最后得时间。

    setInterval(function () {
        let ii = []
        let date = new Date();
        for (var i = 0; i < UserInfo.length; i++) {
           let time = date -  UserInfo[i].Time
            if (time > 6000) {
                ii.push(i)
            }
        }
        if (ii.length !== 0) {
            for (var j = 0; j < ii.length; j++) {
                let index = ii[j]
                console.log('离线检查:'+ UserInfo.splice(i,1))
                UserInfo.splice(index,1)
    
            }
        }
    }, 6000) //6秒检查一次
    

    大家都知道这是一个异步得定时器,6秒执行一次。他得作用是什么呢?他得作用就是每过6秒去检测一下用户列表查看一下那个得时间长时间没有去更新了。如果找到了长时间没有去更新得,如果没有长时间更新那么我就是断开他得链接,就直接删除他在Userinfo里面数据。就ok了,我服务端得事情就ok了

     let date = new Date();
        for (var i = 0; i < UserInfo.length; i++) {
           let time = date -  UserInfo[i].Time
            if (time > 6000) {
                ii.push(i)
            }
        }
    

    这段代码就是去检测,是否超时,我设置得时间是6000毫秒,大家想设置多久。自己随意

    接下来是客户端,uniapp我建议做app大家可以去尝试一下很简单。基本得不教,自己去看文档,基本上都是ok得

    在这里插入图片描述

    我创建了一个netSocket.js文件

    
    function CreateTheSocket(ipAddress){
    	FNetSocket = uni.connectSocket({
    		url:ipAddress,
    		header: {
    			'binarytype': 'arraybuffer'
    		},
    		method: 'GET',
    		success: function(e) {}
    	});
    	store.state.FNetSocket = FNetSocket 
    	FNetSocket.onOpen(function(res){
    		console.log("ok socket connect ok");
    		SendUserDeviceInfo();
    	});
    	
    	/*
    		收到了socket 的数据...
    	*/
    	FNetSocket.onMessage(function(res){
    		console.log("ok receive From Server ");
    		var content = new Int8Array(res.data);
    		ReadData(content);
    	});
    	
    	FNetSocket.onClose(function(res){
    		console.log("ok here close the socket");
    		// #ifdef H5
    		var content = new Int8Array(evt.data);
    		ReadData(content);
    		// #endif
    			
    		// #ifdef APP-PLUS
    		if (evt && evt.data) {
    			var content = new Int8Array(evt.data);
    			ReadData(content);
    		}
    		// #endif
    	});
    	
    	FNetSocket.onError(function(res){
    		console.log("ok here the socket error");
    	})
    }
    

    这个是创建websocket得方法,看不懂得可以去对照文档看。应该可以得很简单,https://uniapp.dcloud.io/api/request/websocket

    FNetSocket 是创建socket之后返回得对象,我在创建得时候就给服务端发送了一条信息,SendUserDeviceInfo方法中发送了第一条数据

    function SendUserDeviceInfo(){
    	var S = store.state.boardDeviceid + "|" + store.state.soundBoxDeviceid;
    	var SendData = WriteString(null, S);
    	SendData = WriteCmdToBuffer(SendData, 1);
    	if (FNetSocket != null) {
    		FNetSocket.send({
    			data:SendData
    		})
    	}
    }
    

    这些WriteString,WriteCmdToBuffer,都是我自己封装得方法,就是把数据变成二进制得方法

    FNetSocket.onOpen 打开socket得时候,我就调用了发送数据得方法,发送得是一个cmd=1得方法,大家可以去看一下服务端,我在服务端cmd =1 得时候,给客户端发送了一个cmd = 2得数据,就是告诉客户端,他可以开始发送心跳了

    于是接下来就是,该在cmd=2得时候发送客户端得数据了

    ReadData方法里面去进行数据解析,由于我这里传输得是二进制数据,涉及算法解析问题,就不给大家看了

    由于在ReadData里面进行解析成功后,我接收到一个cmd= 2得数据在这里插入图片描述

    我在SendHeartPackage方法里面,封装了心跳发送。意思就是说每过5秒发送一条cmd = 13得数据给服务端,我在服务端得时候接收到cmd =13得时候去操作心跳,大家可以去看前面服务端得代码可以看的见得。 这就是心跳得原理,感觉也不难

    function SendHeartPackage(){
    	console.log('心跳')
    	setInterval(function(){
    			let SendData = WriteCmdToBuffer(null,13)  //cmd 13是心跳包
    			FNetSocket.send({
    				data:SendData
    			})
    			console.log('sendHeartPackage')
    	},5000)
    }
    

    大家如果要尝试玩玩就用字符串为传输得格式,如果说是正式项目得运用就用 Buffer数据,如果用过node.js写过服务端得应该不会陌生得。 https://www.runoob.com/nodejs/nodejs-buffer.html 这个文档是解释什么是buffer数据得。大家如果耐心看完了,请给个赞,顺便动手试一试, 其实TCP也是同理,个人所理解长连接得心跳都是这个道理!

    展开全文
  • netty心跳包demo

    2018-01-01 15:01:17
    这是更具netty的一个demo自己再修改一下 有问题可以联系我
  • 内容索引:脚本资源,jQuery,jQuery插件 jHeartbeat 是一个jQuery 的插件,用来定时执行某项任务,例如定时向服务器发送请求;定时更新页面元素等,特别适合用在聊天室开发上。压缩包内含有jHeartbeat.js代码及使用...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 97,848
精华内容 39,139
关键字:

心跳包

友情链接: UMLDoAn.rar