想建个网站/病毒式营销案例
序言
在本篇内容中,将为大家介绍在操作系统中的一个重要的机制 — 信号。大家可能感到疑惑,好像我在使用 Linux 的过程中并没有接触过信号,这是啥呀?其实我们经常遇到过,当我们运行的进程当进程尝试访问非法内存地址时,我们的进程会被中断,这是因为操作系统向该进程发送了中断信号。
Linux 操作系统离不开信号机制,在这篇文章中,让我们走进信号,了解信号的从哪里来,又到哪里去。
1. 信号的概念
1.1 定义
信号是操作系统向进程发送的一种通知,表示某个特定事件已经发生。在Unix、类Unix 以及其他系统中,信号被广泛使用。
1.2 特点
信号具有如下的特点:
异步性
:信号的产生对进程来说是异步的,即进程无法预知信号何时到来
。通知机制
:信号是一种软件中断(由软件程序触发的中断方式)
,用于中断进程的正常执行流程,使其处理特定事件。进程间通信
:虽然信号主要用于异常处理和系统调试,但也可以用于进程间的基本通信。
1.3 种类
在 Linux 系统下,使用指令:kill -l
即可查看所有的信号:
信号是使用宏定义的,每个信号前面的数字,就是该信号宏对应的值。
前 31 个信号为常规信号,其余为实时信号。在本篇文章中,我们主要讨论前 31 个常规信号。
你也可以使用指令: man 7 signal
查看每一个详细信息:
补充知识点:Core && Term
在描述信号的字段中,有一个叫做 Action
的特征,他的值大多都是 Core Term
这是什么呢?
Term
term
是 terminate
的缩写,表示默认动作是终止进程
。当进程接收到一个默认动作为 term
的信号时,进程会被立即终止。
Core
core
表示 默认动作是终止进程并生成一个核心转储(core dump)文件
。核心转储是一个包含进程在终止时的内存映像的文件,它对于调试程序非常有用,因为它 提供了进程终止时的状态信息
。
咦?就比如,SIGSEGV 段错误
当我的程序非法访问被终结时,被没有产生传说中的核心转储文件呀?这是因为你的服务器默认关闭了该功能,使用指令 ulimit -a
查看:
现在我们使用指令 ulimit -c 4096
开启该功能:
现在我们运行下一段程序:
8 int main()9 {10 11 int *ptr = NULL;12 *ptr = 1;13 14 return 0;15 }
程序不负众望地报错并退出了,产生了一个文件:
这个文件可以干嘛呢?当我们的程序出现异常时,相当该文件保存了案发现场,具体的用法是:
- 首先使用
gdb
调试你的程序:
- 之后输入指令
core-file your_core
可以看到,直接就复原了事故现场。
区别
- 进程终止:
term
信号会终止进程,但不生成核心转储文件
。term
信号通常是用于请求进程正常终止的情况。 - 调试信息:
core
信号不仅会终止进程,还会生成核心转储文件
,这包含了进程的内存映像、寄存器状态、堆栈跟踪等信息,用于调试目的。
2. 信号的产生
信号是从哪里产生的呢?虽然最后都是操作系统来执行对一个进程发送信号,但是是谁告诉操作系统这样做的呢?
2.1 用户操作 — kill
指令
当我们运行一个程序时,可以通过指令 kill
来让操作系统对该进程发送相应的信号,就比如,我们可以手动发送 SIGKILL 9号
信号将该进程终结,这里有一个程序:
1 TestSig1.cc X 1 #include <iostream>2 #include <unistd.h>3 4 5 int main()6 {7 while(true)8 {9 std::cout << "I am Running, my pid is " << getpid() << std::endl;10 sleep(1);11 }12 return 0;13 }
该程序会每秒打印相应的内容,现在我们可以使用相关的指令 kill -9 [pid]
来杀掉该进程:
可以看到该进程被杀掉了!!!
2.2 用户操作 — 按键操作
我们也可通过按键来让操作系统发送相关的信号,就比如我们平时终止一个进程的方式更多的是通过键盘按键,就比如 ctrl + c
:
其实这个按键对应的就是 3 号信号 SIGQUIT
。
2.3 用户操作 — 系统调用
操作系统提供一个系统调用 int kill(pid_t pid, int sig);
该函数你可以想指定进程发送信号:
pid
: 表示要发送信号的进程ID
sig
: 表示要发送的信号- 返回值:成功返回 0 ,失败返回 -1 ,错误码被设置
还有一个函数是 int raise(int sig);
,该函数是向当前进程发送指定信号,简单来说,相当于简化的 kill
=> kill(getpid(), int sig);
2.4 触发软件条件
在之前管道的学习中,我们了解到如果 读端被关闭了,写端一直再写
,那么操作系统就会认为这是一个坏掉的管道,就会发送 13 号信号 SIGPIPE
终止该进程,这就是触发了某种软件条件。
现在,在这里先向大家介绍几个非常重要的函数:
signal
函数
该函数允许程序员定义当特定信号发生时,程序应该如何响应, 简单说,这个函数用于捕获特定信号,然后执行指定操作的
。sighandler_t signal(int signum, sighandler_t handler);
:
signum
:指定要处理的信号类型。注意,SIGKILL
和SIGSTOP
这两个信号不能被捕获、阻塞或忽略。handler
:指定信号的处理方式。它可以是一个函数指针,指向一个用户定义的信号处理函数;也可以是SIG_IGN
,表示忽略该信号;或者是SIG_DFL
,表示采用信号的默认处理方式。- 返回值:成功时,
signal
函数返回之前为该信号设置的信号处理函数的指针。如果之前没有为该信号设置过处理函数,则返回SIG_DFL
。失败时,返回SIG_ERR
,并设置errno
以指示错误原因。
看着描述这么多,其实用起来不复杂,比如,现在我要捕获 2 号信号 SIGINT
,他的默认操作是退出,现在我不想要推出,想要执行我的逻辑:
1 TestSig1.cc X 1 #include <iostream>2 3 #include <unistd.h>4 #include <sys/types.h>5 #include <signal.h>6 7 void signal_handle(int signum)8 {9 std::cout << "I got you signal: " << signum << std::endl;10 }11 12 int main()13 {14 15 // 2号信号的捕获16 signal(2, signal_handle);17 18 while(true)19 {20 std::cout << "I am Running, my pid is " << getpid() << std::endl;21 sleep(1);22 }23 return 0;24 }
现在我们使用 ctrl + c
已经不能终止该进程了:
你也可将 signal(2, signal_handle);
中的函数换成 SIG_IGN
这样就会忽略该信号。
现在我有个想法就是将全部信号都捕获,在写个死循环,是不是就没有人把我停下来了!!!我们能想到的,人家肯定也想到了,规定 9 号信号 SIGKILL 和 19 号信号 SIGSTOP 这两个信号不能被捕获、阻塞或忽略。
保证系统的稳定性和管理员的控制权。
alarm
函数
大家为了早起都设置过闹钟吧,闹钟的作用就是时间一到就提醒我们执行某件任务。在 Linux
中的闹钟 alarm
也是一样的,我们设置一个定时,当时间一到执行某项任务,unsigned int alarm(unsigned int seconds);
:
seconds
:定时器应该等待的秒数。如果seconds
是 0,则任何当前设置的定时器都会被取消(你可以同时设置多个闹钟),但不会发送SIGALRM
信号。- 返回值:如果之前已经设置了定时器,
alarm
函数返回之前设置的剩余时间(秒)
,直到定时器到期。如果之前没有设置定时器,则返回 0。
当 alarm
定时器到期时,会向进程发送 SIGALRM
信号,终止进程:
12 int main()13 {14 15 // 设置一个闹钟,执行默认操作16 alarm(2);17 18 while(true)19 {20 std::cout << "I am Running, my pid is " << getpid() << std::endl;21 sleep(1);22 }23 return 0;24 }
两秒之后,进程自动终止:
但更多情况下,我们想要闹钟解释后执行我们的逻辑,而不是终止进程,那咋办呢? 捕获该信号,自定义处理信号
,这就需要我们上面说的 signal
函数了:
7 void signal_handle(int signum)8 {9 std::cout << "Your alarm clock is ringing." << std::endl;10 }11 12 int main()13 {14 15 // 设置一个闹钟,执行默认操作16 alarm(2);17 // 捕获闹钟信号18 signal(SIGALRM, signal_handle);19 20 while(true)21 {22 std::cout << "I am Running, my pid is " << getpid() << std::endl;23 sleep(1);24 }25 return 0;26 }
现在闹钟时间到了,就不会终止进程啦!但是,我还有一个疑问,你这个闹钟只能执行一次呀,之后就失效了,我想要一个一直生效的定时任务,怎么做到呢?当捕获并执行自定义函数时再设置一个闹钟不就好啦:
7 void signal_handle(int signum)8 {9 std::cout << "Your alarm clock is ringing." << std::endl;10 alarm(2);11 }
这样就得到一个持续的定时任务啦:
在这里的闹钟就是一个触发了软件条件(倒计时),从而产生信号发送给进程!
2.4 硬件异常
段错误
在我们的程序中,很可能涉及到 段错误(非法内存访问)
,具体触发错误的细节如下:
- 现代计算机使用内存管理单元(
MMU
)来管理内存。MMU
负责将虚拟地址(程序使用的地址)映射到物理地址(实际内存地址)
。 - 当我们尝试访问一个地址时,
MMU
尝试将虚拟地址翻译为物理地址,并检查该虚拟地址对应的页表项,以确定是否有权限访问该地址,以及地址是否有效 - 当
CPU
发现该块地址是无效的,或者是不具有写权限的,或者是无权限访问的
,将触发异常 - 操作系统向该进程发送
SIGSEG
的信号
这就是简单的硬件异常触发流程。
3. 信号的保存
现在我们已经基本了解了信号是从哪里来的,那么信号被一个进程接受过后,是以什么形式存在于进程当中呢?
在介绍信号的保存之前,希望大家记住这几个概念:
- 实际执行信号的处理动作称为信号递达(
Delivery
) - 信号从产生到递达之间的状态,称为信号未决(
Pending
)。
信号的信息被保存在一个进程的 task_struct
中:
我们来好好的介绍这 3 个结构:
3.1 block 位图
Block
位图用于指示哪些信号当前被进程 阻塞
。如果一个信号在 Block
位图中对应的位被设置(为 1 ),那么即使该信号已经到达,它也不会被立即处理,而是会 保持在未决状态,直到进程解除对该信号的阻塞。
3.2 pending 位图
Pending
位图(通常不是直接暴露给用户的,而是作为进程控制块 task_struct
的一部分)用于 跟踪哪些信号已经到达进程但尚未被处理
。每个位代表一个信号,如果该位被设置(通常为 1 ),则表示对应的信号已经到达且处于 未决状态
。
3.3 handler 函数指针数组
用户可以通过系统调用来设置特定信号的处理函数。当用户为某个信号注册了一个自定义的处理函数时,操作系统就会将该函数的地址存储在 handler
表中对应信号编号的位置。如果用户没有为某个信号设置自定义处理函数,那么当该信号发生时,操作系统就会 调用默认的处理函数
。
所以,我们总结一下,当一个信号传递给进程时,操作系统会将
pending
表中该信号对应的值置 1,如果block
表中的对应的值 也是 1,代表该信号被阻塞,不会被立即处理,直至解除阻塞;当解除阻塞或者一开始就不是阻塞状态的话,就会执行handle
表中该信号对应的函数操作。
3.4 验证结论
现在我们准备验证我们的想法,我们先阻塞一个信号,然后发送该信号,查看是否执行相关操作,在解除对该信号的阻塞,再次观察现象:
在这里会涉及到对信号集的操作,大家可以简单理解为
对信号集进行的对某个信号的阻塞操作最终会保存到阻塞表中
,在这里就不具体说明操作了,感兴趣的小伙伴,我找了一篇比较好的文章 👉信号集操作指南。
6 // 自定义函数7 void signal_handler(int signum)8 {9 std::cout << "Recived signal SIGINT!!!" << std::endl;10 }11 12 int main()13 {14 // 捕获信号15 signal(SIGINT, signal_handler);16 17 sigset_t sigset;18 // 初始化信号集19 sigemptyset(&sigset);20 // 添加指定信号到信号集21 sigaddset(&sigset, SIGINT);22 23 // 阻塞该信号24 if(sigprocmask(SIG_BLOCK, &sigset, NULL) == -1)25 {26 perror("sigprocmask");27 }28 29 std::cout << "SIGINT is blocked. Try pressing Ctrl+C after 5s!!!\n" << std::endl;30 sleep(5);31 32 // 解除阻塞33 if(sigprocmask(SIG_UNBLOCK, &sigset, NULL) == -1)34 {35 perror("sigprocmask");36 }37 38 std::cout << "SIGINT is unblocked. Try pressing Ctrl+C!!!\n" << std::endl;39 40 while(true)41 {42 sleep(1);43 }44 }
第一次我们按 ctrl + c
没什么反应,过了 5s 后,函数自动被执行,可以看出我们的结论是正确的。
4. 信号的处理
现在我们知道信号哪里来的了,也知道保存在哪里了,现在我们来看看信号的处理方式。
4.1 执行默认方式
对于没有为其注册信号处理函数的信号,进程会执行该信号的默认操作。就比如,SIGTERM
信号的默认操作是请求进程终止,而 SIGSEGV(段错误)
信号的默认操作是生成core文件并终止进程。
4.2 调用信号处理函数
如果进程为某个信号注册了信号处理函数(也称为信号处理器,上面代码中的 signal_handler
函数),那么当该信号到达时,内核会暂停进程的正常执行流程,转而调用该处理函数。
4.3 忽略信号
进程可以选择忽略某些信号。这意味着当这些信号到达时,进程不会执行任何特别的操作,而是继续执行其当前的代码路径。
然而,需要注意的是,并非所有信号都可以被忽略。例如,SIGKILL和SIGSTOP等信号是不能被忽略的
。
4.4 阻塞信号
进程可以选择屏蔽某些信号,以 避免在关键操作期间接收到这些信号
。通过调用sigprocmask
等系统调用,进程可以设置其信号屏蔽字,以决定哪些信号能够传递到进程中。被屏蔽的信号将保持在未决状态,直到屏蔽被解除后才会被处理。
4.5 阻塞和忽略的区别
这两个概念相当容易混淆,从定义上来说:
- 阻塞是指
进程选择性地阻止某些信号的传递
。当这些被阻塞的信号发生时,它们会被内核记录下来(处于未决状态
),但不会立即执行信号的处理函数或执行默认操作。 - 忽略是指进程对收到的某些信号
不执行任何操作
,即不调用处理函数也不执行默认操作,而是简单地丢弃这些信号。
大家可以这样理解:信号被阻塞就不会递达,而忽略是在递达之后可选的一种处理动作。
前者是处于未决的状态,后者是被递达后选择了忽略,不做其他处理。
总结
在这篇文章中介绍了信号的概念,也介绍了信号从哪里来,到哪里去,被接受处理的过程,希望大家有所收获😁。
相关文章:

Linux 操作系统 --- 信号
序言 在本篇内容中,将为大家介绍在操作系统中的一个重要的机制 — 信号。大家可能感到疑惑,好像我在使用 Linux 的过程中并没有接触过信号,这是啥呀?其实我们经常遇到过,当我们运行的进程当进程尝试访问非法内存地址时…...

黑马前端——days09_css
案例 1 页面框架文件 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><meta http-equiv"X-UA-Compati…...

【Python爬虫】技术深度探索与实践
目录 引言 第一部分:Python爬虫基础 1.1 网络基础 1.2 Python爬虫基本流程 第二部分:进阶技术 2.1 动态网页抓取 2.2 异步编程与并发 2.3 反爬虫机制与应对 第三部分:实践案例 第四部分:法律与道德考量 第五部分&#x…...

智启万象|挖掘广告变现潜力,保障支付安全便捷
谷歌致力于为开发者提供 先进的广告变现与支付解决方案 一起回顾 2024 Google 开发者大会 了解如何利用谷歌最新工具和功能 提高变现收入,优化用户体验,保障交易安全 让变现更上一层楼 广告检查器是谷歌 AdMob 平台最新推出的高级测试工具,开…...

函数递归,匿名、内置行数,模块和包,开发规范
一、递归与二分法 一)递归 1、递归调用的定义 递归调用:在调用一个函数的过程中,直接或间接地调用了函数本身 2、递归分为两类:直接与间接 #直接 def func():print(from func)func()func() # 间接 def foo():print(from foo)bar…...

Springboot3 整合swagger
一、pom.xml <dependency><groupId>org.springdoc</groupId><artifactId>springdoc-openapi-starter-webmvc-api</artifactId><version>2.1.0</version></dependency> 二、application.yml # SpringDoc配置 # springdoc:swa…...

查看同一网段内所有设备的ip
使用命令提示符(CMD)进行扫描 查看本机IP地址 首先通过 ipconfig /all 命令查看本机的IP地址,确定你的网段,例如 192.168.1.。 Ping网段内每个IP地址 接着使用循环命令: for /L %i IN (1,1,254) DO ping -w 1 -n …...

Spark MLlib 特征工程(上)
文章目录 Spark MLlib 特征工程(上)特征工程预处理 Encoding:StringIndexer特征构建:VectorAssembler特征选择:ChiSqSelector归一化:MinMaxScaler模型训练总结Spark MLlib 特征工程(上) 前面我们一起构建了一个简单的线性回归模型,来预测美国爱荷华州的房价。从模型效果来…...

《SPSS零基础入门教程》学习笔记——03.变量的统计描述
文章目录 3.1 连续变量(1)集中趋势(2)离散趋势(3)分布特征 3.2 分类变量(1)单个分类变量(2)多个分类变量 3.1 连续变量 (1)集中趋势 …...

2024年杭州市网络与信息安全管理员(网络安全管理员)职业技能竞赛的通知
2024年杭州市网络与信息安全管理员(网络安全管理员)职业技能竞赛的通知 一、组织机构 本次竞赛由杭州市总工会牵头,杭州市人力资源和社会保障局联合主办,杭州市萧山区总工会承办,浙江省北大信息技术高等研究院协办。…...

SpringBoot参数校验详解
前言 在web开发时,对于请求参数,一般上都需要进行参数合法性校验的,原先的写法时一个个字段一个个去判断,这种方式太不通用了,Hibernate Validator 是 Bean Validation 规范的参考实现,用于在 Java 应用中…...

安全基础学习-SHA-1(Secure Hash Algorithm 1)算法
SHA-1(Secure Hash Algorithm 1)是一种密码学哈希函数,用于将任意长度的输入数据(消息)转换成一个固定长度的输出(哈希值或摘要),长度为160位(20字节)。SHA-1的主要用途包括数据完整性验证、数字签名、密码存储等。 1、SHA-1 的特性 定长输出:无论输入数据长度是多…...

leetcode350. 两个数组的交集 II,哈希表
leetcode350. 两个数组的交集 II 给你两个整数数组 nums1 和 nums2 ,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可…...

基于YOLOv8的缺陷检测任务模型训练
文章目录 一、引言二、环境说明三、缺陷检测任务模型训练详解3.1 PCB数据集3.1.1 数据集简介3.1.2 数据集下载3.1.3 构建yolo格式的数据集 3.2 基于ultralytics训练YOLOv83.2.1 安装依赖包3.2.2 ultralytics的训练规范说明3.2.3 创建训练配置文件3.2.4 下载预训练模型3.2.5 训练…...

【upload]-ini-[SUCTF 2019]CheckIn-笔记
上传图片木马文件后看到,检查的文件内容,包含<? 一句话木马提示 检查的文件格式 用如下图片木马,加上GIF89a绕过图片和<?检查 GIF89a <script languagephp>eval($_POST[cmd])</script> .user.ini实际上就是一个可以由用…...

uniapp条件编译使用教学(#ifdef、#ifndef)
#ifdef //仅在xxx平台使用#ifndef //除了在xxx平台使用#endif // 结束 标识平台APP-PLUSAPPMP微信小程序/支付宝小程序/百度小程序/头条小程序/QQ小程序MP-WEIXIN微信小程序MP-ALIPAY支付宝小程序MP-BAIDU百度小程序MP-TOUTIAO头条小程序MP-QQQQ小程序H5H5APP-PLUS-NVUEApp nv…...

NXP i.MX8系列平台开发讲解 - 4.1.2 GNSS 篇(二) - 卫星导航定位原理
专栏文章目录传送门:返回专栏目录 Hi, 我是你们的老朋友,主要专注于嵌入式软件开发,有兴趣不要忘记点击关注【码思途远】 文章目录 关注星号公众号,不容错过精彩 作者:HywelStar Hi, 我是你们的老朋友HywelStar, 根…...

怎样在 SQL 中对一个包含销售数据的表按照销售额进行降序排序?
在当今数字化商业的浪潮中,数据就是企业的宝贵资产。对于销售数据的有效管理和分析,能够为企业的决策提供关键的支持。而在 SQL 中,对销售数据按照销售额进行降序排序,是一项基础但极其重要的操作。 想象一下,您面前有…...

DIAdem 与 LabVIEW
DIAdem 和 LabVIEW 都是 NI (National Instruments) 公司开发的产品,尽管它们有不同的核心功能和用途,但它们在工程、测试和测量领域中常常一起使用,以形成一个完整的数据采集、分析、处理和报告生成的解决方案。 1. 功能和用途 LabVIEW (Lab…...

UE虚幻引擎可以云渲染吗?应用趋势与挑战了解
虚幻云渲染技术是基于虚幻引擎的云端渲染技术,将虚幻引擎的渲染计算任务通过云计算的方式进行处理和渲染、并将渲染结果传输到终端设备上进行展示。虚幻引擎云渲染技术在近年来得到了迅猛的发展,并在各个领域得到了广泛的应用,包括游戏、电影…...

实战分享:DefenderUI在企业环境中的部署与应用
前言 想象一下,你的电脑就像一座坚固的城堡,但城门却时常被一些不速之客窥探甚至企图入侵;Defender,作为城堡自带的守护者,实力自然不容小觑;但你是否觉得它有时候太过低调,有些隐藏技能还没完…...

中英双语介绍金融经济中的鹰派 (Hawkish)和鸽派 (Dovish)
中文版 在金融和经济政策中,“鹰派”和“鸽派”是两种对货币政策和经济管理有不同立场的群体。 鹰派 (Hawkish) 鹰派倾向于担心通货膨胀的风险,通常支持较高的利率和更紧的货币政策,以防止经济过热和控制物价上涨。具体特征包括࿱…...

Android 开发中常用的布局类型及其选择指南
在 Android 开发过程中,选择正确的布局类型对于构建高效、美观且响应式的用户界面至关重要。本文将介绍 Android 中几种最常用的布局类型,并对比它们的特点和适用场景,帮助开发者们做出明智的选择。 1. LinearLayout - 线性布局 特点: LinearLayout 是最基本的布局类型之一…...

短视频SDK解决方案,降低行业开发门槛
美摄科技匠心打造了一款集前沿技术与极致体验于一体的短视频SDK解决方案,它不仅重新定义了短视频创作的边界,更以行业标杆级的短视频特效,让每一帧画面都闪耀不凡光芒。 【技术赋能,创意无限】 美摄科技的短视频SDK,…...

【C++】String常见函数用法
一、string类对象的常见构造 我们可采取以下的方式进行构造,以下是常用的接口: //生成空字符串 string; //拷贝构造函数 string(const string& str); //用C-string来构造string类对象 string(const char* s); //string类对象中包含n个字符c strin…...

LeetCode49.字母异位词分组
题目大意 给你一个字符串数组,请你将字母异位词组合在一起。可以按任意顺序返回结果列表。 字母异位词是由重新排列源单词的所有字母得到的一个新单词。 思路分析 示例 1: 输入: strs ["eat", "tea", "tan", "ate", &…...

Nginx日志按天分割
需求、日志按照天的单位进行分割存储。 如果你直接百度,可能会搜到很多教你用各种脚本或是三方插件来按天分割的,这边我用nginx服务本身来分割日志。 方法一 通过使用 $time_iso8601 变量和 map 指令,实现了日志文件按天分割的功能。以下是…...

文本摘要简介
文本摘要是从一段长文本中提取出最重要的信息,并生成一个简短而有意义的摘要。这个过程可以分为两种主要方法: 抽取式摘要(Extractive Summarization):从原文中直接提取出关键句子或段落,组成摘要…...

3.MySQL面试题之Redis 和 Mysql 如何保证数据一致性?
Redis 和 MySQL 数据一致性是分布式系统中的一个常见挑战。保证数据一致性通常涉及几种策略,我会详细解释这些策略并提供相应的代码示例。 先更新数据库,再更新缓存 这种方法先更新 MySQL,然后更新或删除 Redis 缓存。 Transactional publ…...

浅谈TCP协议、UDP协议
一、介绍说明 TCP(传输控制协议) 面向连接:TCP在数据传输之前必须建立连接。这通过一个称为三次握手的过程来完成,确保连接的两端都准备好进行数据传输。 可靠性:TCP提供可靠的数据传输,确保数据包正确无…...