Linux内核源码剖析之TCP保活机制(KeepAlive)
写在前面:
版本信息:
Linux内核2.6.24(大部分centos、ubuntu应该都在3.1+。但是2.6的版本比较稳定,后续版本本质变化也不是很大)
ipv4 协议
https://blog.csdn.net/ComplexMaze/article/details/124201088
本文使用案例如上地址,感谢案例的分享,本篇文章核心部分还是在Linux内核源码分析~
为什么写下这篇文章,因为在实际项目中,是无法避免TCP通讯(对于这点,可能大部分Java程序员感受不到底层的网络通讯),正因为无法避免TCP通讯,恰好TCP通讯存在三次握手和四次挥手的过程,如果建立一次连接就三次握手和四次挥手,而我们清楚的知道三次握手和四次挥手是同步的过程,此过程也会带来不少的时间浪费和资源的浪费。所以Linux内核TCP网络协议栈就出现了KeepAlive机制,此机制减少三次握手和四次挥手次数,第一次建立连接后保持长连接,后续通讯就可以只考虑发送数据报文即可。往往出现一个机制解决某个问题,其他问题又出现,如果所有连接都建立长连接保活机制,而连接数又有限制,此时该如何解决呢?如下代码,Linux使用心跳机制去检测连接是否存活~
#define TCP_KEEPALIVE_TIME (120*60*HZ) // 首次,2小时
#define TCP_KEEPALIVE_PROBES 9 // 重试9次
#define TCP_KEEPALIVE_INTVL (75*HZ) // 后续,每75秒一次
- 在Linux内核中默认关闭KeepAlive
- 开启KeepAlive后,默认2小时后往对端发送心跳包,检查是否还活着
- 默认后续每75秒往对端发送心跳包,检查是否还活着
- 默认当对端9次都没有响应报文就发送RST报文,断开TCP连接,释放资源!
- 当然这一切参数都可以配置,通过sys_setsockopt系统调用,当然setsockopt函数库就行啦
回到上述描述的话题,往往出现一个机制解决某个问题,其他问题又出现。解决了频繁握手和挥手的时间,但是连接数量不够的问题又出现了,可能很多连接建立在那里,完全不通讯了,或者对端已经断网,或者宕机等等原因占用连接不释放,而Linux默认一个连接存活检测需要2个小时+ 才去检测对端是否活着,如果说服务器的负荷比较大,2小时才检测一次,会导致正常请求无法进行,所以此参数需要通过setsockopt函数库重新设置参数(当然,如果是Java等等虚拟机语言,本身也有自身的封装函数去操作setsockopt函数库,或者直接调用sys_setsockopt系统调用,这个需要看语言手册~!)话又说回来,如果设置的阈值大小、时间太短的问题也会很明显,一直都在发心跳包检测,甚至性能损耗大于了握手和挥手的时间,所以需要根据业务环境、服务器的硬件从性能损耗和空闲连接数量做折中考虑~
案例:
下面是C语言的服务端的案例源码,此案例是借用的,但是我们重点关心机制~
/*server.c*/
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/wait.h>
#include <netinet/tcp.h>
#define PORT 4000//端口号
#define BACKLOG 5/*最大监听数*/
#define MAX_DATA 100//接收到的数据最大程度
int main(){int sockfd,new_fd;/*socket句柄和建立连接后的句柄*/struct sockaddr_in my_addr;/*本方地址信息结构体,下面有具体的属性赋值*/struct sockaddr_in their_addr;/*对方地址信息*/int sin_size;char buf[MAX_DATA];//储存接收数据
sockfd=socket(AF_INET,SOCK_STREAM,0);//建立socket if(sockfd==-1){printf("socket failed:%d",errno);return -1;}my_addr.sin_family=AF_INET;/*该属性表示接收本机或其他机器传输*/my_addr.sin_port=htons(PORT);/*端口号*/my_addr.sin_addr.s_addr=htonl(INADDR_ANY);/*IP,括号内容表示本机IP*/bzero(&(my_addr.sin_zero),8);/*将其他属性置0*/if(bind(sockfd,(struct sockaddr*)&my_addr,sizeof(struct sockaddr))<0){//绑定地址结构体和socketprintf("bind error");return -1;}listen(sockfd,BACKLOG);//开启监听 ,第二个参数是最大监听数 while(1){sin_size=sizeof(struct sockaddr_in);new_fd=accept(sockfd,(struct sockaddr*)&their_addr,&sin_size);//在这里阻塞知道接收到消息,参数分别是socket句柄,接收到的地址信息以及大小 // 开启保活,1分钟内探测不到,断开连接int keep_alive = 1;int keep_idle = 3;int keep_interval = 1;int keep_count = 57;if (setsockopt(new_fd, SOL_SOCKET, SO_KEEPALIVE, &keep_alive, sizeof(keep_alive))) {perror("Error setsockopt(SO_KEEPALIVE) failed");exit(1);}if (setsockopt(new_fd, IPPROTO_TCP, TCP_KEEPIDLE, &keep_idle, sizeof(keep_idle))) {perror("Error setsockopt(TCP_KEEPIDLE) failed");exit(1);}if (setsockopt(new_fd, SOL_TCP, TCP_KEEPINTVL, (void *)&keep_interval, sizeof(keep_interval))) {perror("Error setsockopt(TCP_KEEPINTVL) failed");exit(1);}if (setsockopt(new_fd, SOL_TCP, TCP_KEEPCNT, (void *)&keep_count, sizeof(keep_count))) {perror("Error setsockopt(TCP_KEEPCNT) failed");exit(1);}while(new_fd != -1) {recv(new_fd,buf,MAX_DATA,0);//将接收数据打入buf,参数分别是句柄,储存处,最大长度,其他信息(设为0即可)。 printf("%s",buf);}}return 0;
}
此服务端案例非常的简单,当客户端与服务端建立连接后,修改KeepAlive的机制参数,使用setsockopt库函数修改。
SO_KEEPALIVE:开启KeepAlive机制
TCP_KEEPIDLE:首次检测的时长
TCP_KEEPINTVL:下次检测的间隔时长
TCP_KEEPCNT:重试阈值次数
源码分析:
首先看到TCP_KEEPIDLE、TCP_KEEPINTVL、TCP_KEEPCNT这三个参数的设置,源码在net/ipv4/tcp.c 文件do_tcp_setsockopt方法,此方法由sys_setsockopt系统调用方法调用。
static int do_tcp_setsockopt(struct sock *sk, int level,int optname, char __user *optval, int optlen)
{struct tcp_sock *tp = tcp_sk(sk);struct inet_connection_sock *icsk = inet_csk(sk);int val;int err = 0;switch (optname) {…………case TCP_KEEPIDLE: // 设置第一次触发的时间if (val < 1 || val > MAX_TCP_KEEPIDLE)err = -EINVAL;else {// 算出设置的时间tp->keepalive_time = val * HZ;// 如果KeepAlive机制已开启,并且当前不是关闭状态和监听状态。if (sock_flag(sk, SOCK_KEEPOPEN) &&!((1 << sk->sk_state) &(TCPF_CLOSE | TCPF_LISTEN))) {// 当前时间 - 上次ACK的时候 = 相对时间__u32 elapsed = tcp_time_stamp - tp->rcv_tstamp;if (tp->keepalive_time > elapsed)// 如果上次ACK同步的时间小于设置的时间,那就把剩余的时间算出来elapsed = tp->keepalive_time - elapsed;else// 如果上次ACK同步的时间大于设置的时间,那就立马检测elapsed = 0;// 设置内核的定时器inet_csk_reset_keepalive_timer(sk, elapsed);}}break;case TCP_KEEPINTVL: // 设置每次的间隔时间if (val < 1 || val > MAX_TCP_KEEPINTVL)err = -EINVAL;elsetp->keepalive_intvl = val * HZ;break;case TCP_KEEPCNT: // 设置阈值次数if (val < 1 || val > MAX_TCP_KEEPCNT)err = -EINVAL;elsetp->keepalive_probes = val;break;release_sock(sk);return err;
}
这里非常的简单,通过switch case的形式把参数添加到结构体中,并且设置了首次触发的时间
接下来,我们看到定时器何时设置的。在net/ipv4/tcp_ipv4.c 文件中tcp_v4_init_sock方法。
static int tcp_v4_init_sock(struct sock *sk)
{…………tcp_init_xmit_timers(sk);…………return 0;
}void tcp_init_xmit_timers(struct sock *sk)
{inet_csk_init_xmit_timers(sk, &tcp_write_timer, &tcp_delack_timer,&tcp_keepalive_timer);
}void inet_csk_init_xmit_timers(struct sock *sk,void (*retransmit_handler)(unsigned long),void (*delack_handler)(unsigned long),void (*keepalive_handler)(unsigned long))
{struct inet_connection_sock *icsk = inet_csk(sk);…………// 初始化sk->sk_timer,也即初始化timer_list// timer_list在内核是一个定时器的结构体init_timer(&sk->sk_timer);// 设置定时器的回调函数sk->sk_timer.function = keepalive_handler;…………
}
把大部分无关的代码省略掉以后,源码看起来非常的简单,这里初始化了定时器,并且把定时器的回调函数设置成tcp_keepalive_timer,所以接下来,我们直接分析tcp_keepalive_timer方法即可。在net/ipv4/tcp_timer.c 文件中 tcp_keepalive_timer方法。
// 当达到keepalive设置的值以后回掉此方法。
static void tcp_keepalive_timer (unsigned long data)
{struct sock *sk = (struct sock *) data;struct inet_connection_sock *icsk = inet_csk(sk);struct tcp_sock *tp = tcp_sk(sk);__u32 elapsed;/* Only process if socket is not in use. */bh_lock_sock(sk);if (sock_owned_by_user(sk)) {// 这里很简单,因为锁的原因,所以需要重试。inet_csk_reset_keepalive_timer (sk, HZ/20);goto out;}// 4次挥手阶段,而此时达到了保活的检测,此时发送RST报文给对端,表示我要断开了,然后释放资源即可。if (sk->sk_state == TCP_FIN_WAIT2 && sock_flag(sk, SOCK_DEAD)) {if (tp->linger2 >= 0) {const int tmo = tcp_fin_time(sk) - TCP_TIMEWAIT_LEN;if (tmo > 0) {tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);goto out;}}tcp_send_active_reset(sk, GFP_ATOMIC);goto death;}// 如果KeepAlive没有开启,或者当前已经是关闭状态if (!sock_flag(sk, SOCK_KEEPOPEN) || sk->sk_state == TCP_CLOSE)goto out;// 算出下次检测的时间elapsed = keepalive_time_when(tp);// 此时正在发送报文,所以无须检测,直接重置下次检测的时间if (tp->packets_out || tcp_send_head(sk))goto resched;// 算出距离上一次ACK的相对时间elapsed = tcp_time_stamp - tp->rcv_tstamp;// 如果上一次ACK的相对时间 大于等于 设置的时间,那么就代表达到一次阈值if (elapsed >= keepalive_time_when(tp)) {// 查看是否达到次数阈值,达到阈值后直接发送RST报文给对方,然后关闭连接。if ((!tp->keepalive_probes && icsk->icsk_probes_out >= sysctl_tcp_keepalive_probes) ||(tp->keepalive_probes && icsk->icsk_probes_out >= tp->keepalive_probes)) {tcp_send_active_reset(sk, GFP_ATOMIC);tcp_write_err(sk);goto out;}// 没达到阈值的情况// 尝试发送报文给对方,看是否还活着if (tcp_write_wakeup(sk) <= 0) {// 如果回复了,那就把下次检测的时间设置好icsk->icsk_probes_out++;elapsed = keepalive_intvl_when(tp);} else { // 对端没有回复,不知道是因为丢失还是怎么了,所以加快速度,尝试下一次。elapsed = TCP_RESOURCE_PROBE_INTERVAL;}} else {// 没有达到上次ACK的相对时间,所以算出差值,设置到定时器中。elapsed = keepalive_time_when(tp) - elapsed;}TCP_CHECK_TIMER(sk);sk_stream_mem_reclaim(sk);resched:// 把最新值设置到定时器中。inet_csk_reset_keepalive_timer (sk, elapsed);goto out;death:// 关闭连接,释放资源。tcp_done(sk);out:bh_unlock_sock(sk);sock_put(sk);
}
此方法是当定时器结束后回调执行,检测是否达到了我们设置或者默认的阈值,如果没有达到,再设置下一次定时器的时间,如果达到了就发送RST报文,关闭连接,释放资源~!
相关文章:
Linux内核源码剖析之TCP保活机制(KeepAlive)
写在前面: 版本信息: Linux内核2.6.24(大部分centos、ubuntu应该都在3.1。但是2.6的版本比较稳定,后续版本本质变化也不是很大) ipv4 协议 https://blog.csdn.net/ComplexMaze/article/details/124201088 本文使用案例…...
后端 springboot 给 vue 提供参数
前端 /** 发起新增或修改的请求 */requestAddOrEdit(formData) {debuggerif(formData.id undefined) {formData.id }getAction(/material/getNameModelStandard, {standard: this.model.standard,name: this.model.name,model: this.model.model}).then((res) > {if (res …...
《vue3实战》运用radio单选按钮或Checkbox复选框实现单选多选的试卷制作
文章目录 目录 系列文章目录 1.《Vue3实战》使用axios获取文件数据以及走马灯Element plus的运用 2.《Vue3实战》用路由实现跳转登录、退出登录以及路由全局守护 3.《vue3实战》运用Checkbox复选框实现单选多选的试卷展现(本文) 文章目录 前言 radio是什…...
排序算法-冒泡排序(C语言实现)
简介😀 冒泡排序是一种简单但效率较低的排序算法。它重复地扫描待排序元素列表,比较相邻的两个元素,并将顺序错误的元素交换位置,直到整个列表排序完成。 实现🧐 以下内容为本人原创,经过自己整理得出&am…...
星际争霸之小霸王之小蜜蜂(一)
目录 前言 一、安装pygame库 1、pygame库简介 2、在windows系统安装pygame库 二 、搭建游戏框架 1、创建游戏窗口 2、改变窗口颜色 总结 前言 大家应该都看过或者都听说过python神书“大蟒蛇”,上面有一个案例是《外星人入侵》,游戏介绍让我想起了上…...
图数据库_Neo4j基于docker服务版安装_Neo4j Desktop桌面版安装---Neo4j图数据库工作笔记0004
然后我们来看看如何用docker来安装Neo4j community server 首先去执行docker pull neo4j:3.5.22-community 去拉取镜像 然后执行命令就可以安装了 可以用docker ps查看一下 看看暴露了哪些端口 然后再看一下访问一下这个时候,要用IP地址了注意 然后再来看一下安装Desktop 去下…...
docker-compose部署可道云
文章目录 一. Mac1.1 下载源码1.2 部署1.2.1 修改密码部署(可忽略)1.2.2 直接部署 1.3 卸载1.4 访问 二. Win2.1 下载源码2.2 部署2.2.1 修改密码部署(可忽略)2.2.2 直接部署 2.3 卸载 一. Mac 1.1 下载源码 mkdir -p /Users/wanfei/docker-compose && cd /Users/wan…...
Windows上使用FFmpeg实现本地视频推送模拟海康协议rtsp视频流
场景 Nginx搭建RTMP服务器FFmpeg实现海康威视摄像头预览: Nginx搭建RTMP服务器FFmpeg实现海康威视摄像头预览_nginx rtmp 海康摄像头_霸道流氓气质的博客-CSDN博客 上面记录的是使用FFmpeg拉取海康协议摄像头的rtsp流并推流到流媒体服务器。 如果在其它业务场景…...
单片机之从C语言基础到专家编程 - 4 C语言基础 - 4.8 运算符
1.算术运算符 运算符名称备注加法运算符双目运算,a b-减法运算符双目运算,a - b*乘法运算符双目运算,a * b/除法运算符双目运算,a / b%求余运算符双目运算, a % b自增运算符单目运算, a–自减运算符单目运算, a– 2.关系运算符…...
轮腿机器人的PID控制
1 PID介绍 PID(Proportional Integral Derivative)控制系统。其实质是根据输入的偏差值,按比例、积分、微分的函数关系进行运算,运算结果用以输出进行控制。它是在长期的工程实践中总结出来的一套控制方法,实际运行经…...
ChatGPT爆火,会给教育带来什么样的影响或者冲击?
近来,人工智能聊天机器人ChatGPT连上热搜,火爆全网。ChatGPT拥有强大的信息整合能力、自然语言处理能力,可谓是“上知天文,下知地理”,而且还能根据要求进行聊天、撰写文章等。 ChatGPT一经推出,便迅速在社…...
Servlet+JDBC实战开发书店项目讲解第三篇:商品查询实现
ServletJDBC实战开发书店项目讲解第三篇:商品查询实现 本篇博客将介绍如何在ServletJDBC实战开发书店项目中实现商品查询功能。我们将从设计数据库表结构和实体类开始,一步一步详细讲解代码实现过程,包括前端页面的设计和后端Servlet代码的编…...
爬虫逆向实战(十七)--某某丁简历登录
一、数据接口分析 主页地址:某某丁简历 1、抓包 通过抓包可以发现数据接口是submit 2、判断是否有加密参数 请求参数是否加密? 通过查看“载荷”模块可以发现有一个enPassword加密参数 请求头是否加密? 通过查看请求头可以发现有一个To…...
《安富莱嵌入式周报》第320期:键盘敲击声解码, 军工级boot设计,开源CNC运动控制器,C语言设计笔记,开源GPS车辆跟踪器,一键生成RTOS任务链表
周报汇总地址:嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版: https://www.bilibili.com/video/BV1Cr4y1d7Mp/ 《安富莱嵌入式周报》第320期:键盘敲击…...
DRF 缓存
应用环境 django4.2.3 ,python3.10 由于对于服务而言,有些数据查询起来比较费时,所以,对于有些数据,我们需要将其缓存。 最近做了一个服务,用的时 DRF 的架构,刚好涉及缓存,特此记…...
Collada .dae文件格式简明教程【3D】
当你从互联网下载 3D 模型时,可能会在格式列表中看到 .dae 格式。 它是什么? 推荐:用 NSDT编辑器 快速搭建可编程3D场景。 1、Collada DAE概述 COLLADA是COLLAborative Design Activity(中文:协作设计活动)…...
在K8s上处理nginx
基本说明 创建一个名为ssl的TLS类型的Secret对象,用于存储证书和密钥信息。 kubectl create secret tls ssl --certserver.crt --keyserver.key配置Nginx的events块,设置worker连接数为1024。 events {worker_connections 1024; }配置Nginx的http块&a…...
嵌入式:ARM Day4
一、自己编写代码实现三盏灯点亮 源码: .text .global _start _start: 进行一次初始化bl RCC_INITbl LED1_INITbl LED2_INITbl LED3_INITb looploop: 循环开关灯bl LED1_ONbl delay_1sbl LED1_OFFbl delay_1sbl LED2_ONbl delay_1sbl LED2_OFFbl delay_1sbl…...
SpringBoot案例-员工管理-分页条件查询
根据页面原型,明确需求 页面原型 需求 查看接口文档 接口文档的链接如下: 【腾讯文档】SpringBoot案例所需文档 https://docs.qq.com/doc/DUkRiTWVaUmFVck9N 思路分析 分页条件查询就时将条件查询的结果进行分页展示,由于有的条件可能设…...
python控制obs实现无缝切换场景!obs-websocket-py
前言 最近一直在研究孪生数字人wav2lip。目前成果可直接输入高清嘴型,2070显卡1分钟音频2.6分钟输出。在直播逻辑上可以做到1比1.3这样,所以现在开始研究直播。在逻辑上涉及到了无缝切换,看到csdn上有一篇文章还要vip解锁。。。那自己研究吧…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
第一篇:Agent2Agent (A2A) 协议——协作式人工智能的黎明
AI 领域的快速发展正在催生一个新时代,智能代理(agents)不再是孤立的个体,而是能够像一个数字团队一样协作。然而,当前 AI 生态系统的碎片化阻碍了这一愿景的实现,导致了“AI 巴别塔问题”——不同代理之间…...
使用 SymPy 进行向量和矩阵的高级操作
在科学计算和工程领域,向量和矩阵操作是解决问题的核心技能之一。Python 的 SymPy 库提供了强大的符号计算功能,能够高效地处理向量和矩阵的各种操作。本文将深入探讨如何使用 SymPy 进行向量和矩阵的创建、合并以及维度拓展等操作,并通过具体…...
iview框架主题色的应用
1.下载 less要使用3.0.0以下的版本 npm install less2.7.3 npm install less-loader4.0.52./src/config/theme.js文件 module.exports {yellow: {theme-color: #FDCE04},blue: {theme-color: #547CE7} }在sass中使用theme配置的颜色主题,无需引入,直接可…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
java 局域网 rtsp 取流 WebSocket 推送到前端显示 低延迟
众所周知 摄像头取流推流显示前端延迟大 传统方法是服务器取摄像头的rtsp流 然后客户端连服务器 中转多了,延迟一定不小。 假设相机没有专网 公网 1相机自带推流 直接推送到云服务器 然后客户端拉去 2相机只有rtsp ,边缘服务器拉流推送到云服务器 …...
CentOS 7.9安装Nginx1.24.0时报 checking for LuaJIT 2.x ... not found
Nginx1.24编译时,报LuaJIT2.x错误, configuring additional modules adding module in /www/server/nginx/src/ngx_devel_kit ngx_devel_kit was configured adding module in /www/server/nginx/src/lua_nginx_module checking for LuaJIT 2.x ... not…...
WinUI3开发_使用mica效果
简介 Mica(云母)是Windows10/11上的一种现代化效果,是Windows10/11上所使用的Fluent Design(设计语言)里的一个效果,Windows10/11上所使用的Fluent Design皆旨在于打造一个人类、通用和真正感觉与 Windows 一样的设计。 WinUI3就是Windows10/11上的一个…...
联邦学习带宽资源分配
带宽资源分配是指在网络中如何合理分配有限的带宽资源,以满足各个通信任务和用户的需求,尤其是在多用户共享带宽的情况下,如何确保各个设备或用户的通信需求得到高效且公平的满足。带宽是网络中的一个重要资源,通常指的是单位时间…...
【学习记录】使用 Kali Linux 与 Hashcat 进行 WiFi 安全分析:合法的安全测试指南
文章目录 📌 前言🧰 一、前期准备✅ 安装 Kali Linux✅ 获取支持监听模式的无线网卡 🛠 二、使用 Kali Linux 进行 WiFi 安全测试步骤 1:插入无线网卡并确认识别步骤 2:开启监听模式步骤 3:扫描附近的 WiFi…...
