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.rhkb.cn/news/325985.html

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

相关文章

生产设备数据管控要怎么做 可以实现精益生产和智能制造?

生产设备在制造过程中会产生多种类型的数据&#xff0c;这些数据对于优化生产流程、提高效率、降低成本和预防性维护等方面至关重要。需要对这些数据进行有效的采集和管理&#xff0c;以实现对生产设备数据管控。 一、生产设备数据类型包括&#xff1a; 设备运行状态数据&…

机器学习周报第三十八周 iTransformer

文章目录 week38 iTransformer摘要Abstract一、文献阅读1. 题目2. abstract3. 网络架构**转置Embedding&#xff1a;****LayerNorm&#xff08;层归一化&#xff09;****Feed-forward network&#xff08;前馈网络&#xff09;****Multivariate-Attention&#xff08;多变量注意…

【Linux 网络】网络编程套接字 -- 详解

⚪ 预备知识 1、理解源 IP 地址和目的 IP 地址 举例理解&#xff1a;&#xff08;唐僧西天取经&#xff09; 在 IP 数据包头部中 有两个 IP 地址&#xff0c; 分别叫做源 IP 地址 和目的 IP 地址。 如果我们的台式机或者笔记本没有 IP 地址就无法上网&#xff0c;而因为…

【UnityUI程序框架】The PureMVC Framework[一]底层源码中文详解

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

推导 模型矩阵的逆转置矩阵求运动物体的法向量

一个物体表面的法向量如何随着物体的坐标变换而改变&#xff0c;取决于变换的类型。使用逆转置矩阵&#xff0c;可以安全地解决该问题&#xff0c;而无须陷入过度复杂的计算中。 法向量变化规律 平移变换不会改变法向量&#xff0c;因为平移不会改变物体的方向。 旋转变换会改…

Python 全栈系列244 nginx upstream 负载均衡 踩坑日记

说明 最初是因为租用算力机(Python 全栈系列242 踩坑记录:租用算力机完成任务)&#xff0c;所以想着做一个负载均衡&#xff0c;然后多开一些服务&#xff0c;把配置写在nginx里面就好了。 一开始租用了一个3080起了一个服务&#xff0c;后来觉得速度不够快&#xff0c;再起了…

基于地平线J6E,「吃蟹者」易航智能重塑高速NOA

作者 |张祥威 编辑 |德新 一批基于地平线J6E的智驾方案将要到来&#xff0c;高速NOA领域很快会变天。 易航智能是这批智驾方案公司中的一家。 近日在北京车展&#xff0c;这家公司推出一套基于地平线J6 E的7V1R方案&#xff0c;可以实现城市记忆领航、高速NOA、记忆泊车、L2…

社交媒体数据恢复:密聊猫

一、概述 密聊猫是一款提供多种优质体验的手机社交聊天软件。通过这款软件&#xff0c;用户可以享受到多种不同的乐趣体验&#xff0c;如真人在线匹配、真实的交友体验等。同时&#xff0c;密聊猫也提供了数据恢复功能&#xff0c;帮助用户找回丢失的数据。 二、数据恢复步骤…

前端Vue架构

1 理解&#xff1a; 创建视图的函数&#xff08;render&#xff09;和数据之间的关联&#xff1b; 当数据发生变化的时候&#xff0c;希望render重新执行&#xff1b; 监听数据的读取和修改&#xff1b; defineProperty&#xff1a;监听范围比较窄&#xff0c;只能通过属性描…

博客互动革命:如何打造活跃读者社区并提升参与度

CSDN 的朋友你们好&#xff0c;我是未来&#xff0c;今天给大家带来专栏【程序员博主教程&#xff08;完全指南&#xff09;】的第 10 篇文章“与读者互动”。本文揭示了提升技术博客参与度的秘诀。从评论互动到社交媒体策略&#xff0c;本文将指导你如何建立强大的读者社区。掌…

blender 为世界环境添加纹理图像

1、打开世界环境配置项 2、点击颜色右侧的黄色小圆&#xff0c;选择环境纹理 3、打开一张天空图像 4、可以通过调整强度/力度&#xff0c;调整世界环境的亮度

传感网应用开发教程--AT指令访问新大陆云平台(ESP8266模块+物联网云+TCP)

实现目标 1、熟悉AT指令 2、熟悉新大陆云平台新建项目 3、具体目标&#xff1a;&#xff08;1&#xff09;注册新大陆云平台&#xff1b;&#xff08;2&#xff09;新建一个联网方案为WIFI的项目&#xff1b;&#xff08;3&#xff09;ESP8266模块&#xff0c;通过AT指令访问…

用python写算法——队列笔记

1.队列定义 队列是一种特殊的线性表&#xff0c;它只允许在表的前端进行删除操作&#xff0c;在表的后端进行插入操作&#xff0c;和栈一样&#xff0c;队列是一种操作受限制的线性表。进行插入操作的端称为队尾&#xff0c;进行删除操作的端称为队头。队列中没有元素时&#…

部署Discuz论坛项目

DIscuz 是由 PHP 语言开发的一款开源社交论坛项目。运行在典型的LNMP/LAMP 环境中。 安装MySQL数据库5.7 主机名IP地址操作系统硬件配置discuz-db192.168.226.128CentOS 7-mini-20092 Core/4G Memory 修改主机名用来自己识别 hostnamectl set-hostname discuz-db #重连远程…

一键复制:基于vue实现的tab切换效果

需求&#xff1a;顶部栏有切换功能&#xff0c;内容区域随顶部切换而变化 目录 实现效果实现代码使用示例在线预览 实现效果 如下 实现代码 组件代码 MoTab.vue <template><div class"mo-tab"><divv-for"item in options"class"m…

OBS插件--视频回放

视频回放 视频回放是一款源插件&#xff0c;它可以将指定源的视频缓存一段时间&#xff08;时间可以设定&#xff09;&#xff0c;将缓存中的视频添加到当前场景中后&#xff0c;可以快速或慢速不限次数的回放。这个功能在类似体育比赛的直播中非常有用&#xff0c;可以捕获指…

网络基础(三)——网络层

目录 IP协议 1、基本概念 2、协议头格式 2.1、报头和载荷如何有效分离 2.2、如果超过了MAC的规定&#xff0c;IP应该如何做呢&#xff1f; 2.3、分片会有什么影响 3、网段划分 4、特殊的ip地址 5、ip地址的数量限制 6、私有ip地址和公网ip地址 7、路由 IP协议 网络…

C++/Qt 小知识记录6

工作中遇到的一些小问题&#xff0c;总结的小知识记录&#xff1a;C/Qt 小知识6 dumpbin工具查看库导出符号OSGEarth使用编出的protobuf库&#xff0c;报错问题解决VS2022使用cpl模板后&#xff0c;提示会乱码的修改设置QProcess调用cmd.exe执行脚本QPainterPath对线段描边处理…

实验0.0 Visual Studio 2022安装指南

Visual Studio 2022 是一个功能强大的开发工具&#xff0c;对于计算机专业的学生来说&#xff0c;它不仅可以帮助你完成学业项目&#xff0c;还能为你将来的职业生涯打下坚实的基础。通过学习和使用 Visual Studio&#xff0c;你将能够更高效地开发软件&#xff0c;并在编程领域…

picoCTF-Web Exploitation-More SQLi

Description Can you find the flag on this website. Additional details will be available after launching your challenge instance. Hints SQLiLite 先随便输入个账号密码登录一下&#xff0c;得到查询SQL&#xff0c;接下来应该对SQL进行某些攻击来绕过密码登录成功 -- …