【linux】进程间通信(IPC)——匿名管道,命名管道与System V内核方案的共享内存,以及消息队列和信号量的原理概述
目录
✈必备知识
进程间通信概述
🔥概述
🔥必要性
🔥原理
管道概述
🔥管道的本质
🔥管道的相关特性
🔥管道的同步与互斥机制
匿名管道
🔥系统调用接口介绍
🔥内核原理
🔥匿名管道小实战——进程池
命名管道
🔥概述
🔥创建命名管道的指令
🔥创建命名管道函数
system V共享内存
🔥概述
🔥系统调用接口
🔥代码示例
消息队列
🔥原理概述
🔥接口介绍
创建消息队列
向消息队列发送消息
从消息队列接收消息
控制消息队列
信号量
🔥概述
🔥接口介绍
OS管理共享内存,消息队列,信号量
🤗个人主页:东洛的克莱斯韦克
✈必备知识
进程地址空间
进程控制——进程创建,进程退出,进程等待
理解文件系统
进程间通信概述
🔥概述
进程间通信简称IPC,是在不同进程之间传播或交换信息的一种机制。
管道是基于内存级文件的通信方案
在System V解决方案中有共享内存,消息队列,信号量。在POSIX IPC解决方案中有共享内存,消息队列,信号量,互斥量,条件变量,读写锁
这些通信方案在系统中都属于IPC资源。
🔥必要性
每个进程都有自己的地址空间,这保证了进程的独立性。即使两个进程是父子关系,可以看到同一份数据,但只要有一方进行写入操作都会发生写时拷贝。
进程A异常了,不会影响到进程B,进程C......本质上来讲进程的独立性是因为进程间的数据具有独立性。
进程具有独立性是在内核设计时就有的理念。但后来发现,进程有时相互传输数据是必要的,所以必须提出解决方案——不破坏进程的独立性又能实现进程的通信。
🔥原理
先让两个进程看到同一份公共资源,这公共资源可以是文件,也可以是一块内存。
进程A向公共资源写入数据,进程B向公共资源读数据。此时两个进程完成了数据的交互,这样进程A和进程B就实现了通信。
管道概述
🔥管道的本质
管道也是文件的一种,属于p类型的文件。管道文件属于公共资源,如果多个进程打开同一个管道文件,就能相互传输数据。
管道文件是内存级文件,它在磁盘中并没有对应的数据块来为它保存文件内容。系统只会在内存中为管道文件开辟文件缓冲区来保存数据。这样做避免了内存与外设的IO交互,提高进程间通信效率。
管道文件在磁盘中有个简单的映像,这个映像的大小为 0 ,这么做是为了让文件系统知道有这么一个文件存在。
🔥管道的相关特性
管道一般是单向通信——一方是读端,一方是写端 |
管道具有同步与互斥机制——保护数据 |
管道的生命周期随进程——被打开的文件的声明周期随进程 |
管道是面向字节流的 |
🔥管道的同步与互斥机制
同步与互斥机制是为了保护管道数据的安全
读写端都正常,管道为空,读端阻塞 |
读写端都正常,管道为满,写端阻塞 |
读端正常,写端关闭,读端会读到 0 ,相当于读到文件结尾,进程正常执行后续代码 |
读端关闭,写端正常,写端的进程会被信号杀掉,进程异常终止 |
匿名管道
匿名管道本质上是没有名字的内存级的文件——适用于具有血缘关系的进程
🔥系统调用接口介绍
🔥内核原理
在使用pipe系统调用之后,系统会为该进程分配两个文件描述符——files_struct数组下标。会有两个file结构体指向同一个文件缓冲区。分别以只读和只写打开文件缓冲区。
使用fork系统调用之后,子进程会继承父进程的内核数据,也包括了对应的文件描述符。此时,父子进程已经能看到同一份公共资源了——有了通信的基础。
父进程和子进程分别关掉一端文件描述符,就能实现通信啦~
🔥匿名管道小实战——进程池
与进程池概念类似的还有内存池。这些都属于池化技术——申请资源时申请走一大块资源,用户层把这些资源维护起来,在需要申请资源的时候不用再向系统中申请,提高程序的效率。
我们要设计的进程池基本原理如下
父进程创建一批子进程,父子进程用匿名管道通信,父进程只要不向管道里写数据,子进程就会一直阻塞(因为父进程是写端,子进程是读端)。
父进程想让子进程执行任务时不需要再向OS申请进程,直接选一个在阻塞状态的子进程即可。
🔥代码示例
#include <stdio.h>
#include <stdlib.h>
#include <vector>
#include <unistd.h>
#include <ctime>
#include <sys/types.h>
#include <sys/wait.h>
#include <iostream>
using namespace std;
#define PID_QUANTITY 10 // 子进程数量
#define NUMBER_OF_BYTES 4// 子进程一次读取的字节数
#define SEND 10//父进程发送消息的数量class pipeline // 描述进程池属性
{
public:pipeline(int fd, pid_t pid) // 构造函数: _fd(fd), _pid(pid){}int _fd; // 文件描述符pid_t _pid; // 进程pid
};void reception() // 子进程接收任务
{int tmp = 0;while (true){int i = read(0, &tmp, NUMBER_OF_BYTES); // 从信道里读取if (NUMBER_OF_BYTES == i){printf("我是子进程%d,我读到的数据是:%d\n", getpid(), tmp);}if (0 == i)break;}
}void Cistern(std::vector<pipeline> *vp) // 创建进程池,输出型参数vector<pipeline> * vp
{std::vector<int> oldfds;for (int i = 0; i < PID_QUANTITY; i++){int fds[2]; // 0下标表示读端,1下表表示写端 pipe(fds); // 建立信道pid_t tmp_pid = fork(); // 创建子进程if (0 == tmp_pid) // 子进程{for (auto fd : oldfds) // 关闭子进程多余的写端{close(fd);}close(fds[1]); // 关闭子进程写端dup2(fds[0], 0); // 本来从键盘读取变为从信道读取,减少传参close(fds[0]); // 关闭多余的读端reception(); // 接收任务exit(0); // 子进程结束}close(fds[0]); // 关闭父进程读端vp->push_back(pipeline(fds[1], tmp_pid)); // 向父进程管理的进程池中添加字段,父进程信道的文件描述符,以及子进程的pidoldfds.push_back(fds[1]); // 记录子进程的写端,方便下一个进程继承后关闭~}
}void Reclaim(std::vector<pipeline> &vp) // 回收子进程
{for (auto c : vp)
{close(c._fd);waitpid(c._pid, nullptr, 0);
}}void Send(std::vector<pipeline> &vp) //向子进程发消息
{for (int k = 0; k < SEND; k++) {int i = rand() % 10;int j = rand() % 10;write(vp[i]._fd, &j, sizeof(int));printf("我是父进程,我向子进程传递的数据是%d\n", j);sleep(1); }
}int main()
{srand(time(nullptr) ^ getpid() ^ 1023); // 种一个随机数种子std::vector<pipeline> pv; // 创建容器管理进程池Cistern(&pv); // 初始化Send(pv); // 父进程派发任务Reclaim(pv); // 关闭信道和等待子进程return 0;
}
命名管道
🔥概述
命名管道的本质是具有名字的内存级文件。
使用命名管道通信无需在意进程间是否具有血缘关系。只要不同的进程能打开同一个命名管道文件——就有了通信的基础。
对命名管道文件的操作只需像普通文件操作一样调用系统的文件操作接口即可,如open close read write lseek
🔥创建命名管道的指令
mkfifo 文件名
🔥创建命名管道函数
头文件
#include <sys/stat.h>
#include <fcntl.h>
函数原型
int mkfifo(const char *filename,mode_t mode);
参数
filename指向以 null 结尾的字符串的指针,该字符串指定了命名管道的名称(包括路径)。如果指定的文件已存在且不是一个命名管道,mkfifo
会失败。
mode指定了命名管道的权限,与 open
或 creat
函数中的 mode
参数类似。这些权限会受到进程的文件模式创建掩码(umask)的影响。
返回值
成功时,mkfifo
返回 0,
出错时,返回 -1。
system V共享内存
🔥概述
进程间通信的基础是让不同进程看到同一份资源,为了达到这种目的,共享内存是一种更直接的方案。
OS会在物理内存中开辟一块空间,这块空间就是公共资源。只要把这块公共资源挂接到相应进程的地址空间上就实现了通信的基础。
只要把内存挂接到进程的地址空间的内存映射段上,该进程就有对该空间的读写权限。每一个进程都会认为该空间是自己的,那么进程之间在传输数据时不会考虑彼此的感受。换句话说,共享内存没有同步与互斥的保护机制。
进程地址空间示意图
但共享内存的通信方案时最快的。只要进程A把数据刷到共享内存中,其他进程就能立马拿到数据。
🔥系统调用接口
key_t ftok(const char *pathname, int proj_id);
int shmget(key_t key, size_t size, int shmflg);
功能:创建一块共享内存
参数:第一个参数是key值,是内核标识IPC资源的唯一标识符。
第二个参数size则指定共享内存的大小,建议传入4096(4KB)的整数倍,如果传入了4097,那么系统会申请4096 * 2 的大小,因为系统申请内存是以4KB为单位大小申请的。但用户层只能用4097大小的内存。
第三个参数传入权限码和选项。0666 | IPC_CREAT|IPC_EXCL,这样传表示创建新的、唯一的共享内存段,并且权限为0666。值传入IPC_CREAT表示要获取返回值。
返回值:该函数的返回值表示用户层的key值。也就是说用户层不使用ftok返回的key值,而是使用shmget返回的值。
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:把共享内存挂接到地址空间
参数:第一个参数是shmget的返回值。第二个表示要挂接到进程地址空间的什么位置,设为nullptr即可,表示让系统指定。第三个传 0即可。
int shmdt(const void *shmaddr);
功能:地址空间和共享内存去关联
参数:指向共享内存第一个节,也就是shmat的返回值
🔥代码示例
IPC.hpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <sys/types.h> key_t Ftok() //获取key值
{key_t key = ftok("/home/ccc/d2", 's'); return key;
}int Shmget(int i) //创建
{int shm_id;size_t shm_size = 4096; // 共享内存段的大小,例如1024字节key_t key = Ftok();shm_id = shmget(key, shm_size, i); return shm_id;
}int createshm() //创建
{return Shmget(0666 | IPC_CREAT|IPC_EXCL);//权限 | 创建 有的话就获取 | 创建新的、唯一的共享内存段
}int getshm() //获取
{return Shmget(IPC_CREAT);
}
Client.cc
// 客户端
#include "IPC.hpp"int main()
{int shm_id = getshm();char *ptr = (char *)shmat(shm_id, nullptr, 0); // 挂接printf("%s", ptr);sleep(15);shmdt(ptr);return 0;
}
Server.cc
// 服务端
#include "IPC.hpp"int main()
{int shm_id = createshm(); char* ptr = (char*)shmat(shm_id, nullptr, 0); strcpy(ptr, "你是个小可爱");printf("%s", ptr);sleep(15);shmdt(ptr);return 0;
}
消息队列
🔥原理概述
这种通信方案的公共资源是由内核维护的一种数据结构——队列。
不同进程间需要先看到同一个消息队列,这样就有了通信的基础。进程向消息队列发送的是一个个带类型的数据块,带类型是为了区分该数据块是由那个进程发送的,其他进程只需要取走不属于自己的数据块就可以实现通信了。
🔥接口介绍
创建消息队列
int msgget(key_t key, int msgflg);
参数
key
:消息队列的标识符,通常通过ftok
函数生成。msgflg
:创建标志,常用的有IPC_CREAT
(如果不存在则创建)和IPC_EXCL
(与IPC_CREAT
一起使用时,如果队列已存在则创建失败)。还可以按位或上权限值(如0666
)来设置队列的权限。
返回值
- 成功时返回消息队列的ID。
- 失败时返回-1,并设置
errno
以指示错误原因。
向消息队列发送消息
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数
msqid
:消息队列的ID。msgp
:指向要发送的消息的指针,通常是一个结构体,其第一个成员为消息类型(long
类型),后续为消息数据。msgsz
:消息的大小(不包括消息类型),即msgp
指向的结构体中消息数据的长度。msgflg
:控制标志,常用的有0
(阻塞发送)和IPC_NOWAIT
(非阻塞发送)。
返回值
- 成功时返回0。
- 失败时返回-1,并设置
errno
以指示错误原因。
从消息队列接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
参数
msqid
:消息队列的ID。msgp
:指向接收消息的缓冲区的指针,该缓冲区应与发送消息时使用的结构体类型相同。msgsz
:缓冲区的最大长度,即能接收的最大消息大小(不包括消息类型)。msgtyp
:消息类型,用于指定接收哪种类型的消息。可以指定为0、正数或负数,具体含义见参考文章。msgflg
:控制标志,常用的有0
(阻塞接收)和IPC_NOWAIT
(非阻塞接收)。
返回值
- 成功时返回实际接收到的消息大小(不包括消息类型)。
- 失败时返回-1,并设置
errno
以指示错误原因。
控制消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数
msqid
:消息队列的ID。cmd
:控制命令,常用的有IPC_STAT
(获取消息队列的状态)、IPC_SET
(设置消息队列的属性)和IPC_RMID
(删除消息队列)。buf
:指向msqid_ds
结构体的指针,用于存储或设置消息队列的状态和属性。
返回值
- 成功时返回0。
- 失败时返回-1,并设置
errno
以指示错误原因。
信号量
🔥概述
信号量本质是一把计数器
互斥:任何时刻只允许一个执行流访问资源
临界资源:共享的,只能被一个执行流访问的资源
临界区:访问临界资源的代码
访问公共资源时不能直接访问,而是要让计数器减减。也就是说,要想申请访问公共资源必须访问计数器资源,如果计数器资源为 0 ,就不能访问公共资源。
如果计数器资源为 1 ——二元信号量,那么只能有一个执行流再访问公共资源,这就时互斥,也是锁。
计数器资源也会被多个执行流访问,那么计数器资源也是公共资源。如果如果信号量要保证公共资源的安全性,就必须保证自己的计数操作是原子性的。
原子性:本质就是两态性,即一件事要么没有做,要么做完了,没有正在做的状态。
对计数器减减称为 P 操作,对计数器加加称为 V操作。PV操作是原子性的
🔥接口介绍
创建或获取信号量集
int semget(key_t key, int nsems, int sem_flags);
参数
key
:用于标识信号量集的键值,通常使用ftok函数生成。nsems
:信号量集中包含的信号量数量,通常为1。sem_flags
:用于指定信号量的权限和标志,常用的标志包括IPC_CREAT(如果不存在则创建)、IPC_EXCL(与IPC_CREAT一起使用,确保创建的信号量集是唯一的)。
返回值
成功时返回信号量集的标识符(semid),失败时返回-1。
控制信号量集
int semctl(int semid, int semnum, int cmd, ...);
参数说明:
semid
:信号量集的标识符。semnum
:信号量的索引,通常为0(如果信号量集中只有一个信号量)。cmd
:控制命令,用于指定要进行的操作,如SETVAL(设置信号量的值)、IPC_RMID(删除信号量集)等。...
:根据cmd
的不同,可能需要额外的参数,如使用SETVAL时需要指定信号量的新值。
返回值:根据不同的命令,返回不同的值。
操作信号量
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数
semid
:信号量集的标识符。sops
:指向sembuf结构体数组的指针,每个sembuf结构体描述了一个对信号量的操作。nsops
:sops数组中结构体的数量,即要执行的操作数。
sembuf结构体
struct sembuf { unsigned short sem_num; // 信号量在信号量集中的索引 short sem_op; // 操作值,正数表示V操作(释放信号量),负数表示P操作(等待信号量) short sem_flg; // 操作标志位,如SEM_UNDO(使操作系统跟踪信号量,并在进程终止时自动释放)
};
返回值
成功时返回0,失败时返回-1。
OS管理共享内存,消息队列,信号量
操作系统为了管理这些IPC资源,必须先用结构体描述这些IPC资源。如下是内核显示给用户层的部分内核数据字段
🔥共享内存
struct shmid_ds
{
ipc_perm
shm_perm:这是一个ipc_perm结构体,包含了共享内存段的权限和所有权信息,如UID(用户ID)、GID(组ID)、权限模式(读写执行)等。
shm_segsz:共享内存段的大小(以字节为单位)。
shm_atime:最后一次附加(attach)到共享内存段的时间。
shm_dtime:最后一次从共享内存段分离(detach)的时间。
shm_ctime:共享内存段最后一次改变的时间(如权限或所有权改变)。
shm_cpid:创建共享内存段的进程的PID(进程ID)。
shm_lpid:最后一次执行shmat(附加)或shmdt(分离)操作的进程的PID。
shm_nattch:当前附加到共享内存段的进程数。}
🔥消息队列
struct msqid_ds
{
ipc_perm
msg_perm:这是一个ipc_perm结构体,包含了消息队列的权限和所有权信息。
key:用于msgget系统调用的键,用于获取消息队列的标识符。
msg_stime:最后一次msgsnd(发送消息)操作的时间。
msg_rtime:最后一次msgrcv(接收消息)操作的时间。
uid、gid:消息队列拥有者的有效UID和GID。
cuid、cgid:创建消息队列的进程的有效UID和GID。
msg_ctime:消息队列最后一次改变的时间(如权限或所有权改变)。
msg_qbytes:消息队列的最大字节数。
msg_qnum:消息队列中当前的消息数。
msg_lspid、msg_lrpid(未在文本中直接列出,但通常存在):最后发送消息和最后接收消息的进程的PID。}
🔥信号量
struct semid_ds
{
ipc_perm
sem_perm:这是一个ipc_perm结构体,包含了信号量集的权限和所有权信息。
sem_otime:最后一次semop(信号量操作)操作的时间。
sem_ctime:信号量集最后一次改变的时间(如权限或所有权改变)。
sem_nsems:信号量集中的信号量数量。
}
🔥ipc_perm
struct
ipc_perm{
key:IPC 对象的键(key)。这是一个用于唯一标识 IPC 对象的值,尽管它并不直接用于权限控制,但在创建 IPC 对象时作为查找或创建对象的依据。
uid:用户标识符(User ID)。这表示 IPC 对象的拥有者的用户 ID。只有拥有者(或其具有适当权限的进程)才能修改 IPC 对象的某些属性。
gid:组标识符(Group ID)。这表示 IPC 对象所属的组。组内的成员可能具有访问 IPC 对象的特定权限。
cuid:创建者用户标识符(Creator's User ID)。这表示创建 IPC 对象的进程的用户 ID。这个字段通常用于记录谁创建了 IPC 对象,但它本身并不直接用于权限控制。
cgid:创建者组标识符(Creator's Group ID)。这表示创建 IPC 对象的进程的组 ID。与 cuid 类似,这个字段用于记录谁创建了 IPC 对象,但本身不直接用于权限检查。
mode:权限模式。这个字段是一个位掩码,指定了 IPC 对象的访问权限。这些权限通常包括读(r)、写(w)和执行(x)权限,但对于 IPC 对象来说,“执行”权限并不适用。相反,这些权限位用于控制对 IPC 对象的访问(如读取消息、写入消息、修改信号量值等)。权限位可以分别设置给拥有者(user,即 uid)、组(group,即 gid)和其他(others)用户。
seq:序列号(Sequence Number)。这是一个内部使用的字段,用于内核中跟踪 IPC 对象的版本或变化。它对于用户空间程序来说通常是不透明的。}
共享内存,消息队列,信号量他们之间的通信方式肯定有差异,但也有相同的属性,这些相同的属性被放在ipc_perm结构体中,ipc_perm就是基类,shmid_ds,msqid_ds,semid_ds就是
派生类。
内核中用一个数组储存ipc_perm类型指针,ipc_perm类型指针可能指向不同的IPC资源,这就是面向对象中多态的概念。
内核反馈给用户层的就是该数组的下标。该数组下标是分配是线性递增的,即使删除了IPC资源,反馈给用户层的数组也是线性递增的。当增到某个阈值值就会回绕,也就是再从 0 开始。
相关文章:

【linux】进程间通信(IPC)——匿名管道,命名管道与System V内核方案的共享内存,以及消息队列和信号量的原理概述
目录 ✈必备知识 进程间通信概述 🔥概述 🔥必要性 🔥原理 管道概述 🔥管道的本质 🔥管道的相关特性 🔥管道的同步与互斥机制 匿名管道 🔥系统调用接口介绍 🔥内核原理 …...

【深度学习】卷积网络代码实战ResNet
ResNet (Residual Network) 是由微软研究院的何凯明等人在2015年提出的一种深度卷积神经网络结构。ResNet的设计目标是解决深层网络训练中的梯度消失和梯度爆炸问题,进一步提高网络的表现。下面是一个ResNet模型实现,使用PyTorch框架来展示如何实现基本的…...
org.apache.zookeeper.server.quorum.QuorumPeerMain
QuorumPeerMain源代码 package org.apache.zookeeper.server.quorum;import java.io.IOException; import javax.management.JMException; import javax.security.sasl.SaslException; import org.apache.yetus.audience.InterfaceAudience; import org.apache.zookeeper.audi…...

oscp学习之路,Kioptix Level2靶场通关教程
oscp学习之路,Kioptix Level2靶场通关教程 靶场下载:Kioptrix Level 2.zip 链接: https://pan.baidu.com/s/1gxVRhrzLW1oI_MhcfWPn0w?pwd1111 提取码: 1111 搭建好靶场之后输入ip a看一下攻击机的IP。 确定好本机IP后,使用nmap扫描网段&…...
SkyWalking java-agent 是如何工作的,自己实现一个监控sql执行耗时的agent
Apache SkyWalking 是一个开源的应用性能监控 (APM) 工具,支持分布式系统的追踪、监控和诊断。SkyWalking Agent 是其中的一个重要组件,用于在服务端应用中收集性能数据和追踪信息,并将其发送到 SkyWalking 后端服务器进行处理和展示。 SkyW…...

每天40分玩转Django:Django表单集
Django表单集 一、知识要点概览表 类别知识点掌握程度要求基础概念FormSet、ModelFormSet深入理解内联表单集InlineFormSet、BaseInlineFormSet熟练应用表单集验证clean方法、验证规则熟练应用自定义配置extra、max_num、can_delete理解应用动态管理JavaScript动态添加/删除表…...

查看vue的所有版本号和已安装的版本
1.使用npm查看Vue的所有版本: npm view vue versions2.查看项目中已安装的 Vue.js 版本 npm list vue...
钉钉h5微应用,鉴权提示dd.config错误说明,提示“jsapi ticket读取失败
这个提示大多是因为钉钉服务器没有成功读取到该企业的jsticket数据 1. 可能是你的企业corpid不对 登录钉钉管理后台 就可以找到对应企业的corpid 请严格使用这个corpid 。调用获取jsapi_ticket接口,使用的access_token对应的corpid和dd.config中传递的corpid不一致…...

【openGauss】正则表达式次数符号“{}“在ORACLE和openGauss中的差异
一、前言 正则作为一种常用的字符串处理方式,在各种开发语言,甚至数据库中,都有自带的正则函数。但是正则函数有很多标准,不同标准对正则表达式的解析方式不一样,本次在迁移一个ORACLE数据库到openGauss时发现了一个关…...
宏任务和微任务的区别
在 JavaScript 的异步编程模型中,宏任务(Macro Task)和微任务(Micro Task)是事件循环(Event Loop)机制中的两个重要概念。它们用于管理异步操作的执行顺序。 1. 宏任务 (Macro Task) 宏任务是较…...

数据库系统原理复习汇总
数据库系统原理复习汇总 一、数据库系统原理重点内容提纲 题型:主观题 1、简答题 第一章:数据库的基本概念:数据库、数据库管理系统、三级模式;两级映像、外码 第二章:什么是自然连接、等值连接; 第三…...

Linux day1204
五.安装lrzsz lrzsz 是用于在 Linux 系统中文件上传下载的软件。大家可能会存在疑问,我们用 MobaXterm 图形化界面就可以很方便的完成上传下载,为什么还要使用这个软件来 完成上传下载呢?实际上是这样的, Linux 的远程连接工具…...

如何在 Ubuntu 22.04 上安装并开始使用 RabbitMQ
简介 消息代理是中间应用程序,在不同服务之间提供可靠和稳定的通信方面发挥着关键作用。它们可以将传入的请求存储在队列中,并逐个提供给接收服务。通过以这种方式解耦服务,你可以使其更具可扩展性和性能。 RabbitMQ 是一种流行的开源消息代…...

【OpenGL ES】GLSL基础语法
1 前言 本文将介绍 GLSL 中数据类型、数组、结构体、宏、运算符、向量运算、矩阵运算、函数、流程控制、精度限定符、变量限定符(in、out、inout)、函数参数限定符等内容,另外提供了一个 include 工具,方便多文件管理 glsl 代码&a…...

如何使用交叉编译器调试C语言程序在安卓设备中运行
一、前言 随着移动设备的普及与技术的飞速发展,越来越多的开发者面临着在Android设备上运行和调试C语言等程序的需求。然而,在软件开发的世界里,不同硬件架构对程序运行的要求千差万别,这无疑增加了开发的复杂性。特别是在移动计…...
Java全栈项目 - 智能考勤管理系统
项目介绍 智能考勤管理系统是一个基于 Java 全栈技术开发的现代化企业考勤解决方案。该系统采用前后端分离架构,实现了员工考勤、请假管理、统计分析等核心功能,旨在帮助企业提高人力资源管理效率。 技术栈 后端技术 Spring Boot 2.6.xSpring Securi…...
Linux Shell : Process Substitution
注:本文为 “Process Substitution” 相关文章合辑。 英文引文机翻,未校。 Process Substitution. 进程替换允许使用文件名引用进程的输入或输出。它采取以下形式 <(list)or >(list)进程 list 异步运行,其输入或输出显示为文件名。…...
JOGL 从入门到精通:开启 Java 3D 图形编程之旅
一、引言 Java 作为一门广泛应用的编程语言,在图形编程领域也有着强大的工具和库。JOGL(Java OpenGL)便是其中之一,它为 Java 开发者提供了访问 OpenGL(Open Graphics Library)功能的接口,使得…...
汽车网络安全基线安全研究报告
一、引言 随着汽车行业朝着智能网联方向飞速发展,汽车网络安全已成为保障用户安全和行业健康发展的关键要素。本报告将深入探讨汽车网络安全相关内容,以及国际、国内重要的汽车网络安全标准基线和相应防护措施等内容。 二、汽车网络安全的重要性 &…...

Eclipse 修改项目栏字体大小
1、菜单栏选择window->preference,然后选择General->Appearance->Colors and Fonts,在搜索栏输入"tree",点击"Edit"修改字体。 2、修改字体,选择"四号字体",点击"确定&qu…...

2.Vue编写一个app
1.src中重要的组成 1.1main.ts // 引入createApp用于创建应用 import { createApp } from "vue"; // 引用App根组件 import App from ./App.vue;createApp(App).mount(#app)1.2 App.vue 其中要写三种标签 <template> <!--html--> </template>…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...
【无标题】路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论
路径问题的革命性重构:基于二维拓扑收缩色动力学模型的零点隧穿理论 一、传统路径模型的根本缺陷 在经典正方形路径问题中(图1): mermaid graph LR A((A)) --- B((B)) B --- C((C)) C --- D((D)) D --- A A -.- C[无直接路径] B -…...
Go 并发编程基础:通道(Channel)的使用
在 Go 中,Channel 是 Goroutine 之间通信的核心机制。它提供了一个线程安全的通信方式,用于在多个 Goroutine 之间传递数据,从而实现高效的并发编程。 本章将介绍 Channel 的基本概念、用法、缓冲、关闭机制以及 select 的使用。 一、Channel…...

Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...

如何更改默认 Crontab 编辑器 ?
在 Linux 领域中,crontab 是您可能经常遇到的一个术语。这个实用程序在类 unix 操作系统上可用,用于调度在预定义时间和间隔自动执行的任务。这对管理员和高级用户非常有益,允许他们自动执行各种系统任务。 编辑 Crontab 文件通常使用文本编…...

Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...

nnUNet V2修改网络——暴力替换网络为UNet++
更换前,要用nnUNet V2跑通所用数据集,证明nnUNet V2、数据集、运行环境等没有问题 阅读nnU-Net V2 的 U-Net结构,初步了解要修改的网络,知己知彼,修改起来才能游刃有余。 U-Net存在两个局限,一是网络的最佳深度因应用场景而异,这取决于任务的难度和可用于训练的标注数…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...

2.3 物理层设备
在这个视频中,我们要学习工作在物理层的两种网络设备,分别是中继器和集线器。首先来看中继器。在计算机网络中两个节点之间,需要通过物理传输媒体或者说物理传输介质进行连接。像同轴电缆、双绞线就是典型的传输介质,假设A节点要给…...