Linux——进程间通信

目录

一、进程通信的初步认识

1.1 进程间通信目的

1.2 进程间通信的种类

管道(Pipes)

System V IPC

POSIX IPC

三、管道

3.1 知识铺垫

3.2 匿名管道

3.2.1 基本概念

3.2.2 测试用例:

3.3 管道的行为

3.4 命名管道

3.4.1 基本概念

3.4.2 代码演示

四、共享内存 Shm(Shared memory)

4.1 基本概念

4.2 相关函数

4.2.1 shmget

功能

函数原型

参数

返回值

使用场景

 4.2.2 shmat

功能

函数原型

参数

返回值

使用场景

4.2.3 shmdt

功能

函数原型

参数

返回值

使用场景

4.2.4 shmctl

功能

函数原型

参数

返回值

使用场景

4.3 代码演示

4.3.1 shm.hpp

4.3.2 server.cc(服务端)

4.3.3 client.cc(客户端)


一、进程通信的初步认识

1.1 进程间通信目的

数据传输:一个进程需要将它的数据发送给另一个进程。
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

1.2 进程间通信的种类

Linux进程间通信(Inter-Process Communication, IPC)是操作系统中的一个核心概念,它允许运行在同一台机器上的不同进程之间进行数据交换。从历史的发展角度来看,Linux支持多种IPC机制,包括管道(Pipes)、System V IPC机制和POSIX IPC机制。这些机制各有特点,适用于不同的场景。

管道(Pipes)

管道是最早的Unix IPC机制之一,提供了一个单向通信的简单接口。管道可以是匿名的,也可以是命名的(也称为FIFO)。它们允许将一个进程的输出直接连接到另一个进程的输入。

  • 匿名管道:仅限于有父子关系的进程间通信。
  • 命名管道(FIFO):允许不相关的进程通信,因为它们通过文件系统中的名字进行识别。

管道是简单有效的数据流通信方式,但它们的功能相对有限,比如只支持单向通信,且数据流是无结构的字节流

System V IPC

System V(System 5)IPC引入了更为复杂和灵活的通信机制,包括消息队列、信号量和共享内存。这些机制不仅支持不相关进程间的通信,还提供了更多的控制机制来同步进程和管理对共享资源的访问。

  • 消息队列:允许进程将消息发送到一个队列中,其他进程可以从这个队列中读取消息,支持复杂的通信模式。
  • 信号量:主要用于进程间的同步,控制多个进程对共享资源的访问。
  • 共享内存:是一种高效的IPC方式,允许多个进程共享一个内存区域,适用于大量数据的交换。

System V IPC机制提供了较强的功能,但使用相对复杂,需要处理更多的资源管理工作。

POSIX IPC

为了解决System V IPC的一些不足,并提供一种更标准化的IPC机制,POSIX(Portable Operating System Interface)引入了自己的IPC方式,包括消息队列、信号量和共享内存。

  • POSIX 消息队列:比System V消息队列提供了更好的性能和更强的特性,例如消息优先级。
  • POSIX 信号量:提供了更灵活的同步机制,包括局部和命名信号量。
  • POSIX 共享内存:提供了一种映射文件或匿名内存到进程地址空间的方式,使得进程间可以通过读写同一块内存来交换数据。

POSIX IPC机制提供了与System V 类似的功能,但具有更好的跨平台支持,并且在API的设计上更为一致和易用。

三、管道

3.1 知识铺垫

3.2 匿名管道

3.2.1 基本概念

匿名管道:仅限于有父子关系的进程间通信。

#include <unistd.h>
功能 : 创建一无名管道
原型 : int pipe(int fd[2]);

当你调用 pipe(fd) 时,它会创建一个管道,并产生两个文件描述符:

  • fd[0] 用于读取管道。
  • fd[1] 用于写入管道。

数据写入 fd[1] 的一端可以从 fd[0] 这端读取出来,实现了单向通信。需要注意的是,管道中的数据是按照先入先出(FIFO)的顺序处理的。

3.2.2 测试用例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() 
{int pipefd[2];pid_t cpid;char buf;if (pipe(pipefd) == -1) {  // 创建管道perror("pipe");exit(EXIT_FAILURE);}cpid = fork();  // 创建子进程if (cpid == -1) {perror("fork");exit(EXIT_FAILURE);}if (cpid == 0)   /* 子进程 */{  close(pipefd[1]);  // 关闭写端while (read(pipefd[0], &buf, 1) > 0) // 从管道读取数据{  write(STDOUT_FILENO, &buf, 1);}write(STDOUT_FILENO, "\n", 1);close(pipefd[0]);  // 关闭读端_exit(EXIT_SUCCESS);} else /* 父进程 */{            close(pipefd[0]);  // 关闭读端write(pipefd[1], "Hello, Child!", 13);  // 向管道写入数据close(pipefd[1]);  // 关闭写端,表示完成wait(NULL);        // 等待子进程退出exit(EXIT_SUCCESS);}return 0;
}

 另外,我们在命令行中使用的 | 就是匿名管道

3.3 管道的行为


我们可以一个父进程创建很多个子进程,这就形成了进程池:

观察进程池我们可以比较清楚的看到管道的实际应用。

3.4 命名管道

前面说了,匿名管道只用于存在血缘关系的进程之间的通信,那如果是不相干的两个进程应当如何通信呢?这就需要使用到命名管道。
这里做一个假设,需要两个进程对同一个文件进行操作,当工程量足够庞大时,我们如何能确定两个进程使用的一定是同一个文件?答案是肯定能确定的,文件的路径是唯一的!
知道了这点,基本的困惑应该也就消除了,下面来看看命名管道的原理:

3.4.1 基本概念

其实看图就可以看出来和匿名管道很像,只需要让两个进程对同一个文件进行操作,把文件的路径写对,然后把重点集中在红字部分就会发现,命名管道就是一个特殊文件,它可以不让缓冲区的数据立马刷到磁盘。

3.4.2 代码演示

下面直接根据代码来看命名管道:

宏定义:

1.const std::string comm_path = "./5-9Linux内存共享";
这是一个全局常量声明,不是传统意义上的宏。它定义了一个字符串常量 comm_path ,用于指定默认的命名管道文件路径。
2.#define DefaultFd -1
定义了一个宏 DefaultFd 其值为-1。-1通常用来表示无效的文件描述符(File Descriptor)。
3.#define Creater 1
   #define User 2
这两个宏用来标识“创建者”和“使用者”角色的常量,也就是开发端和客户端,使代码更加美观
4.#define Read O_RDONLY
   #define Write O_WRONLY
为了使代码更加具有可读性,使用宏定义分别替换了只读和只写的系统宏定义。
5.#define BaseSize 4096
这里使用了一个通常用作读写操作的基本缓冲区大小,缓冲区大小一般设置为4096的整数倍
const std::string comm_path = "./5-9Linux内存共享";
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096

类的框架如下: 

class NamePiped
{
private:public:private:const std::string _fifo_path;//一个只读的类成员变量,用于存储命名管道的文件路径。int _id;//类成员变量,用于标示当前对象在逻辑上是“创建者”还是“使用者”。int _fd;//类成员变量,存储文件描述符(File Descriptor)。打开命名管道(无论是读还是写)后,系统调用 open() 将返回一个文件描述符,该描述符用于后续的读写操作。
};

NamePiped 构造函数

  • 功能:根据角色(创建者或用户)创建或准备使用一个命名管道。
  • 系统调用
    • mkfifo(const char *pathname, mode_t mode):创建一个命名管道文件。
    • pathname 指定命名管道文件的名称,mode 指定文件的权限。成功时返回0,失败时返回-1。
    NamePiped(const std::string &path, int who): _fifo_path(path), _id(who), _fd(DefaultFd)//根据初始化信息构造类{if (_id == Creater)//如果识别为创建者,则创建管道{int res = mkfifo(_fifo_path.c_str(), 0666);//创建一个名字为path的管道并设置初始权限为0666,其中c_str()是为了统一函数传参类型和传参if (res != 0){perror("mkfifo");}std::cout << "creater create named pipe" << std::endl;}}

OpenForRead

  • 功能:打开现有的命名管道以读取数据。
  • 调用的函数OpenNamedPipe(int mode),间接使用了下面的系统调用。
  • 系统调用
    • open(const char *pathname, int flags):打开或创建一个文件。这里用于打开命名管道文件,flags 参数设置为 O_RDONLY,表示文件以只读方式打开。

OpenForWrite

  • 功能:打开现有的命名管道以写入数据。
  • 调用的函数OpenNamedPipe(int mode),间接使用了下面的系统调用。
  • 系统调用
    • open:此处与OpenForRead类似,但flags参数设置为O_WRONLY,表示文件以只写方式打开。
    bool OpenForRead(){return OpenNamedPipe(Read);//Read在宏定义中,定义为只读}bool OpenForWrite(){return OpenNamedPipe(Write);//Write在宏定义中,定义为只写}

ReadNamedPipe

  • 功能:从命名管道读取数据。
  • 系统调用
    • read(int fd, void *buf, size_t count):从打开的文件或者设备(在这种情况下是命名管道)中读取数据。fd 是文件描述符,buf 是接收数据的缓冲区地址,count 是缓冲区的大小。返回读取的字节数,失败时返回-1。
    int ReadNamedPipe(std::string *out){char buffer[BaseSize];int n = read(_fd, buffer, sizeof(buffer));//把_fd指向的文件读到buffer中if(n > 0){buffer[n] = 0;*out = buffer;}return n;}

WriteNamedPipe

  • 功能: 向命名管道写入数据。
  • 系统调用
    • write(int fd, const void *buf, size_t count):写入数据到打开的文件或设备(这里是命名管道)。fd 是文件描述符,buf 是要写入的数据的缓冲区地址,count 是要写入的字节数。返回写入的字节数,失败时返回-1。
    int WriteNamedPipe(const std::string &in)//调用函数时需要传入要写入文件的内容{return write(_fd, in.c_str(), in.size());//把传参的内容写入文件}

NamePiped 析构函数

  • 功能:销毁对象时关闭文件描述符,并由创建者删除命名管道文件。
  • 系统调用
    • close(int fd):关闭一个打开的文件描述符。成功时返回0,失败时返回-1。
    • unlink(const char *pathname):删除一个文件的目录项,并减少文件的链接数。当文件的链接数减少到0,并且没有进程打开该文件时,释放文件占用的资源。此处用于删除命名管道文件。成功时返回0,失败时返回-1。
    int WriteNamedPipe(const std::string &in){return write(_fd, in.c_str(), in.size());}
#pragma once#include <iostream>
#include <cstdio>
#include <cerrno>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>const std::string comm_path = "./5-9Linux内存共享";
#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BaseSize 4096class NamePiped
{
private:bool OpenNamedPipe(int mode){_fd = open(_fifo_path.c_str(), mode);if (_fd < 0)return false;return true;}public:NamePiped(const std::string &path, int who): _fifo_path(path), _id(who), _fd(DefaultFd){if (_id == Creater){int res = mkfifo(_fifo_path.c_str(), 0666);if (res != 0){perror("mkfifo");}std::cout << "creater create named pipe" << std::endl;}}bool OpenForRead(){return OpenNamedPipe(Read);}bool OpenForWrite(){return OpenNamedPipe(Write);}int ReadNamedPipe(std::string *out){char buffer[BaseSize];int n = read(_fd, buffer, sizeof(buffer));if(n > 0){buffer[n] = 0;*out = buffer;}return n;}int WriteNamedPipe(const std::string &in){return write(_fd, in.c_str(), in.size());}~NamePiped(){if (_id == Creater){int res = unlink(_fifo_path.c_str());if (res != 0){perror("unlink");}std::cout << "creater free named pipe" << std::endl;}if(_fd != DefaultFd) close(_fd);}private:const std::string _fifo_path;int _id;int _fd;
};

四、共享内存 Shm(Shared memory)

4.1 基本概念

共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据
 

4.2 相关函数

4.2.1 shmget

功能

shmget 是一个System V共享内存系统调用,用于创建新的共享内存段访问一个已存在的共享内存段。

函数原型
int shmget(key_t key, size_t size, int shmflg);
参数
  • key: 这是共享内存段的识别符。可以由ftok函数产生,或者可以指定一个明确的键值。
  • size: 共享内存段大小,单位是字节。当创建新的共享内存段时需要指定。
  • shmflg: 操作标志,由一个或多个权限位(如 0644)和可能的标志位(如 IPC_CREATIPC_EXCL)组合而成。IPC_CREAT 表示如果指定的共享内存段不存在,则创建它;IPC_EXCL 与IPC_CREAT 同时使用时,如果共享内存已存在,则shmget调用失败。
返回值
  • 成功返回共享内存段的标识符(一个非负整数)。
  • 失败返回 -1 并设置 errno 以指示错误类型。
使用场景

创建或访问共享内存用于存储进程间共享的数据。

 4.2.2 shmat

功能

shmat 是用于将共享内存段附加到当前进程的地址空间。通俗点将就是把调用该函数的进程链接到共享内存。

函数原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
  • shmid: 使用shmget函数获取的共享内存段标识符。
  • shmaddr: 指针,建议的附加地址。通常设置为 NULL,让操作系统选择地址。
  • shmflg: 操作标志,设置为 SHM_RDONLY 立即将内存段设为只读,否则默认为读写。
返回值
  • 成功时返回共享内存段附加后的地址指针。
  • 失败时返回 (void *)-1 并设置 errno
使用场景

在进行进程间通信时,需要访问共享内存段中存储的数据。

4.2.3 shmdt

功能

shmdt 用于断开当前进程与共享内存段的连接。

函数原型
int shmdt(const void *shmaddr);
参数
  • shmaddr: 之前通过 shmat 获得的共享内存段的地址。
返回值
  • 成功返回 0
  • 失败返回 -1 并设置 errno
使用场景

结束对共享内存的访问,通常在进程认为自己不再需要共享内存数据后调用。

4.2.4 shmctl

功能

shmctl 对共享内存段执行各种控制操作。

函数原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
  • shmid: 共享内存段的标识符。
  • cmd: 控制命令,如 IPC_STAT (获得共享内存段的状态),IPC_SET (设置共享内存段的参数),IPC_RMID (标记共享内存段删除)。
  • buf: 指向 shmid_ds 结构体的指针,用来存储共享内存段的状态或设置状态,依赖于 cmd 参数。
返回值
  • 成功返回 0
  • 失败返回 -1 并设置 errno
使用场景

在需要检视或修改共享内存属性,或删除共享内存段时使用。

4.3 代码演示

4.3.1 shm.hpp

#ifndef __SHM_HPP__
#define __SHM_HPP__#include <iostream>
#include <string>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>const int gCreater = 1;
const int gUser = 2;
const std::string gpathname ="/home/Flash/studying/2024-5/5-9Linux内存共享";
const int gproj_id = 0x66;
const int gShmSize = 4097; // 4096*nclass Shm
{
public:/*作用: 根据传入的路径、项目ID和用户角色(创建者或使用者)初始化共享内存。它首先获取键值,然后根据角色创建或连接共享内存,并最后将共享内存连接到进程的地址空间。使用场景: 创建一个 Shm 对象,自动完成共享内存的创建或连接以及初始化操作。*/Shm(const std::string &pathname, int proj_id, int who): _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr){_key = GetCommKey();if (_who == gCreater)GetShmUseCreate();else if (_who == gUser)GetShmForUse();_addrshm = AttachShm();std::cout << "shmid: " << _shmid << std::endl;std::cout << "_key: " << ToHex(_key) << std::endl;}/*作用: 断开共享内存的连接,并且如果是创建者,则删除共享内存段。使用场景: 当 Shm 对象生命期结束时,自动清理资源,确保共享内存被正确管理。*/~Shm(){if (_who == gCreater){int res = shmctl(_shmid, IPC_RMID, nullptr);}std::cout << "shm remove done..." << std::endl;}/*作用: 将键值转换为十六进制字符串,用于打印日志。使用场景: 在调试或记录日志时,显示键值。*/std::string ToHex(key_t key){char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", key);return buffer;}/*作用: 分别用于创建和获取共享内存段。GetShmUseCreate 使用 IPC_CREAT | IPC_EXCL 标志,确保只有在共享内存不存在时,才创建新的共享内存。而 GetShmForUse 则用于连接到已经存在的共享内存。使用场景: 根据进程的角色(创建者或使用者),选择合适的方法来获取共享内存标识符*/bool GetShmUseCreate(){if (_who == gCreater){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);if (_shmid >= 0)return true;std::cout << "shm create done..." << std::endl;}return false;}bool GetShmForUse(){if (_who == gUser){_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);if (_shmid >= 0)return true;std::cout << "shm get done..." << std::endl;}return false;}/*作用: 将整个共享内存段的数据置零。使用场景: 初始化共享内存内容,或者在某些操作完成后清理共享内存。*/void Zero(){if (_addrshm){memset(_addrshm, 0, gShmSize);}}/*作用: 返回共享内存段在当前进程地址空间中的起始地址。使用场景: 当需要操作共享内存中的数据时,可以通过此地址来访问。*/void *Addr(){return _addrshm;}/*作用: 使用 shmctl 的 IPC_STAT 命令获取共享内存的状态,并打印相关信息。使用场景: 调试或监控共享内存的使用情况。*/void DebugShm(){struct shmid_ds ds;int n = shmctl(_shmid, IPC_STAT, &ds);/*int shmctl(int shmid, int cmd, struct shmid_ds *buf);功能:对共享内存段执行控制操作,比如删除共享内存段。参数:shmid:共享内存标识符。cmd:命令标志,例如IPC_STAT(获取共享内存的状态)、IPC_SET(设置共享内存的参数)或IPC_RMID(删除共享内存段)。buf:指向shmid_ds结构体的指针,该结构体包含共享内存段的当前状态信息。返回值:成功时返回0,失败时返回-1。*/if (n < 0)return;std::cout << "ds.shm_perm.__key : " << ToHex(ds.shm_perm.__key) << std::endl;std::cout << "ds.shm_nattch: " << ds.shm_nattch << std::endl;}private:/*作用: 通过 ftok 函数生成一个唯一的键值,用于共享内存的创建或访问。使用场景: 在创建共享内存之前需要先获取一个键值。*/key_t GetCommKey(){key_t k = ftok(_pathname.c_str(), _proj_id);/*key_t ftok(const char *pathname, int proj_id);功能:生成一个System V IPC键值(key),用于shmget函数。需要给定一个路径名和一个项目ID(非零),通常用于确保生成的键值唯一。参数:pathname:指向一个存在的文件的路径字符串。proj_id:一个非零整数,通常是一个字符常量,用于帮助生成唯一键。返回值:成功时返回键值,失败时返回-1。*/if (k < 0){perror("ftok");}return k;}/*作用: 封装了 shmget 函数,根据提供的键值、大小和标志来获取共享内存标识符。使用场景: 创建共享内存或获取访问既存共享内存的标识符。*/int GetShmHelper(key_t key, int size, int flag){int shmid = shmget(key, size, flag);/*int shmget(key_t key, size_t size, int shmflg);功能:根据指定的键值key获取共享内存标识符shmid(创建或访问共享内存段)。参数:key:共享内存段的键值。size:共享内存段的大小,以字节为单位。shmflg:权限标志,可以是权限位的组合,如0666(八进制),可能还会包括IPC_CREAT(不存在则创建)、IPC_EXCL(与IPC_CREAT同时使用,若已存在则失败)等。返回值:成功时返回共享内存段的标识符,失败时返回-1。*/if (shmid < 0){perror("shmget");}return shmid;}/*作用: 将角色标识(创建者或使用者)转换为字符串表示,用于打印日志。使用场景: 在日志输出时,标明当前操作是由创建者还是使用者进行。*/std::string RoleToString(int who){if (who == gCreater)return "Creater";else if (who == gUser)return "gUser";elsereturn "None";}/*作用: 通过 shmdt 函数断开共享内存段与当前进程的连接。使用场景: 当完成对共享内存的操作后,为了避免资源泄露,需要将其从进程的地址空间断开。*/void DetachShm(void *shmaddr){if (shmaddr == nullptr)return;shmdt(shmaddr); // shmdt断开共享内存段与当前进程的连接/*int shmdt(const void *shmaddr);功能:断开共享内存段与当前进程的连接。参数:shmaddr:共享内存段在当前进程中的起始地址指针。返回值:成功时返回0,失败时返回-1。*/std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;}/*作用: 通过 shmat 函数将共享内存段连接到当前进程的地址空间。使用场景: 当需要在进程中读写共享内存中的数据时,需要先将其连接到进程的地址空间。*/void *AttachShm(){if (_addrshm != nullptr)DetachShm(_addrshm);void *shmaddr = shmat(_shmid, nullptr, 0);/*void *shmat(int shmid, const void *shmaddr, int shmflg);功能:将共享内存段连接(attach)到当前进程的地址空间。参数:shmid:共享内存标识符。shmaddr:指定共享内存连接到当前进程中的地址位置,通常设为NULL,让系统选择该地址。shmflg:操作标志,设置为0表示允许读写操作。返回值:成功时返回指向共享内存第一个字节的指针,失败时返回-1。*/if (shmaddr == nullptr) // 共享内存首字节为空,说明没有创建共享内存{perror("shmat");}std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;return shmaddr;}private:key_t _key;int _shmid;std::string _pathname;int _proj_id;int _who;void *_addrshm;
};#endif

4.3.2 server.cc(服务端)

#include "Shm.hpp"int main()
{//创建共享内存Shm shm(gpathname, gproj_id, gCreater);char *shmaddr = (char*)shm.Addr();shm.DebugShm();sleep(5);return 0;
}

4.3.3 client.cc(客户端)

#include "Shm.hpp"int main()
{//创建共享内存Shm shm(gpathname, gproj_id, gUser);shm.Zero();char *shmaddr = (char *)shm.Addr();sleep(3);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/10894.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Django Admin后台管理:高效开发与实践

title: Django Admin后台管理&#xff1a;高效开发与实践 date: 2024/5/8 14:24:15 updated: 2024/5/8 14:24:15 categories: 后端开发 tags: DjangoAdmin模型管理用户认证数据优化自定义扩展实战案例性能安全 第1章&#xff1a;Django Admin基础 1.1 Django Admin简介 Dj…

手撕C语言题典——反转链表

目录 前言 一.思路 1&#xff09;创建新链表 2&#xff09;创建三个指针 二.代码实现 搭配食用更佳哦~~ 数据结构之单单单——链表-CSDN博客 数据结构之单链表的基本操作-CSDN博客 前面学了单链表的相关知识&#xff0c;我们来尝试做一下关于顺序表的经典算法题~ 前言 反转…

Github 2024-05-12 php开源项目日报 Top10

根据Github Trendings的统计,今日(2024-05-12统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目10Filament: 加速Laravel开发的完美起点 创建周期:1410 天开发语言:PHP协议类型:MIT LicenseStar数量:12228 个Fork数量:1990 次关…

Isaac Sim 4 键盘控制小车前进方向(学习笔记5.8.2)

写的乱糟糟&#xff0c;主要是这两周忘了记录了...吭哧吭哧往下搞&#xff0c;突然想起来要留档&#xff0c;先大致写一个&#xff0c;后面再往里添加和修改吧&#xff0c;再不写就全忘了 有一个一直没解决的问题&#xff1a; 在保存文件时出现问题&#xff1a;isaac sim mism…

Docker学习(带图详细)

一、安装docker 参考官方文档&#xff1a;https://docs.docker.com/engine/install/centos/ 查看系统版本 [rootlocalhost ~]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) [rootlocalhost ~]# [rootlocalhost ~]# uname -a Linux localhost.localdomai…

vs code中如何使用git

由于本地代码有了一些储备&#xff0c;所以想通过网址托管形式&#xff0c;之前一直使用了github&#xff0c;但是鉴于一直被墙&#xff0c;无法登录账号&#xff0c;所以选择了国内的gitee来作为托管网站。 gitee的网址&#xff1a;Gitee - 基于 Git 的代码托管和研发协作平台…

在另外一个页面,让另外一个页面弹框显示操作(调佣公共的弹框)

大概意思是&#xff0c;登录弹框在另外一个页面中&#xff0c;而当前页面不存在&#xff0c;在当前页面中判断如果token不存在&#xff0c;就弹框出登录的弹框 最后一行 window.location.href … 如果当前用户已登录&#xff0c;则执行后续操作(注意此处&#xff0c;可不要)

QT设计模式:策略模式

基本概念 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为型设计模式&#xff0c;它定义了一系列方法&#xff0c;并使它们可以相互替换。策略模式使得算法可以独立于客户端而变化&#xff0c;使得客户端可以根据需要选择相应的算法。 策略模式通常由以下角色组…

如何创建window7,window10虚拟机

一、创建window7虚拟机 他的镜像不像window11一样可以搜到的&#xff0c;我们需要去msdn下载他的镜像文件 个人推荐倒数第四个&#xff0c;也就是我勾选的那个 这个是迅雷下载地址&#xff0c;复制到迅雷里下载就好了。 最好和我这样&#xff0c;创建文件夹&#xff0c;虚拟机…

创新指南|设计冲刺 – 更快找到成功的创新方案

“ 设计冲刺&#xff08;Design Sprint&#xff09;” 一词与跑步无关&#xff0c;而且不局限于设计&#xff0c;它与引导团队加速创新密切相关。设计冲刺旨在帮助创新团队在很短的时间内解决一个极有价值的问题。本文将深入解析这一法宝&#xff1a;设计冲刺是什么&#xff1f…

49. UE5 RPG 使用Execution Calculations处理对目标造成的最终伤害

Execution Calculations是Unreal Engine中Gameplay Effects系统的一部分&#xff0c;用于在Gameplay Effect执行期间进行自定义的计算和逻辑操作。它允许开发者根据特定的游戏需求&#xff0c;灵活地处理和修改游戏中的属性&#xff08;Attributes&#xff09;。 功能强大且灵…

【操作系统期末速成】​内存管理|内存的装入模块在装入内存的方式|分配管理方式|页面置换算法|页面置换

&#x1f3a5; 个人主页&#xff1a;深鱼~&#x1f525;收录专栏&#xff1a;操作系统&#x1f304;欢迎 &#x1f44d;点赞✍评论⭐收藏 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到…

栈和队列讲解

文章目录 栈栈的实现栈的初始化压栈出栈获取栈顶元素获取栈内有效元素个数检查是否为空销毁栈栈的使用 栈全部代码队列的初始化队尾入队列队头出队列获取队列头部元素获取队列队尾元素获取队列中有效元素个数检测队列是否为空&#xff0c;如果为空返回非零结果&#xff0c;如果…

矿用泄爆装置之——水封式防爆器使用说明

矿用泄爆装置之——水封式防爆器使用说明。 在这个快节奏的时代&#xff0c;您需要一款能够与您一同成长&#xff0c;满足您需求的产品。我们为您带来的不仅仅是一件产品&#xff0c;更是一份属于您不一样的新体验。【1-5-9】 一、水封式防爆器产品使用介绍 安装在抽放瓦斯泵进…

汇昌联信:做拼多多网点需要具备什么能力?

在当前电商行业高速发展的背景下&#xff0c;拼多多以其独特的商业模式迅速崛起&#xff0c;成为众多创业者和商家关注的焦点。想要运营一家成功的拼多多网点&#xff0c;不仅需要对平台规则有深入的了解&#xff0c;还需要具备多方面的能力。这些能力是确保网点稳定运营并实现…

使用python将`.mat`文件转换成`.xlsx`格式的Excel文件!!

要将.mat文件转换成.xlsx格式的Excel文件 第一步&#xff1a;导入必要的库第二步&#xff1a;定义函数来转换.mat文件第三步&#xff1a;调用函数注意事项 要将.mat文件转换成.xlsx格式的Excel文件&#xff0c;并保持文件名一致&#xff0c;你可以使用scipy.io.loadmat来读取.m…

【getopt函数用法】

这里写目录标题 一、概述二、选项字符串规则&#xff1a;三、getopt 返回值四、会用到的全局变量&#xff1a;三、示例代码四、上机实验 一、概述 int getopt(int argc, char * const argv[], const char *optstring); extern char *optarg; //这个最常用&#xff0c;保存一个…

Geopandas以及CMakeList程序编写技巧

Geopandas官方文档 Geopandas官方文档 reset_index()函数 pandas库中的reset_index()函数是用于重新设置数据框索引的方法。 例如&#xff1a;当我对于文件数据进行了一系列操作后&#xff0c;例如设置了索引set_index&#xff0c;那么会导致数据的索引框发生变化&#xff…

医疗行业面临的网络安全挑战及应对策略

网络攻击已经成为各行各业日益严重的威胁&#xff0c;但医疗行业尤其容易受到影响。2023年&#xff0c;医疗领域的黑客事件占数据泄露的79.7%。 医疗领域 虽然患者、医疗服务提供者和决策者都对保护医疗信息有所关注&#xff0c;但关键的弱点在于提供电子健康记录&#xff08;…

【iOS】RunLoop详解(二)

RunLoop详解&#xff08;二&#xff09; RunLoop 的概念RunLoop 与线程的关系RunloopRunloop与线程的关系RunLoop对外的接口Runloop的Mode应用场景举例举例说明小结 RunLoop 的内部逻辑RunLoop的底层实现苹果用RunLoop实现的功能AutoreleasePool事件响应手势识别界面更新定时器…