【Linux】Socket中的心跳机制(心跳包)
Socket中的心跳机制(心跳包)
1. 什么是心跳机制?(心跳包)
在客户端和服务端长时间没有相互发送数据的情况下,我们需要一种机制来判断连接是否依然存在。直接发送任何数据包可以实现这一点,但为了效率和简洁,通常发送一个空包,这个就是心跳包。
心跳包类似心跳,每隔固定时间发送一次,通知服务器客户端依然活着。它是一种保持长连接的机制,包的内容没有特别规定,通常是很小的包或仅包含包头的空包。

心跳包可以由客户端发到服务器,也可以由服务器发到客户端,但一般是客户端发到服务器。发送心跳包需要额外的线程,不能和正常数据发送的线程混在一起。发送间隔根据具体业务情况决定,通常在while循环中加sleep()函数即可。
2. 心跳包的实现技术
心跳包可以通过两种方式实现:
2.1 应用层自实现:
由应用程序自己发送心跳包来检测连接是否正常,服务器每隔一定时间向客户端发送一个短小的数据包,然后启动一个线程,在线程中不断检测客户端的回应, 如果在一定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在一定时间内没有收到服务器的心跳包,则认为连接不可用
2.2 使用SO_KEEPALIVE套接字选项:
在TCP机制中,默认存在一个心跳频率为2小时的机制,但无法检测断电、网线拔出、防火墙等断线情况。因此,在逻辑层常用空包发送来实现心跳包。服务器定时发送空包给客户端,客户端收到后回复一个同样的空包,服务器如果在一定时间内未收到回复则认为客户端掉线。在长连接下,如果一段时间没有数据往来,有可能会被中间节点断开连接,因此需要心跳包维持连接。一旦断线,服务器逻辑可能需要清理数据、重新连接等处理。通常情况下,心跳包的判定时间为30-40秒,要求高时可缩短至6-9秒。
1. setsockopt函数介绍
setsockopt函数用于设置与某个套接字关联的选项。选项可能存在于多层协议中,但它们总会出现在最上面的套接字层。要操作套接字层的选项,应该将层的值指定为SOL_SOCKET。要操作其他层的选项,需要提供合适的协议号。函数原型如下:
int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen)
sock: 将要被设置选项的套接字level: 选项所在的协议层optname: 需要访问的选项名optval: 指向包含新选项值的缓冲optlen: 现选项的长度
2. 心跳机制的实现
在TCP客户端代码中加入心跳机制,使服务端在断网重连后能自动保持连接。
#include "socket_tcp_server.h"
#include "tcp_keepalive.h"
#include "socket_wrap.h"
#include "ctype.h"
#include "FreeRTOS.h"
#include "task.h"static char ReadBuff[BUFF_SIZE];void vTcpKeepaliveTask(void){int cfd, n, i, ret;struct sockaddr_in server_addr;int so_keepalive_val = 1;int tcp_keepalive_idle = 3;int tcp_keepalive_intvl = 3;int tcp_keepalive_cnt = 3;int tcp_nodelay = 1;again: //创建socketcfd = Socket(AF_INET, SOCK_STREAM, 0);//使能socket层的心跳检测setsockopt(cfd, SOL_SOCKET, SO_KEEPALIVE, &so_keepalive_val, sizeof(int));server_addr.sin_family = AF_INET;server_addr.sin_port = htons(SERVER_PORT);server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);//连接到服务器ret = Connect(cfd, (struct sockaddr*)&server_addr, sizeof(server_addr));if(ret < 0){//100ms去连接一次服务器vTaskDelay(100);goto again;}//配置心跳检测参数setsockopt(cfd, IPPROTO_TCP, TCP_KEEPIDLE, &tcp_keepalive_idle, sizeof(int)); setsockopt(cfd, IPPROTO_TCP, TCP_KEEPINTVL, &tcp_keepalive_intvl, sizeof(int)); setsockopt(cfd, IPPROTO_TCP, TCP_KEEPCNT, &tcp_keepalive_cnt, sizeof(int)); setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, &tcp_nodelay, sizeof(int)); printf("server is connect ok\r\n");while(1){//等待服务器发送数据n = Read(cfd, ReadBuff, BUFF_SIZE);if(n <= 0){ goto again; }//进行大小写转换for(i = 0; i < n; i++){ ReadBuff[i] = toupper(ReadBuff[i]); }//写回服务器n = Write(cfd, ReadBuff, n);if(n <= 0){ goto again; } }
}
3. 心跳包的自主实现
服务端代码
主要思路:
在服务端代码中,首先创建一个监听套接字,等待客户端连接。一旦有客户端连接成功,就将其信息(包括客户端的文件描述符、IP地址和心跳计数器)存储在一个map中。同时,服务端启动一个心跳检测线程,定期遍历这个map,检查每个客户端的心跳计数器。若某客户端在一定时间内未发送心跳包,则认为该客户端掉线,关闭相应连接并从map中移除。
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <vector>
#include <map>
using namespace std;#define HEART_COUNT 5
#define BUF_SIZE 512
#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while (0)typedef map<int, pair<string, int>> FDMAPIP;enum Type
{HEART,OTHER
};struct PACKET_HEAD
{Type type;int length;
};//支线程传递的参数结构体
struct myparam
{FDMAPIP *mmap;
};//心跳线程
void *heart_handler(void *arg)
{printf("the heart-beat thread started\n");struct myparam *param = (struct myparam *)arg;//Server *s = (Server *)arg;while (1){FDMAPIP::iterator it = param->mmap->begin();for (; it != param->mmap->end();){// 3s*5没有收到心跳包,判定客户端掉线if (it->second.second == HEART_COUNT){printf("The client %s has be offline.\n", it->second.first.c_str());int fd = it->first;close(fd); // 关闭该连接param->mmap->erase(it++); // 从map中移除该记录}else if (it->second.second < HEART_COUNT && it->second.second >= 0){it->second.second += 1;++it;}else{++it;}}sleep(3); // 定时三秒}
}int main()
{//创建套接字int m_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (m_sockfd < 0){ERR_EXIT("create socket fail");}//初始化socket元素struct sockaddr_in server_addr;int server_len = sizeof(server_addr);memset(&server_addr, 0, server_len);server_addr.sin_family = AF_INET;//server_addr.sin_addr.s_addr = inet_addr("0.0.0.0"); //用这个写法也可以server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(39002);//绑定文件描述符和服务器的ip和端口号int m_bindfd = bind(m_sockfd, (struct sockaddr *)&server_addr, server_len);if (m_bindfd < 0){ERR_EXIT("bind ip and port fail");}//进入监听状态,等待用户发起请求int m_listenfd = listen(m_sockfd, 20);if (m_listenfd < 0){ERR_EXIT("listen client fail");}//定义客户端的套接字,这里返回一个新的套接字,后面通信时,就用这个m_connfd进行通信struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);int m_connfd = accept(m_sockfd, (struct sockaddr *)&client_addr, &client_len);printf("client accept success\n");string ip(inet_ntoa(client_addr.sin_addr)); // 获取客户端IP// 记录连接的客户端fd--><ip, count>,暂时就一个FDMAPIP mmap;mmap.insert(make_pair(m_connfd, make_pair(ip, 0)));struct myparam param;param.mmap = &mmap;// 创建心跳检测线程pthread_t id;int ret = pthread_create(&id, NULL, heart_handler, ¶m);if (ret != 0){printf("can't create heart-beat thread.\n");}//接收客户端数据,并相应char buffer[BUF_SIZE];while (1){if (m_connfd < 0){m_connfd = accept(m_sockfd, (struct sockaddr *)&client_addr, &client_len);printf("client accept success again!!!\n");}PACKET_HEAD head;int recv_len = recv(m_connfd, &head, sizeof(head), 0); // 先接受包头if (recv_len <= 0){close(m_connfd);m_connfd = -1;printf("client head lose connection!!!\n");continue;}if (head.type == HEART){mmap[m_connfd].second = 0;printf("receive heart beat from client.\n");}else{//接收数据部分}}//关闭套接字close(m_connfd);close(m_sockfd);printf("server socket closed!!!\n");return 0;
}
客户端代码
主要思路:
客户端代码中,首先创建一个套接字并连接到服务端。连接建立后,客户端启动一个心跳发送线程,定期向服务端发送心跳包。发送心跳包的过程不需要携带任何实际数据,只需发送一个标识心跳的消息即可。服务端收到心跳包后,将对应客户端的心跳计数器重置为0,表示该客户端仍然活跃。如果服务端未收到心跳包,则认为客户端已经掉线。
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <vector>
#include <map>
using namespace std;#define BUF_SIZE 512
#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while (0)enum Type
{HEART,OTHER
};struct PACKET_HEAD
{Type type;int length;
};//线程传递的参数结构体
struct myparam
{ int fd;
};void *send_heart(void *arg)
{printf("the heartbeat sending thread started.\n");struct myparam *param = (struct myparam *)arg;int count = 0; // 测试while (1){PACKET_HEAD head;//发送心跳包head.type = HEART;head.length = 0;send(param->fd, &head, sizeof(head), 0);// 定时3秒,这个可以根据业务需求来设定sleep(3); }
}int main()
{//创建套接字int m_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (m_sockfd < 0){ERR_EXIT("create socket fail");}//服务器的ip为本地,端口号struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = inet_addr("81.68.140.74");server_addr.sin_port = htons(39002);//向服务器发送连接请求int m_connectfd = connect(m_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));if (m_connectfd < 0){ERR_EXIT("connect server fail");}struct myparam param;param.fd = m_sockfd;pthread_t id;int ret = pthread_create(&id, NULL, send_heart, ¶m);if (ret != 0){printf("create thread fail.\n");}//发送并接收数据char buffer[BUF_SIZE] = "asdfg";int len = strlen(buffer);while (1){// 发送数据部分;}//断开连接close(m_sockfd);printf("client socket closed!!!\n");return 0;
}
相关文章:
【Linux】Socket中的心跳机制(心跳包)
Socket中的心跳机制(心跳包) 1. 什么是心跳机制?(心跳包) 在客户端和服务端长时间没有相互发送数据的情况下,我们需要一种机制来判断连接是否依然存在。直接发送任何数据包可以实现这一点,但为了效率和简洁,通常发送一个空包&am…...
Java怎样动态给对象添加属性并赋值【代码实现】
本篇文章主要介绍Java如何给已有实体类动态的添加字段并返回新的实体对象且不影响原来的实体对象结构。 参考代码如下: 引入依赖包 <dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2.2</…...
C#多线程同步lock、Mutex
C#使用多线程可以通过System.Threading命名空间下的Thread类来实现 lock和Mutex用于实现线程同步的机制: 上代码: class People{public People(int idd){id idd;}public int id;public int age;}class TestHelper{public TestHelper() { }List<Peo…...
模型实战(21)之 C++ - tensorRT部署yolov8-det 目标检测
C++ - tensorRT部署yolov8-det 目标检测 python环境下如何直接调用推理模型转换并导出:pt -> onnx ->.engineC++ tensorrt 部署检测模型不写废话了,直接上具体实现过程+all代码 1.Python环境下推理 直接命令行推理,巨简单yolo detect predict model=yolov8n.pt source…...
Linux完整版命令大全(二十一)
mkswap 功能说明:设置交换区(swap area)。语 法:mkswap [-cf][-v0][-v1][设备名称或文件][交换区大小]补充说明:mkswap可将磁盘分区或文件设为Linux的交换区。参 数: -c 建立交换区前,先检查是否有损坏的区…...
Elasticsearch集群许可证过期问题解决方法汇总
最近在使用elasticsearch的过程中,使用elastic-head进行可视化展示集群的状态和信息,从2024年5月18日突然elastic-head无法现在集群的状态界面啦,elasticsearch集群状态是正常,命令如下: curl -X GET "localhost:9200/_cluster/health?pretty" 在google页面上通过…...
Golang实现根据文件后缀删除文件和递归删除文件
概述 这个功能会非常强大,因为在日常工作中,我通常会遇到需要批量删除文件的场景,通过这个方法,再结合我的另一个 命令行开发框架,能够很轻松的开发出这个功能。 代码 package zdpgo_fileimport ("errors"…...
Python OCR 文字识别使用模型:读光-文字识别-行识别模型-中英-通用领域
介绍 什么是OCR? OCR是“Optical Character Recognition”的缩写,中文意为“光学字符识别”。它是一种技术,可以识别和转换打印在纸张或图像上的文字和字符为机器可处理的格式,如计算机文本文件。通过使用OCR技术,可…...
Python | Leetcode Python题解之第105题从前序与中序遍历序列构造二叉树
题目: 题解: class Solution:def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:if not preorder:return Noneroot TreeNode(preorder[0])stack [root]inorderIndex 0for i in range(1, len(preorder)):preorderVal pr…...
经典面试题:什么是事物的隔离级别?什么是MVCC?
目录 前言一、事物的隔离级别二、隔离级别是如何实现的1. 锁定技术2. 多版本并发控制(MVCC)基本原理实现机制优缺点 3. 乐观锁与悲观锁 三、MySQL设置隔离级别MySQL数据库Java (使用JDBC) 前言 事务的四大特性(ACID特性)…...
Java程序中,不同jar包,有2个完全相同的类,运行时的加载顺序
在Java程序中,如果有两个完全相同的类存在于不同的jar包中,那么在运行时究竟加载哪个类,主要取决于类加载器的类路径(Classpath)搜索顺序。 Java使用类加载器(ClassLoader)来动态加载类。当Jav…...
EI期刊的定金和尾款
当涉及到EI(工程索引)期刊发表并支付定金和尾款时,许多学者和研究人员可能会感到担忧,因为这涉及到一定的风险。在探讨这个话题时,我们需要考虑几个因素,包括期刊的声誉、可信度、出版质量以及作者的权益保…...
python+pytest+pytest-html+allure集成测试案例
pythonpytestpytest-htmlallure集成测试案例 下面是pythonpytestpytest-htmlallure四个组件同时集成使用的简单案例。 1. 项目结构 project/ │ ├── src/ │ ├── __init__.py │ ├── main.py │ ├── tests/ │ ├── __init__.py │ ├── conftest.p…...
STL-priority_queue的使用及其模拟实现
优先级队列(priority_queue)默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中的元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。 注意࿱…...
pycharm连接阿里云服务器过程记录
因为不想用自己的电脑安装anaconda环境,所以去查了一下怎么用服务器跑代码,试着用pycharm连接阿里云服务器,参考了很多博客,自己简单配置了一下,记录一下目前完成的流程. 主要是:阿里云服务器的远程登录和安装anaconda,以及怎么用pycharm连接阿里云服务器上的解释器. 小白刚开始…...
移动硬盘未格式化数据恢复及预防策略
随着数字化时代的到来,移动硬盘作为数据存储的重要载体,被广泛应用于个人和企业中。然而,当移动硬盘遭遇“未格式化”的困境时,其中的数据便岌岌可危。本文将深入探讨移动硬盘未格式化的现象、原因、数据恢复方案以及预防措施&…...
MySQL数据库入门之视图、存储过程、触发器
一、视图: 1.1、视图是什么?怎么理解? 视图是从数据库的基本表(或者视图)导出的虚表(数据库只放定义,它不实际存储数据,而是根据用户定义的SQL查询动态生成的结果集,具…...
Kafka原生API使用Java代码-生产者-分区策略-默认分区策略轮询分区策略
文章目录 1、代码演示1.1、pom.xml1.2、KafkaProducerPartitioningStrategy.java1.2.1、ProducerConfig.LINGER_MS_CONFIG取 0 值得情况,不轮询1.2.2、ProducerConfig.LINGER_MS_CONFIG取 0 值得情况,轮询1.2.3、ProducerConfig.LINGER_MS_CONFIG取 1000…...
网页中的音视频裁剪拼接合并
一、需求描述 项目中有一个配音需求: 1)首先,前台会拿到一个英语视频,视频的内容是A和B用英语交流; 2)然后,用户可以选择为某一个角色配音,假如选择为A配音,那么视频在播…...
【入门】使用sklearn实现的KNN算法:鸢尾花数据集分类预测
目录 前言 第一步:安装和导入sklean模块 第二步:获取数据 第二步:分割出训练集和测试集 第三步:训练模型 第四步:测试结果 总结 前言 本文将介绍如何利用K最近邻(KNN)算法对经典的鸢尾花数…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...
Python爬虫(一):爬虫伪装
一、网站防爬机制概述 在当今互联网环境中,具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类: 身份验证机制:直接将未经授权的爬虫阻挡在外反爬技术体系:通过各种技术手段增加爬虫获取数据的难度…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...
AI语音助手的Python实现
引言 语音助手(如小爱同学、Siri)通过语音识别、自然语言处理(NLP)和语音合成技术,为用户提供直观、高效的交互体验。随着人工智能的普及,Python开发者可以利用开源库和AI模型,快速构建自定义语音助手。本文由浅入深,详细介绍如何使用Python开发AI语音助手,涵盖基础功…...
k8s从入门到放弃之HPA控制器
k8s从入门到放弃之HPA控制器 Kubernetes中的Horizontal Pod Autoscaler (HPA)控制器是一种用于自动扩展部署、副本集或复制控制器中Pod数量的机制。它可以根据观察到的CPU利用率(或其他自定义指标)来调整这些对象的规模,从而帮助应用程序在负…...
