【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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/18944.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

npm入门介绍

npm&#xff08;Node Package Manager&#xff09;是 Node.js 的包管理工具&#xff0c;用于安装、管理和共享 JavaScript 代码包。它是全球最大的软件注册表&#xff0c;拥有数百万个开源包&#xff0c;为开发者提供了丰富的资源和工具。以下是 npm 的详细介绍&#xff1a; 1…

vs工程添加自定义宏

一、简介 用户可以添加自定义宏变量方便工程路径名称的修改和配置 例&#xff1a;$(SolutionDir) 为解决方案路径&#xff0c;$(PojectDir) 为工程所在路径 测试环境&#xff1a;vs2017&#xff0c;qt5.14.0 二、配置 1、打开属性窗口&#xff1a;视图-》其他窗口-》属性管…

四数之和-力扣

本题在三数之和的基础上&#xff0c;再增加一重循环进行解答 首先注意的点是&#xff0c;一级剪枝处理&#xff0c;target > 0 && nums[i] > target 此处只有整数才可剪枝处理&#xff0c;如果target为负数&#xff0c;nums[i] < target&#xff0c;也不能代…

精准操控时间的艺术:JavaScript节流函数的深度探索与实践【含代码示例】

精准操控时间的艺术&#xff1a;JavaScript节流函数的深度探索与实践【含代码示例】 节流基础&#xff1a;概念与作用实现策略&#xff1a;案例展示案例一&#xff1a;基础定时器实现案例二&#xff1a;立即执行版本案例三&#xff1a;使用requestAnimationFrame实现动画节流 功…

蓝桥杯-AB路线(详细原创)

问题描述&#xff1a; 有一个由 N M 个方格组成的迷宫&#xff0c;每个方格写有一个字母 A 或者 B。小蓝站在迷宫左上角的方格&#xff0c;目标是走到右下角的方格。他每一步可以移动到上下左右相邻的方格去。 由于特殊的原因&#xff0c;小蓝的路线必须先走 K 个 A 格子、再…

前端面试题日常练-day40 【面试题】

题目 希望这些选择题能够帮助您进行前端面试的准备&#xff0c;答案在文末 1. Bootstrap 的栅格系统是基于&#xff08; &#xff09;进行布局的。A. 像素 B. 百分比 C. 媒体查询 2. 在 Bootstrap 中&#xff0c;要创建一个按钮&#xff0c;可以使用&#xff08; &#xff…

【CSharp】判断目录以及文件是否存在

【CSharp】判断目录以及文件是否存在 1.背景2.判断目录3.判断文件1.背景 我们在进行磁盘IO的时候进行需要判断目录、文件是否存在,根据判断结果再做进一步的操作。 其中判断目录是否存在,涉及Directory.Exists(String) 方法; 命名空间:System.IO 方法功能:确定给定路径是…

Spring OAuth2:开发者的安全盾牌!(下)

上文我们教了大家如何像海盗一样寻找宝藏&#xff0c;一步步解锁令牌的奥秘&#xff0c;今天将把更加核心的技巧带给大家一起学习&#xff0c;共同进步&#xff01; 文章目录 6. 客户端凭证与密码模式6.1 客户端凭证模式应用适用于后端服务间通信 6.2 密码模式考量直接传递用户…

(精确度,召回率,真阳性,假阳性)ACC、敏感性、特异性等 ROC指标

&#xff08;精确度&#xff0c;召回率&#xff0c;真阳性&#xff0c;假阳性&#xff09;ACC、敏感性、特异性等 ROC指标

【微机原理及接口技术】可编程计数器/定时器8253

【微机原理及接口技术】可编程计数器/定时器8253 文章目录 【微机原理及接口技术】可编程计数器/定时器8253前言一、8253的内部结构和引脚二、8253的工作方式三、8253的编程总结 前言 本篇文章就8253芯片展开&#xff0c;详细介绍8253的内部结构和引脚&#xff0c;8253的工作方…

E: Package ‘curl‘ has no installation candidate

错误1&#xff1a; E: Package curl has no installation candidate Package curl is not available, but is referred to by another package. This may mean that the package is missing, has been obsoleted, or is only available from another sourceE: Package curl …

人工智能初识

&#x1f31e;欢迎来到人工智能基础的世界 &#x1f308;博客主页&#xff1a;卿云阁 &#x1f48c;欢迎关注&#x1f389;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f31f;本文由卿云阁原创&#xff01; &#x1f4c6;首发时间&#xff1a;&#x1f339;2024年5月1…

C/C++语法|pthread线程库的使用

笔记主要内容来自 爱编程的大柄–线程 爱编程的大柄–线程同步 在进入代码实践之前&#xff0c;我们应该搞清楚。 线程是成语的最小执行单位&#xff0c;进程是操作系统中最小的资源分配单位。 这样的话我们可以理解以下两点&#xff1a; 同一地址空间中的多个线程独有的是&…

618手把手教你捡漏服务器

618最全捡漏攻略 捡漏规则1、新人优惠⭐⭐⭐2、教育优惠⭐⭐3、回馈活动⭐️ ECS价格对比新人优惠&#x1f49d;京东云 50/年百度云 60.69/年阿里云 82/年腾讯云 99/年 回馈活动&#x1f381;阿里云 教育优惠&#x1f3eb;阿里云腾讯云 hi&#xff0c;好久不见各位&#xff0c;…

16 -java反射

目录 第16章 反射 16.1 反射的概念 16.2反射的作用 16.3 java.lang.Class类 16.3.1 哪些类型可以获取Class对象 16.3.2 获取Class对象的四种方式 16.4 反射的基本应用 16.4.1 获取类型的详细信息 16.4.2 创建任意引用类型的对象 16.4.3 操作任意类型的属性 16.4.4 调…

【JS基础知识07】函数

一&#xff1a;函数是什么以及函数作用 1 函数是什么 函数是经过封装、调用后&#xff0c;能够完成特定任务的代码块 2 函数的作用 仅需“函数名(实参)”就可以调用函数&#xff0c;起到精简代码&#xff0c;提高开发效率的作用 二&#xff1a;函数使用 1 语法规则&#…

探索零阶优化:在语言模型提示优化中的应用与原理【初步理解】

什么是零阶优化&#xff1f; 零阶优化&#xff08;Zero-Order Optimization&#xff09;是一种优化方法&#xff0c;用于在没有显式梯度信息的情况下优化目标函数。与常见的梯度下降法不同&#xff0c;零阶优化不依赖于目标函数的梯度&#xff0c;而是通过直接评估目标函数值来…

达梦数据库创建表空间

CREATE TABLESPACE TBS_SZJG_FRDATA DATAFILE ‘TBS_SZJG_FRDATA.DBF’ size 128 AUTOEXTEND ON NEXT 100;

光耦的工作原理

一、光电耦合器简介 光电耦合器主要是一种围绕光作为媒介的光电转换元器件&#xff0c;能够实现光到电、电到光之间的自由转换。我们又可以称之为光电隔离器&#xff0c;之所以这么称呼&#xff0c;主要是因为光电耦合器能够很好的对电路中的电信号起到隔离的作用。有效的保护…

npm镜像源管理

查看当前使用的镜像源 npm config get registry --locationglobal 设置使用官方源 npm config set registry https://registry.npmjs.org/ --locationglobal 设置淘宝镜像源 npm config set registry https://registry.npm.taobao.org/ --locationglobal 设置其他镜像源 npm co…