【Linux】使用管道实现一个简易版本的进程池
文章目录
- 使用管道实现一个简易版本的进程池
- 流程图
- 代码
- makefile
- Task.hpp
- ProcessPool.cc
- 程序流程:
使用管道实现一个简易版本的进程池
流程图
代码
makefile
ProcessPool:ProcessPool.ccg++ -o $@ $^ -g -std=c++11
.PHONY:clean
clean:rm -f ProcessPool
Task.hpp
#pragma once#include <iostream>
#include <vector>typedef void (*task_t)(); //定义了一个函数指针类型task_t,它指向返回类型为void且不接受任何参数的函数。void task1()
{std::cout << "lol 刷新日志" << std::endl;
}
void task2()
{std::cout << "lol 更新野区,刷新出来野怪" << std::endl;
}
void task3()
{std::cout << "lol 检测软件是否更新,如果需要,就提示用户" << std::endl;
}
void task4()
{std::cout << "lol 用户释放技能,更新用的血量和蓝量" << std::endl;
}void LoadTask(std::vector<task_t> *tasks) // 该函数接受一个指向std::vector<task_t>的指针,并将其作为参数
{tasks->push_back(task1); //将task1函数的地址添加到向量中。tasks->push_back(task2);tasks->push_back(task3);tasks->push_back(task4);
}
ProcessPool.cc
#include "Task.hpp" // 包含任务相关的头文件
#include <string>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>const int processnum = 10; // 设定进程池大小为10
std::vector<task_t> tasks; // 存储任务的向量// 定义channel类,用于管理进程间通信
class channel
{
public:channel(int cmdfd, int slaverid, const std::string &processname):_cmdfd(cmdfd), _slaverid(slaverid), _processname(processname){}
public:int _cmdfd; // 用于向子进程发送命令的文件描述符pid_t _slaverid; // 子进程IDstd::string _processname; // 子进程名称,用于日志显示
};// 子进程执行的函数
void slaver()
{while(true){int cmdcode = 0;// 从标准输入(被重定向到管道)读取命令int n = read(0, &cmdcode, sizeof(int)); if(n == sizeof(int)){std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " << cmdcode << std::endl;// 执行对应的任务if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();}if(n == 0) break; // 管道关闭时退出}
}// 初始化进程池
void InitProcessPool(std::vector<channel> *channels)
{std::vector<int> oldfds; // 存储历史文件描述符for(int i = 0; i < processnum; i++){int pipefd[2];int n = pipe(pipefd); // 创建管道assert(!n);(void)n;pid_t id = fork(); // 创建子进程if(id == 0) // 子进程{// 关闭历史文件描述符std::cout << "child: " << getpid() << " close history fd: ";for(auto fd : oldfds) {std::cout << fd << " "; // 打印当前文件描述符的值,用于显示子进程正在关闭哪些文件描述符。close(fd); // 关闭文件描述符}std::cout << "\n";close(pipefd[1]); // 关闭写端dup2(pipefd[0], 0); // 将管道读端重定向到标准输入close(pipefd[0]); //关闭读端slaver(); // 执行子进程任务std::cout << "process : " << getpid() << " quit" << std::endl;exit(0);}// 父进程close(pipefd[0]); // 关闭读端// 创建新的channel并添加到channels中std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));oldfds.push_back(pipefd[1]);sleep(1);}
}// 打印调试信息
void Debug(const std::vector<channel> &channels)
{for(const auto &c :channels){std::cout << c._cmdfd << " " << c._slaverid << " " << c._processname << std::endl;}
}// 显示菜单
void Menu()
{std::cout << "################################################" << std::endl;std::cout << "# 1. 刷新日志 2. 刷新出来野怪 #" << std::endl;std::cout << "# 3. 检测软件是否更新 4. 更新用的血量和蓝量 #" << std::endl;std::cout << "# 0. 退出 #" << std::endl;std::cout << "#################################################" << std::endl;
}// 控制子进程执行任务
void ctrlSlaver(const std::vector<channel> &channels)
{int which = 0;while(true){int select = 0;Menu();std::cout << "Please Enter@ ";std::cin >> select;if(select <= 0 || select >= 5) break;int cmdcode = select - 1;// 轮询方式分配任务给子进程std::cout << "father say: " << " cmdcode: " <<cmdcode << " already sendto " << channels[which]._slaverid << " process name: " << channels[which]._processname << std::endl;write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));which++;which %= channels.size();}
}// 清理进程池
void QuitProcess(const std::vector<channel> &channels)
{for(const auto &c : channels){close(c._cmdfd); // 关闭所有管道waitpid(c._slaverid, nullptr, 0); // 等待所有子进程结束}
}int main()
{LoadTask(&tasks); // 加载任务列表srand(time(nullptr)^getpid()^1023); // 初始化随机数种子std::vector<channel> channels; //InitProcessPool(&channels); // 初始化进程池ctrlSlaver(channels); // 控制子进程执行任务QuitProcess(channels); // 清理进程池return 0;
}
程序流程:
1.main
函数首先调用LoadTask(&tasks)
,将task1
到task4
四个任务的函数地址存入全局tasks
向量。
2.srand(time(nullptr)^getpid()^1023);
初始化随机数种子
3.std::vector<channel> channels;
,这行代码的作用是定义一个名为 channels
的向量(std::vector
),用于存储 channel
类型的对象。它的主要作用是管理多个 channel
对象,每个 channel
对象代表一个子进程的通信通道。
-
每个
channel
对象包含以下信息:-
_cmdfd
:用于向子进程发送命令的文件描述符(管道写端)。 -
_slaverid
:子进程的进程ID(PID)。 -
_processname
:子进程的名称,用于日志和调试。
-
-
channels
向量存储了所有子进程的通信信息,父进程可以通过它管理所有子进程。
4.InitProcessPool(&channels);
,初始化进程池
// 初始化进程池
void InitProcessPool(std::vector<channel> *channels)
{std::vector<int> oldfds; // 存储历史文件描述符for(int i = 0; i < processnum; i++){int pipefd[2];int n = pipe(pipefd); // 创建管道assert(!n);(void)n;pid_t id = fork(); // 创建子进程if(id == 0) // 子进程{// 关闭历史文件描述符std::cout << "child: " << getpid() << " close history fd: ";for(auto fd : oldfds) {std::cout << fd << " ";close(fd);}std::cout << "\n";close(pipefd[1]); // 关闭写端dup2(pipefd[0], 0); // 将管道读端重定向到标准输入close(pipefd[0]); //关闭读端slaver(); // 执行子进程任务std::cout << "process : " << getpid() << " quit" << std::endl;exit(0);}// 父进程close(pipefd[0]); // 关闭读端// 创建新的channel并添加到channels中std::string name = "process-" + std::to_string(i);channels->push_back(channel(pipefd[1], id, name));oldfds.push_back(pipefd[1]);sleep(1);}
}
5.std::vector<int> oldfds;
的作用是存储父进程中已经创建的管道的写端文件描述符(pipefd[1]
)。它的主要目的是在创建新的子进程时,确保子进程能够关闭不需要的文件描述符,避免资源泄露和潜在的问题。
为什么需要
oldfds
?
文件描述符的继承:
当父进程通过
fork()
创建子进程时,子进程会继承父进程的所有打开的文件描述符。如果父进程创建了多个管道(每个子进程对应一个管道),那么每个子进程都会继承所有管道的文件描述符,即使这些管道是用于其他子进程的。
资源泄露问题:
如果子进程不关闭不需要的文件描述符,这些文件描述符会一直保持打开状态,导致资源泄露。
例如,假设父进程创建了 10 个子进程,每个子进程都会继承 10 个管道的文件描述符,但实际上每个子进程只需要一个管道的读端文件描述符。
避免干扰:
- 如果子进程不关闭不需要的文件描述符,可能会导致意外的行为。例如,某个子进程可能会错误地读取其他子进程的管道数据。
6.for(int i = 0; i < processnum; i++)
,循环 processnum=10
次,每次创建一个子进程和一个管道。
7.int pipefd[2];
pipefd
是一个长度为 2 的整型数组,用于存储管道的两个文件描述符:
pipefd[0]
:管道的 读端文件描述符,用于从管道中读取数据。pipefd[1]
:管道的 写端文件描述符,用于向管道中写入数据。
8.int n = pipe(pipefd);
调用 pipe
系统函数来创建一个管道,并将结果存储在变量 n
中。
1.
pipe
系统函数的作用
pipe
是一个系统调用,用于创建一个管道。管道的本质是一个内核缓冲区,用于在两个进程之间传递数据。管道有两个端点:
- 读端:用于从管道中读取数据。
- 写端:用于向管道中写入数据。
pipe
函数的原型如下:int pipe(int pipefd[2]);
2. 参数
pipefd[2]
pipefd
是一个长度为 2 的整型数组,用于存储管道的两个文件描述符:
pipefd[0]
:管道的 读端文件描述符,用于从管道中读取数据。pipefd[1]
:管道的 写端文件描述符,用于向管道中写入数据。
3. 返回值
n
- 如果
pipe
调用成功,返回0
。- 如果
pipe
调用失败,返回-1
,并设置errno
表示错误原因。
4. 代码解析
int n = pipe(pipefd);
pipe(pipefd)
:调用pipe
函数创建管道。n
:存储pipe
函数的返回值,用于检查管道是否创建成功。
9.assert(!n);
,(void)n;
assert(!n)
:确保管道创建成功。如果pipe
调用失败,程序会终止。(void)n
:忽略未使用的变量警告。
10.pid_t id = fork();
,创建子进程
if(id == 0) // 子进程
{// 关闭历史文件描述符std::cout << "child: " << getpid() << " close history fd: ";for(auto fd : oldfds) {std::cout << fd << " ";close(fd);}std::cout << "\n";close(pipefd[1]); // 关闭写端dup2(pipefd[0], 0); // 将管道读端重定向到标准输入close(pipefd[0]); //关闭读端slaver(); // 执行子进程任务std::cout << "process : " << getpid() << " quit" << std::endl;exit(0);
}
11.在子进程中,id
为 0。
12.std::cout << "child: " << getpid() << " close history fd: ";
打印当前子进程的PID,用于区分不同子进程
" close history fd: "
,说明接下来要关闭的文件描述符
for(auto fd : oldfds) {std::cout << fd << " ";// 打印当前文件描述符的值,用于显示子进程正在关闭哪些文件描述符。close(fd);// 关闭文件描述符
}
在子进程中遍历 oldfds
向量,关闭所有不需要的文件描述符。
具体来说,它的目的是确保子进程只保留与自己相关的文件描述符,关闭其他无关的文件描述符,从而避免资源泄露和潜在的问题。
close(pipefd[1]); // 子进程关闭写端,因为子进程只需要读取命令
dup2(pipefd[0], 0); // 将父进程管道读端重定向到标准输入
close(pipefd[0]); //关闭父进程读端
slaver(); // 执行子进程任务
dup2函数将管道的读端(pipefd[0])复制到标准输入(0)
这意味着之后从标准输入读取的数据实际上是从管道读取的
后续代码中可以直接使用read(0,…)来读取父进程发送的数据
数据流向:
父进程 ---> 写端(pipefd[1]) ---> 管道 ---> 读端(重定向到标准输入) ---> 子进程
子进程:
- 关闭写端(pipefd[1])
- 将读端重定向到标准输入
- 关闭原读端(因为已重定向)
15.进入子进程函数
// 子进程执行的函数
void slaver()
{while(true){int cmdcode = 0;// 从标准输入(被重定向到管道)读取命令int n = read(0, &cmdcode, sizeof(int)); if(n == sizeof(int)){std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " << cmdcode << std::endl;// 执行对应的任务if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode]();}if(n == 0) break; // 管道关闭时退出}
}
while(true)
,无限循环,持续监听命令
int cmdcode = 0;
int n = read(0, &cmdcode, sizeof(int));
read(0, …):从标准输入读取数据,因为前面做了重定向,实际是从管道读取
&cmdcode:存储读取数据的地址
sizeof(int):读取int大小的数据
n:返回实际读取的字节数
if(n == sizeof(int)) { // 成功读取到完整的命令// 打印调试信息std::cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " << cmdcode << std::endl;// 执行对应任务if(cmdcode >= 0 && cmdcode < tasks.size()) tasks[cmdcode](); // 调用任务函数
}
if(cmdcode >= 0 && cmdcode < tasks.size())
,确保cmdcode
非负,确保cmdcode
小于任务数组大小,防止数组越界访问
tasks[cmdcode]();
,tasks[cmdcode]
获取对应的函数指针,()
操作符调用该函数。
// 假设cmdcode = 0
tasks[0](); // 调用task1(),输出"lol 刷新日志"// 假设cmdcode = 1
tasks[1](); // 调用task2(),输出"lol 更新野区,刷新出来野怪"// 假设cmdcode = 2
tasks[2](); // 调用task3(),输出"lol 检测软件是否更新"// 假设cmdcode = 3
tasks[3](); // 调用task4(),输出"lol 更新用户血量和蓝量"
if(n == 0) break;
,管道关闭时退出
16.slaver()
结束,返回刚刚的
std::cout << "process : " << getpid() << " quit" << std::endl; //打印退出信息,getpid帮助我们确认哪个进程正在退出
exit(0); // 立即终止当前进程
17.然后执行InitProcessPool()
函数的剩下来部分
// 父进程
close(pipefd[0]); // 关闭读端// 创建新的channel并添加到channels中
std::string name = "process-" + std::to_string(i);
channels->push_back(channel(pipefd[1], id, name));
oldfds.push_back(pipefd[1]);sleep(1);
close(pipefd[0]);
,父进程只需写入命令,不需要读。及时关闭不需要的文件描述符
std::string name = "process-" + std::to_string(i);
,为每个子进程创建唯一名称。
std::to_string(i) : 将数字i转为字符串,“+” : 字符串拼接运算符。
效果如:process-0, process-1, process-2…
channels->push_back(channel(pipefd[1], id, name));
,push_back
在容器末尾添加新元素。创建临时 channel 对象并添加到 vector
channel
是一个结构体,存储子进程信息:
void InitProcessPool(std::vector<channel> *channels)struct channel {int fd; // 管道写端pid_t pid; // 子进程IDstd::string name; // 进程名称channel(int _fd, pid_t _pid, const std::string& _name): fd(_fd), pid(_pid), name(_name){}
};
oldfds.push_back(pipefd[1]);
,添加管道写端的文件描述符。
保存文件描述符的用途:
- 用于后续关闭文件描述符
- 防止文件描述符泄漏
- 进程间通信的管理
- 资源清理
sleep(1);
,休眠1s。
18.进入main函数,执行ctrlSlaver(channels);
// 控制子进程执行任务
void ctrlSlaver(const std::vector<channel> &channels)
{int which = 0;while(true){int select = 0;Menu();std::cout << "Please Enter@ ";std::cin >> select;if(select <= 0 || select >= 5) break;int cmdcode = select - 1;// 轮询方式分配任务给子进程std::cout << "father say: " << " cmdcode: " <<cmdcode << " already sendto " << channels[which]._slaverid << " process name: " << channels[which]._processname << std::endl;write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));which++;which %= channels.size();}
}
轮询机制
int which = 0; // 轮询索引
which++;
which %= channels.size(); // 循环轮询
实现了循环分配任务给不同子进程
如果有3个进程,which的值会是 0,1,2,0,1,2…
任务选择
while(true) {int select = 0;Menu(); // 显示菜单std::cout << "Please Enter@ ";std::cin >> select; // 获取用户输入if(select <= 0 || select >= 5) break; // 退出条件int cmdcode = select - 1; // 将用户输入的选项编号转换为程序内部使用的命令代码。
}
发送任务示例
// 显示任务分配信息
std::cout << "father say: " << " cmdcode: " << cmdcode << " already sendto " << channels[which]._slaverid << " process name: " << channels[which]._processname << std::endl;// 向子进程发送命令
write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));
cmdcode要执行的命令编号(0代表hello,1代表calc等)
_slaverid: 子进程的PID(进程ID)
_processname: 子进程的名称
write(channels[which]._cmdfd, &cmdcode, sizeof(cmdcode));
channels[which]._cmdfd:管道的写端文件描述符
&cmdcode:命令代码的地址
sizeof(cmdcode):发送的字节数(int类型通常是4字节)
19.返回主函数,执行QuitProcess(channels);
,清理进程池。
void QuitProcess(const std::vector<channel> &channels)
{// 遍历所有channel对象for(const auto &c : channels){// 1. 关闭管道close(c._cmdfd); // 关闭管道写端// 2. 等待子进程结束waitpid(c._slaverid, nullptr, 0); // 阻塞等待直到子进程结束}
}
20.return 0;
相关文章:
【Linux】使用管道实现一个简易版本的进程池
文章目录 使用管道实现一个简易版本的进程池流程图代码makefileTask.hppProcessPool.cc 程序流程: 使用管道实现一个简易版本的进程池 流程图 代码 makefile ProcessPool:ProcessPool.ccg -o $ $^ -g -stdc11 .PHONY:clean clean:rm -f ProcessPoolTask.hpp #pr…...
新型人工智能“黑帽”工具:GhostGPT带来的威胁与挑战
生成式人工智能的发展既带来了有益的生产力转型机会,也提供了被恶意利用的机会。 最近,Abnormal Security的研究人员发现了一个专门为网络犯罪创建的无审查AI聊天机器人——GhostGPT,是人工智能用于非法活动的新前沿,可以被用于网…...
力扣017_最小覆盖字串题解----C++
题目描述 我们可以用滑动窗口的思想解决这个问题。在滑动窗口类型的问题中都会有两个指针,一个用于「延伸」现有窗口的 r 指针,和一个用于「收缩」窗口的 l 指针。在任意时刻,只有一个指针运动,而另一个保持静止。我们在 s 上滑动…...
C++:函数
在之前的博客中已经讲过了C语言中的函数概念了,重复的内容就不赘述了。C中的函数在C语言的基础上还有些补充,在这里说明一下。 一、引用 1.引用的概念 引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变…...
linux asio网络编程理论及实现
最近在B站看了恋恋风辰大佬的asio网络编程,质量非常高。在本章中将对ASIO异步网络编程的整体及一些实现细节进行完整的梳理,用于复习与分享。大佬的博客:恋恋风辰官方博客 Preactor/Reactor模式 在网络编程中,通常根据事件处理的触…...
第一篇:数据库基础与概念
第一篇:数据库基础与概念 目标读者: 没有接触过数据库的初学者。 内容概述: 在本篇文章中,我们将从零开始,详细介绍数据库的基本概念、常见的数据库管理系统(DBMS)以及数据库设计的基础知识…...
从理论到实践:Django 业务日志配置与优化指南
在现代 Web 开发中,日志记录是确保系统可维护性和可观测性的重要手段。通过合理的日志配置,我们可以快速定位问题、分析系统性能,并进行安全审计。本文将围绕 Django 框架,详细介绍如何配置和优化业务日志,确保开发环境和生产环境都能高效地记录和管理日志。 © ivwdc…...
基于Python的药物相互作用预测模型AI构建与优化(上.文字部分)
一、引言 1.1 研究背景与意义 在临床用药过程中,药物相互作用(Drug - Drug Interaction, DDI)是一个不可忽视的重要问题。当患者同时服用两种或两种以上药物时,药物之间可能会发生相互作用,从而改变药物的疗效、增加不良反应的发生风险,甚至危及患者的生命安全。例如,…...
常用的 ASCII 码表字符
ASCII(美国信息交换标准代码,American Standard Code for Information Interchange)是一种字符编码标准,用于表示英文字符、数字、标点符号以及一些控制字符。ASCII 码表包含 128 个字符,每个字符用 7 位二进制数表示&…...
AI大模型开发原理篇-8:Transformer模型
近几年人工智能之所以能迅猛发展,主要是靠2个核心思想:注意力机制Attention Mechanism 和 Transformer模型。本次来浅谈下Transformer模型。 重要性 Transformer模型在自然语言处理领域具有极其重要的地位,为NLP带来了革命性的突破。可以…...
Golang 并发机制-2:Golang Goroutine 和竞争条件
在今天的软件开发中,我们正在使用并发的概念,它允许一次执行多个任务。在Go编程中,理解Go例程是至关重要的。本文试图详细解释什么是例程,它们有多轻,通过简单地使用“go”关键字创建它们,以及可能出现的竞…...
NVLink 拓扑、DGX 硬件渲染图
文章目录 一个 server 固定 8 个GPUP100(4个NVL)V100(6个NVL)A100(12个NVL)H100(18个NVL)【DGX-2 :2018年发布NVSwitch,实现full-mesh】【NVLink 拓扑&#x…...
Python XML 解析
Python XML 解析 引言 XML(可扩展标记语言)是一种用于存储和传输数据的标记语言。Python 作为一种功能强大的编程语言,拥有多种解析 XML 的库,如 xml.etree.ElementTree 和 lxml。本文将详细介绍 Python 中 XML 解析的方法、技巧和注意事项,帮助您更好地掌握 XML 数据的…...
java求职学习day22
MySQL 基础 &SQL 入门 1. 数据库的基本概念 1.1 什么是数据库 1. 数据库 (DataBase) 就是 存储 和 管理 数据的仓库 2. 其本质是一个文件系统, 还是以文件的方式,将数据保存在电脑上 1.2 为什么使用数据库 数据存储方式的比较 通过上面的比较 , 我们可以看出 , 使…...
stm32教程:EXTI外部中断应用
早上好啊大佬们,上一期我们讲了EXTI外部中断的原理以及基础代码的书写,这一期就来尝试一下用它来写一些有实际效能的工程吧。 这一期里,我用两个案例代码来让大家感受一下外部中断的作用和使用价值。 旋转编码器计数 整体思路讲解 这里&…...
OVS-DPDK
dpdk介绍及应用 DPDK介绍 DPDK(Data Plane Development Kit)是一组快速处理数据包的开发平台及接口。有intel主导开发,主要基于Linux系统,用于快速数据包处理的函 数库与驱动集合,可以极大提高数据处理性能和吞吐量&…...
快速分析LabVIEW主要特征进行判断
在LabVIEW中,快速分析程序特征进行判断是提升开发效率和减少调试时间的重要技巧。本文将介绍如何高效地识别和分析程序的关键特征,从而帮助开发者在编写和优化程序时做出及时的判断,避免不必要的错误。 数据流和并行性分析 LabVIEW的图形…...
MySQL数据库(二)
一 DDL (一 数据库操作 1 查询-数据库(所有/当前) 1 所有数据库: show databases; 2 查询当前数据库: select database(); 2 创建-数据库 可以定义数据库的编码方式 create database if not exists ax1; create database ax2…...
Python 梯度下降法(五):Adam Optimize
文章目录 Python 梯度下降法(五):Adam Optimize一、数学原理1.1 介绍1.2 符号说明1.3 实现流程 二、代码实现2.1 函数代码2.2 总代码2.3 遇到的问题2.4 算法优化 三、优缺点3.1 优点3.2 缺点 Python 梯度下降法(五)&am…...
表格结构标签
<!-- thead表示表格的头部 tbody表示表格的主体 --> <thead></thead> <tbody></tbody> <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content&q…...
gcc和g++的区别以及明明函数有定义为何链接找不到
初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的,可以在任何平台上使用。 源码指引:github源…...
Git进阶之旅:tag 标签 IDEA 整合 Git
第一章:tag 标签远程管理 git 标签 tag 管理: 标签有两种: 轻量级标签(lightweight)带有附注标签(annotated) git tag 标签名:创建一个标签git tag 标签名 -m 附注内容 :创建一个附注标签git tag -d 标签名…...
计算机网络一点事(24)
TCP可靠传输,流量控制 可靠传输:每字节对应一个序号 累计确认:收到ack则正确接收 返回ack推迟确认(不超过0.5s) 两种ack:专门确认(只有首部无数据) 捎带确认(带数据…...
集合的奇妙世界:Python集合的经典、避坑与实战
集合的奇妙世界:Python集合的经典、避坑与实战 内容简介 本系列文章是为 Python3 学习者精心设计的一套全面、实用的学习指南,旨在帮助读者从基础入门到项目实战,全面提升编程能力。文章结构由 5 个版块组成,内容层层递进&#x…...
ubuntu20.04.6下运行VLC-Qt例子simple-player
下载examples-master.zip(https://github.com/vlc-qt/examples),编译运行simple-player 参考链接: https://blog.csdn.net/szn1316159505/article/details/143743735 本文运行环境 Qt 5.15.2 Qt creator 5.0.2 主要步骤…...
Node.js MySQL:深度解析与最佳实践
Node.js MySQL:深度解析与最佳实践 引言 Node.js作为一种流行的JavaScript运行时环境,以其轻量级、高性能和事件驱动模型受到开发者的青睐。MySQL则是一款功能强大的关系型数据库管理系统,广泛应用于各种规模的应用程序中。本文将深入探讨Node.js与MySQL的集成,分析其优势…...
Linux网络 | 网络层IP报文解析、认识网段划分与IP地址
前言:本节内容为网络层。 主要讲解IP协议报文字段以及分离有效载荷。 另外, 本节也会带领友友认识一下IP地址的划分。 那么现在废话不多说, 开始我们的学习吧!! ps:本节正式进入网络层喽, 友友们…...
项目测试之Postman
文章目录 基础实战进行批量测试并输出报告 基础 实战 进行批量测试并输出报告 参考: https://blog.csdn.net/tyh_keephunger/article/details/109205191 概述 Newman是什么?Newman是Postman的命令行工具,用于执行接口测试集合。操作过程…...
C++——list的了解和使用
目录 引言 forward_list与list 标准库中的list 一、list的常用接口 1.list的迭代器 2.list的初始化 3.list的容量操作 4.list的访问操作 5.list的修改操作 6.list的其他操作 二、list与vector的对比 结束语 引言 本篇博客要介绍的是STL中的list。 求点赞收藏评论…...
MySQL基本架构SQL语句在数据库框架中的执行流程数据库的三范式
MySQL基本架构图: MySQL主要分为Server层和存储引擎层 Server层: 连接器:连接客户端,获取权限,管理连接 查询缓存(可选):在执行查询语句之前会先到查询缓存中查看是否执行过这条语…...
(leetcode 213 打家劫舍ii)
代码随想录: 将一个线性数组换成两个线性数组(去掉头,去掉尾) 分别求两个线性数组的最大值 最后求这两个数组的最大值 代码随想录视频 #include<iostream> #include<vector> #include<algorithm> //nums:2,…...
如何用KushoAI提升API自动化测试效率:AI驱动的革命
在现代软件开发中,API测试已经成为确保系统稳定性和可靠性的关键。然而,传统的API测试往往依赖手动编写测试用例,每次修改API后都需要重新进行测试,这不仅耗时费力,还容易因人为疏忽而出现问题。想象一下,你是否曾因API在生产环境中出现微小错误而彻夜未眠?每次修改API后…...
docker安装nacos2.2.4详解(含:nacos容器启动参数、环境变量、常见问题整理)
一、镜像下载 1、在线下载 在一台能连外网的linux上执行docker镜像拉取命令 docker pull nacos:2.2.4 2、离线包下载 两种方式: 方式一: -)在一台能连外网的linux上安装docker执行第一步的命令下载镜像 -)导出 # 导出镜像到…...
DBeaver连接MySQL提示Access denied for user ‘‘@‘ip‘ (using password: YES)的解决方法
在使用DBeaver连接MySQL数据库时,如果遇到“Access denied for user ip (using password: YES)”的错误提示,说明用户认证失败。此问题通常与数据库用户权限、配置错误或网络设置有关。本文将详细介绍解决此问题的步骤。 一、检查用户名和密码 首先&am…...
VirtualBox:跨磁盘导入已存的vdi磁盘文件顺便测试冷迁移
目录 1.背景 2.目的 3.步骤 3.1 安装在移动硬盘上 3.2.接管现有主机磁盘上的虚拟机 3.3接管迁移到移动硬盘的虚拟机 4. 结论 1.背景 电脑重新做了系统,然后找不到virtualbox的启动程序了,另外电脑磁盘由于存储了其他文件已经爆红,无法…...
蓝桥杯思维训练营(一)
文章目录 题目总览题目详解翻之一起做很甜的梦 蓝桥杯的前几题用到的算法较少,大部分考察的都是思维能力,方法比较巧妙,所以我们要积累对应的题目,多训练 题目总览 翻之 一起做很甜的梦 题目详解 翻之 思维分析:一开…...
EchoMimicV2的部署使用
最近有一个录课的需要,我不想浪费人力,只想用技术解决。需求很简单,就是用别人现成的录课视频中的形象和声线,再结合我提供的讲稿去生成一个新的录课视频。我觉得应该有现成的技术了,我想要免费大批量生产。最近看到这…...
JVM深入学习(一)
目录 一.JVM概述 1.1 为什么要学jvm? 1.2 jvm的作用 1.3 jvm内部构造 二.JVM类加载 2.1类加载过程 2.2类加载器 2.3类加载器的分类 2.4双亲委派机制 三.运行时数据区 堆空间区域划分(堆) 为什么分区(代)?(…...
线段树(Segment Tree)和树状数组
线段树(Segment Tree)和树状数组 线段树的实现链式:数组实现 解题思路树状数组 线段树是 二叉树结构 的衍生,用于高效解决区间查询和动态修改的问题,其中区间查询的时间复杂度为 O(logN),动态修改单个元素的…...
Teleporters( Educational Codeforces Round 126 (Rated for Div. 2) )
Teleporters( Educational Codeforces Round 126 (Rated for Div. 2) ) There are n 1 n1 n1 teleporters on a straight line, located in points 0 0 0, a 1 a_1 a1, a 2 a_2 a2, a 3 a_3 a3, …, a n a_n an. It’s possible to tele…...
JavaScript 注释
JavaScript 注释 引言 JavaScript 注释是编写代码过程中不可或缺的一部分。它们不仅可以提高代码的可读性和可维护性,还能帮助其他开发者(或未来的自己)更好地理解代码的意图。本文将深入探讨 JavaScript 注释的多种类型、使用方法和最佳实践。 一、注释的分类 JavaScri…...
消息队列篇--原理篇--常见消息队列总结(RabbitMQ,Kafka,ActiveMQ,RocketMQ,Pulsar)
1、RabbitMQ 特点: AMQP协议:RabbitMQ是基于AMQP(高级消息队列协议)构建的,支持多种消息传递模式,如发布/订阅、路由、RPC等。多语言支持:支持多种编程语言的客户端库,包括Java、P…...
AVL搜索树
一、介绍 高度平衡的搜索二叉树,保证每个节点的左右子树高度差不超过1,降低搜索树的高度以提高搜索效率。 通过平衡因子和旋转来保证左右子树高度差不超过1 二、插入节点 1、插入规则 (1)搜按索树规则插入节点 (…...
ELK模块封装starter
文章目录 1.combinations-elk-starter1.目录结构2.log4j2-spring.xml 从环境变量读取host和port3.ELKProperties.java 两个属性4.ELKAutoConfiguration.java 启用配置类5.ELKEnvironmentPreparedListener.java 监听器从application.yml中获取属性值6.spring.factories 注册监听…...
C# 与.NET 日志变革:JSON 让程序“开口说清话”
一、引言:日志新时代的开启 在软件开发的漫长旅程中,日志一直是我们不可或缺的伙伴。它就像是应用程序的 “黑匣子”,默默地记录着程序运行过程中的点点滴滴,为我们在调试、排查问题以及性能优化时提供关键线索。在早期ÿ…...
Ubuntu 系统,如何使用双Titan V跑AI
要在Ubuntu系统中使用双NVIDIA Titan V GPU来运行人工智能任务,你需要确保几个关键组件正确安装和配置。以下是基本步骤: 安装Ubuntu操作系统: 下载最新版本的Ubuntu服务器或桌面版ISO文件。使用工具如Rufus(Windows)或…...
CSDN的历史
CSDN(中国开发者网络,China Software Developer Network)是中国最具影响力的IT技术社区之一,其历史可追溯至1999年。以下是其发展历程和关键节点: --- **一、创立背景(1999年)** - **创始人**:蒋涛(国内知名技术人,曾参与金山软件早期开发)。 - **初衷**:为国内程…...
使用Pygame制作“贪吃蛇”游戏
贪吃蛇 是一款经典的休闲小游戏:玩家通过操控一条会不断变长的“蛇”在屏幕中移动,去吃随机出现的食物,同时要避免撞到墙壁或自己身体的其他部分。由于其逻辑相对简单,但可玩性和扩展性都不错,非常适合作为新手练习游戏…...
【详细教程】如何在Mac部署Deepseek R1?
DeepSeek是目前最火的国产大模型,官方App用户太多服务经常出现卡顿,部署一个本地DeepSeek R1可以方便使用。 1.系统最低要求 macOS 11 Big Sur 或更新 2.下载ollama https://ollama.com/ 3.安装DeepSeek R1 打开终端 运行命令 ollama run deepseek-…...
Java中的getInterfaces()方法:使用与原理详解
在Java中,反射(Reflection)是一个强大的工具,它允许程序在运行时动态地获取类的信息并操作类的属性和方法。getInterfaces()方法是Java反射API中的一个重要方法,用于获取类或接口直接实现的接口。本文将深入探讨getInt…...