当前位置: 首页 > news >正文

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秒一次
  1. 在Linux内核中默认关闭KeepAlive
  2. 开启KeepAlive后,默认2小时后往对端发送心跳包,检查是否还活着
  3. 默认后续每75秒往对端发送心跳包,检查是否还活着
  4. 默认当对端9次都没有响应报文就发送RST报文,断开TCP连接,释放资源!
  5. 当然这一切参数都可以配置,通过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)

写在前面&#xff1a; 版本信息&#xff1a; Linux内核2.6.24&#xff08;大部分centos、ubuntu应该都在3.1。但是2.6的版本比较稳定&#xff0c;后续版本本质变化也不是很大&#xff09; 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复选框实现单选多选的试卷展现&#xff08;本文&#xff09; 文章目录 前言 radio是什…...

排序算法-冒泡排序(C语言实现)

简介&#x1f600; 冒泡排序是一种简单但效率较低的排序算法。它重复地扫描待排序元素列表&#xff0c;比较相邻的两个元素&#xff0c;并将顺序错误的元素交换位置&#xff0c;直到整个列表排序完成。 实现&#x1f9d0; 以下内容为本人原创&#xff0c;经过自己整理得出&am…...

星际争霸之小霸王之小蜜蜂(一)

目录 前言 一、安装pygame库 1、pygame库简介 2、在windows系统安装pygame库 二 、搭建游戏框架 1、创建游戏窗口 2、改变窗口颜色 总结 前言 大家应该都看过或者都听说过python神书“大蟒蛇”&#xff0c;上面有一个案例是《外星人入侵》&#xff0c;游戏介绍让我想起了上…...

图数据库_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实现海康威视摄像头预览&#xff1a; Nginx搭建RTMP服务器FFmpeg实现海康威视摄像头预览_nginx rtmp 海康摄像头_霸道流氓气质的博客-CSDN博客 上面记录的是使用FFmpeg拉取海康协议摄像头的rtsp流并推流到流媒体服务器。 如果在其它业务场景…...

单片机之从C语言基础到专家编程 - 4 C语言基础 - 4.8 运算符

1.算术运算符 运算符名称备注加法运算符双目运算&#xff0c;a b-减法运算符双目运算&#xff0c;a - b*乘法运算符双目运算&#xff0c;a * b/除法运算符双目运算&#xff0c;a / b%求余运算符双目运算, a % b自增运算符单目运算, a–自减运算符单目运算, a– 2.关系运算符…...

轮腿机器人的PID控制

1 PID介绍 PID&#xff08;Proportional Integral Derivative&#xff09;控制系统。其实质是根据输入的偏差值&#xff0c;按比例、积分、微分的函数关系进行运算&#xff0c;运算结果用以输出进行控制。它是在长期的工程实践中总结出来的一套控制方法&#xff0c;实际运行经…...

ChatGPT爆火,会给教育带来什么样的影响或者冲击?

近来&#xff0c;人工智能聊天机器人ChatGPT连上热搜&#xff0c;火爆全网。ChatGPT拥有强大的信息整合能力、自然语言处理能力&#xff0c;可谓是“上知天文&#xff0c;下知地理”&#xff0c;而且还能根据要求进行聊天、撰写文章等。 ChatGPT一经推出&#xff0c;便迅速在社…...

Servlet+JDBC实战开发书店项目讲解第三篇:商品查询实现

ServletJDBC实战开发书店项目讲解第三篇&#xff1a;商品查询实现 本篇博客将介绍如何在ServletJDBC实战开发书店项目中实现商品查询功能。我们将从设计数据库表结构和实体类开始&#xff0c;一步一步详细讲解代码实现过程&#xff0c;包括前端页面的设计和后端Servlet代码的编…...

爬虫逆向实战(十七)--某某丁简历登录

一、数据接口分析 主页地址&#xff1a;某某丁简历 1、抓包 通过抓包可以发现数据接口是submit 2、判断是否有加密参数 请求参数是否加密&#xff1f; 通过查看“载荷”模块可以发现有一个enPassword加密参数 请求头是否加密&#xff1f; 通过查看请求头可以发现有一个To…...

《安富莱嵌入式周报》第320期:键盘敲击声解码, 军工级boot设计,开源CNC运动控制器,C语言设计笔记,开源GPS车辆跟踪器,一键生成RTOS任务链表

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版&#xff1a; https://www.bilibili.com/video/BV1Cr4y1d7Mp/ 《安富莱嵌入式周报》第320期&#xff1a;键盘敲击…...

DRF 缓存

应用环境 django4.2.3 &#xff0c;python3.10 由于对于服务而言&#xff0c;有些数据查询起来比较费时&#xff0c;所以&#xff0c;对于有些数据&#xff0c;我们需要将其缓存。 最近做了一个服务&#xff0c;用的时 DRF 的架构&#xff0c;刚好涉及缓存&#xff0c;特此记…...

Collada .dae文件格式简明教程【3D】

当你从互联网下载 3D 模型时&#xff0c;可能会在格式列表中看到 .dae 格式。 它是什么&#xff1f; 推荐&#xff1a;用 NSDT编辑器 快速搭建可编程3D场景。 1、Collada DAE概述 COLLADA是COLLAborative Design Activity&#xff08;中文&#xff1a;协作设计活动&#xff09…...

在K8s上处理nginx

基本说明 创建一个名为ssl的TLS类型的Secret对象&#xff0c;用于存储证书和密钥信息。 kubectl create secret tls ssl --certserver.crt --keyserver.key配置Nginx的events块&#xff0c;设置worker连接数为1024。 events {worker_connections 1024; }配置Nginx的http块&a…...

嵌入式:ARM Day4

一、自己编写代码实现三盏灯点亮 源码&#xff1a; .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案例-员工管理-分页条件查询

根据页面原型&#xff0c;明确需求 页面原型 需求 查看接口文档 接口文档的链接如下&#xff1a; 【腾讯文档】SpringBoot案例所需文档 https://docs.qq.com/doc/DUkRiTWVaUmFVck9N 思路分析 分页条件查询就时将条件查询的结果进行分页展示&#xff0c;由于有的条件可能设…...

python控制obs实现无缝切换场景!obs-websocket-py

前言 最近一直在研究孪生数字人wav2lip。目前成果可直接输入高清嘴型&#xff0c;2070显卡1分钟音频2.6分钟输出。在直播逻辑上可以做到1比1.3这样&#xff0c;所以现在开始研究直播。在逻辑上涉及到了无缝切换&#xff0c;看到csdn上有一篇文章还要vip解锁。。。那自己研究吧…...

别再只用录屏软件了!用Unity Recorder H.264 MP4格式导出高清无压缩视频的完整配置流程

别再只用录屏软件了&#xff01;用Unity Recorder H.264 MP4格式导出高清无压缩视频的完整配置流程 在数字内容创作领域&#xff0c;视频输出质量往往直接决定作品的专业度。许多开发者习惯使用第三方录屏工具捕捉Unity运行画面&#xff0c;却忽略了引擎内置的Unity Recorder模…...

QQ音乐API逆向工程与数据解析技术架构深度解析

QQ音乐API逆向工程与数据解析技术架构深度解析 【免费下载链接】MCQTSS_QQMusic QQ音乐解析 项目地址: https://gitcode.com/gh_mirrors/mc/MCQTSS_QQMusic QQ音乐作为中国领先的数字音乐平台&#xff0c;其API接口设计与数据加密机制一直是技术社区关注的热点。本项目通…...

别再手动填Excel了!用EasyExcel 3.3.2 + SpringBoot实现模板化导出(附金额大写工具类)

告别手工填表&#xff1a;SpringBootEasyExcel智能报表生成实战 财务小张每周五下午都要面对同样的噩梦&#xff1a;从ERP系统导出销售数据&#xff0c;然后对照模板手动填写上百行Excel报表。金额大写转换要逐个核对&#xff0c;格式错位要反复调整&#xff0c;加班到深夜已成…...

避坑指南:RK3566给GC2053提供MCLK,分压电阻怎么选?实测波形告诉你答案

RK3566与GC2053时钟信号分压设计实战&#xff1a;从波形分析到电阻选型 当RK3566处理器需要为GC2053图像传感器提供MCLK时钟信号时&#xff0c;电平转换电路的设计往往成为项目成败的关键。许多工程师在首次设计分压电路时&#xff0c;会陷入"阻值越大功耗越小"的误区…...

GitHub下载速度提升10倍:Fast-GitHub终极解决方案

GitHub下载速度提升10倍&#xff1a;Fast-GitHub终极解决方案 【免费下载链接】Fast-GitHub 国内Github下载很慢&#xff0c;用上了这个插件后&#xff0c;下载速度嗖嗖嗖的~&#xff01; 项目地址: https://gitcode.com/gh_mirrors/fa/Fast-GitHub 还在为GitHub的龟速下…...

手把手教你给M301H-BYT盒子刷当贝纯净桌面(附Hi3798芯片短接点位图)

从零开始&#xff1a;M301H-BYT盒子刷机实战指南 家里的老旧电视盒子用久了总是卡顿、存储不足&#xff0c;还限制应用安装&#xff1f;今天我们就来彻底解决这个问题。本文将手把手教你如何为M301H-BYT盒子刷入当贝纯净桌面系统&#xff0c;让你的老设备重获新生。不同于简单的…...

别再混淆了!一文理清华为云Stack里FusionStorage、OceanStor Pacific与存储服务的对应关系

华为云Stack存储产品演进史&#xff1a;从FusionStorage到OceanStor Pacific的技术脉络解析 在云计算基础设施领域&#xff0c;存储系统的命名规则往往反映了技术架构的迭代路径。华为云Stack作为企业级混合云解决方案&#xff0c;其存储产品线经历了多次重大技术革新与品牌整合…...

Perplexity股票数据清洗SOP(含NASDAQ非标字段映射表):金融工程师内部使用的12项校验规则

更多请点击&#xff1a; https://codechina.net 第一章&#xff1a;Perplexity股票信息检索 Perplexity AI 公司尚未上市&#xff0c;因此不存在公开交易的股票代码、实时行情或交易所挂牌信息。这一事实常被开发者和投资者误读&#xff0c;尤其在使用金融数据 API 时容易触发…...

【MATLAB源码-第439期】基于MATLAB的APSK与QAM高阶调制在Saleh非线性功放下BER和EVM性能对比

操作环境&#xff1a;MATLAB 2024a1、算法描述摘要 高阶数字调制技术是现代无线通信和卫星通信系统提高频谱利用率的重要方法。QAM 调制通过同相分量和正交分量的幅度组合形成二维星座&#xff0c;在较高信噪比条件下能够获得较高的信息承载能力。APSK 调制则采用多环幅相结构&…...

关键字[Static]

一、static 的三种用法 1. 静态局部变量 * 特性: * - 只初始化一次(程序启动时) * - 函数返回后值保留(不销毁) * - 下次调用时保持上次的值 * - 存储在静态区,不在栈上 2. 静态全局变量(文件作用域限制) 仅在 xx.c 内可见,其他文件无法访问 3. 静态函数(文件作用域限…...