【Linux】System V共享内存:零拷贝加速进程通信!

引言

本文深入探讨System V IPC中的共享内存技术,涵盖其原理、操作步骤、实现细节及与其他IPC机制的关系,助力读者全面掌握这一高效进程间通信方式。

📝 文章总结:

共享内存原理

System V共享内存通过让多个进程共享同一物理内存区域实现高效通信。操作系统开辟共享内存空间,各进程保持独立的内核数据结构、代码和数据,通过页表映射共享内存到各自虚拟地址空间,从而直接访问和修改数据,减少数据复制开销。

共享内存数据结构

共享内存相关数据结构如struct shmid_ds,包含操作权限、段大小、时间戳、创建者和最后操作者进程ID、当前挂载数等信息,用于管理和控制共享内存段。

操作步骤

  1. 获取key值:通过ftok系统调用,以文件路径名和项目标识符生成唯一key值,供创建或访问共享内存段使用。

  2. 创建共享内存段:使用shmget系统调用,传入key值、大小和标志位,创建新共享内存段或获取已有段。

  3. 映射共享内存:通过shmat系统调用,将共享内存段映射到进程虚拟地址空间,使进程能通过指针访问。

  4. 使用共享内存:进程像访问普通内存一样直接操作共享内存,多个进程可同时读写。

  5. 解除映射与删除:使用shmdt解除映射,shmctl删除不再需要的共享内存段。

实现进程间通信的准备工作

  1. 创建与获取共享内存:利用shmget创建或获取共享内存段,标志位组合实现不同创建与访问策略。

  2. 生成唯一key值ftok函数生成key值,确保不同进程能获取同一共享内存段。

  3. 封装代码:创建Shm类管理共享内存,包含获取key、创建、映射、解除映射、删除等操作,方便服务端和客户端使用。

  4. 挂接与解除共享内存:使用shmatshmdt实现共享内存的挂接和解除映射,确保正确管理内存映射。

进行通信

  1. 客户端与服务端通信实现:客户端写入数据,服务端读取并打印,通过循环和睡眠函数模拟实际通信场景。

  2. 共享内存清空:提供Zero函数清空共享内存内容,确保通信开始时内存无残留数据。

  3. 数据一致性问题:指出共享内存不提供保护机制,可能导致数据不一致,需额外同步机制保障。

共享内存的保护机制

共享内存本身不提供保护,易因多进程同时读写出现数据不一致。可借助管道等其他IPC机制实现同步与互斥,如服务端创建管道,客户端写入数据后发信号,服务端接收到信号后读取共享内存,确保数据一致性。

其他相关内容

  1. 共享内存大小设置:建议以4096的倍数设置共享内存大小,如4096*n,以适应操作系统内存分配策略。

  2. 共享内存属性获取:使用shmctl函数的IPC_STAT命令获取共享内存段状态,如挂载数等信息,辅助调试与监控。

  3. 共享内存与其他IPC机制关系:System V IPC包含共享内存、消息队列、信号量,共享内存效率最高,但需与其他机制配合解决同步互斥问题。

目录

引言

📝 文章总结:

共享内存的原理

基本原理

图解--对于共享内存的管理-->OS对链表的增删改查

操作步骤

实现进程间通信的准备工作

1. shmget 函数 -- 创建|获取共享内存段

2. ftok 函数--生成一个唯一的key值

3.创建共享内存

4.删除共享内存(指令)

IPC指令以及key和shmid的区别

ipcs -m  ---- 查到共享内存

ipcrm -m shmid --- 删除共享内存:

5.封装以上代码

6.User和Creater对于共享内存的访问与创建

构造函数

7. shmctl 函数--对共享内存段进行控制操作

8.析构函数

9. shmat 函数-将共享内存段映射到调用进程的地址空间。

10. shmdt 函数--解除共享内存段与进程地址空间的映射

11.挂接共享内存

12.解除共享内存段与进程地址空间的映射

优化以上部分代码:

进行通信:

共享内存不提供对共享内存的任何保护机制,会导致数据不一致问题。

如何保护共享内存

IPC_STAT:获取共享内存段的状态。


共享内存的原理

System V共享内存是一种高效的进程间通信(IPC)机制,它允许多个进程共享一块物理内存区域,从而避免了数据的复制,显著提高了数据传输速率。以下是System V共享内存的原理及操作步骤:

基本原理

  • 共享内存空间:由OS在物理内存当中开辟的内存空间(是由某个进程创建,属于操作系统),而每个进程的内核数据结构,代码和数据依旧保持独立,具有独立性。
  • 共享内存的核心思想:同一块物理内存被页表映射到多个进程的虚拟地址空间(在虚拟地址空间中的堆和栈之间的共享区中申请的一段空间)中。每个进程通过自己的虚拟地址再通过页表访问这块物理内存,再将起始的虚拟地址返回给上层用户,由于多个进程访问的是同一块物理内存,因此它们可以看到彼此写入的数据。

  • 图解--对于共享内存的管理-->OS对链表的增删改查

  •    共享内存数据结构
struct shmid_ds {struct ipc_perm shm_perm; /* operation perms */int shm_segsz; /* size of segment (bytes) */__kernel_time_t shm_atime; /* last attach time */__kernel_time_t shm_dtime; /* last detach time */__kernel_time_t shm_ctime; /* last change time */__kernel_ipc_pid_t shm_cpid; /* pid of creator */__kernel_ipc_pid_t shm_lpid; /* pid of last operator */unsigned short shm_nattch; /* no. of current attaches */unsigned short shm_unused; /* compatibility */void *shm_unused2; /* ditto - used by DIPC */void *shm_unused3; /* unused */}
  • 高效性:通过共享内存,不同进程可以直接访问和修改数据,而不需要经过数据复制的过程,减少了开销。

  • 持久性:共享内存段在进程退出后仍然存在,直到显式地由进程删除。

  • 权限控制:共享内存段可以设定访问权限,控制哪些进程可以读写共享内存。

操作步骤

  1. 获取key值:进程通过ftok系统调用来计算出key值。ftok函数的参数包括一个文件路径名和一个项目标识符,用于生成一个唯一的标识符。

  2. 创建共享内存段:进程使用shmget系统调用来创建或访问共享内存段。shmget函数需要三个参数,分别是key值、共享内存段的大小和标志位。

  3. 映射共享内存:通过shmat系统调用,将共享内存段映射到进程的虚拟地址空间中。这样,进程就可以通过指针访问共享内存。

  4. 使用共享内存:进程可以直接通过指针访问共享内存,就像访问普通内存一样。多个进程可以同时对共享内存进行读写操作。

  5. 解除映射:进程使用shmdt来解除对共享内存段的映射。当进程不再需要访问共享内存时,应该解除映射。

  6. 删除共享内存段:当不再需要共享内存时,进程通过shmctl来删除共享内存段。

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


共享内存示意图

实现进程间通信的准备工作

1. shmget 函数 -- 创建|获取共享内存段

shm --- shared memory

用于创建一个新的共享内存段或获取一个已存在的共享内存段。

函数原型

int shmget(key_t key, size_t size, int shmflg);
       #include <sys/ipc.h>#include <sys/shm.h>

参数

  • key:由ftok,由用户自主生成的key值。想让两进程在不进行通信的前提下,就能得到同一个key,通过一个进程来创建,一个进程来获取来实现进程间的通信。

进程如何得知共享内存是否在OS中存在?共享内存的数据结构Struct Shm当中一定会有一个来区分共享内存唯一性的标识符struct Shm *next(类似于进程的pid)

  • size:共享内存段的大小(字节)。

  • shmflg:标志位(可以采用位图的方式传参)具体是怎么设计的可以看这篇博客

【Linux】 基础IO之操作与文件描述符fd全解析:从C语言到系统调用底层实现_io导出0kb文件-CSDN博客

标志位通常设置为:

IPC_CREAT(创建新段):如果不存在就创建,存在,获取该共享内存并返回标识符。

IPC_EXCL(不单独使用,和IPC_CREAT组合才有意义

IPC_CREAT | IPC_EXCL(创建新段并确保唯一性):如果不存在就创建,存在就出错返回-1,并设置errno。如果成功返回就意味着这shm是全新的还可以通过0666等权限位设置访问权限

因为,在未来总是有人在创建,有人在获取。命名管道中所讲到的Creater,User。

返回值

  • 成功:返回共享内存段的标识符(shm_id)。

  • 失败:返回-1,并设置errno

示例

int shm_id = shmget(key, 4096, IPC_CREAT | 0666);
if (shm_id == -1) {perror("shmget error");exit(1);
}

2. ftok 函数--生成一个唯一的key值

该key值将用于创建或访问共享内存段

函数原型

key_t ftok(const char *pathname, int proj_id);
      #include <sys/types.h>#include <sys/ipc.h>

参数

  • pathname:一个存在的文件路径名,用于生成key。

  • proj_id:项目标识符,通常是一个字符,用于区分不同的项目。

返回值

  • 成功:返回生成的key值

  • 失败:返回-1,并设置errno

示例

key_t key = ftok("/tmp/shmfile", 'A');
if (key == -1) {perror("ftok error");exit(1);
}

具体实现:

 需要准备四个文件:

Shm.hpp --->用于管理共享内存空间       server.cc         client.cc

serverclient 是两个常见的角色名称,这种命名方式是为了模拟一种典型的客户端-服务器(Client-Server)架构

  • Server(服务器):通常是指提供服务的一方,它等待客户端的请求,并根据请求提供相应的服务。

  • Client(客户端):是指请求服务的一方,它主动发起请求,等待服务器的响应。

Makefile:

.PHONY: all
all: client serverclient: client.ccg++ -o $@ $^ -std=c++11server: server.ccg++ -o $@ $^ -std=c++11.PHONY: clean
clean:rm -rf client server

serverclient公共值

1.文件路径了,项目id

const std::string gpathname = "/home/pupu/bitclass/class_29/4.shm";const int gproj_id = 0x66;

2.创建key并转为16进制:

std::string ToHex(key_t k)
{char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", k);return buffer;
}key_t GetCommKey(const std::string &pathname, int proj_id)
{key_t k = ftok(pathname.c_str(), proj_id);if(k < 0){perror("ftok");}return k;
}

3.检测serverclient获取到的key:

#include "Shm.hpp"int main()
{key_t key = GetCommKey(gpathname, gproj_id);std::cout << "key: " << ToHex(key) << std::endl;return 0;
}

获取到同样的key:

3.创建共享内存

1.创建共享内存

int GetShmHelper(key_t key, int size)
{int shmid = shmget(key, size, IPC_CREAT | IPC_EXCL);if (shmid < 0){perror("shmget");}return shmid;
}

2.测试:

    int shmid = GetShmHelper(key, 4096); //创建一个4096B的共享内存

第一次创建:

多次创建(包括make clean):

这说明了共享内存不随着进程的结束而自动释放

因为共享内存是OS创建的,需要通过系统调用来手动释放,不然这个共享内存会一直存在,除非重启系统。

共享内存的生命周期随内核,文件的生命周期随进程

4.删除共享内存(指令)

IPC指令以及key和shmid的区别

ipcs -m  ---- 查到共享内存

ipcs -m

ipcrm -m shmid --- 删除共享内存:

ipcrm -m 0

1.无法用key直接删除

2.使用shmid可删除

key:是我们自己创建的,属于用户形成,内核使用的一个字段,用户不能使用key来进行shm的管理,内核进行区分shm的唯一性

shmid:内核给用户返回的一个标识符,用来进行用户级对共享内存管理的id值(类似于文件系统中的fd ---> struct FILE*(对应每个文件的地址))

为什么要分开:这是为了让内核与用户级隔离 ---> 解耦

5.封装以上代码

1.添加身份标识:

const int gCreater = 1;
const int gUser = 2;

2.成员参数:

private:key_t _key;int _shmid;std::string _pathname;int _proj_id;int _who;

3.各函数修改:

public:   std::string ToHex(){char buffer[128];snprintf(buffer, sizeof(buffer), "0x%x", _key);return buffer;}private:key_t GetCommKey(){key_t k = ftok(_pathname.c_str(), _proj_id);if (k < 0){perror("ftok");}return k;}int GetShmHelper(key_t key, int size, int flag){int shmid = shmget(key, size, flag);if (shmid < 0){perror("shmget");}return shmid;}// std::string 

6.User和Creater对于共享内存的访问与创建

共享内存的大小:

const int gShmSize = 4097; // 4096*n
 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;}

构造函数

    Shm(const std::string &pathname, int proj_id, int who): _pathname(pathname), _proj_id(proj_id), _who(who){_key = GetCommKey();if (_who == gCreater)GetShmUseCreate();else if (_who == gUser)GetShmForUse();std::cout << "shmid: " << _shmid << std::endl;std::cout << "_key: " << ToHex() << std::endl;}

7. shmctl 函数--对共享内存段进行控制操作

用于,如获取状态、设置选项或删除共享内存段。

函数原型

int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

参数

  • shm_id:共享内存段标识符。

  • cmd:命令,常见的有:

    • IPC_STAT:获取共享内存段的状态。

    • IPC_SET:设置共享内存段的选项。

    • IPC_RMID:删除共享内存段。

  • buf:指向shmid_ds结构的指针,用于存储或修改共享内存段的信息。

返回值

  • 成功:返回0。

  • 失败:返回-1,并设置errno

示例(删除共享内存段)

if (shmctl(shm_id, IPC_RMID, NULL) == -1) {perror("shmctl error");exit(1);
}

8.析构函数

    ~Shm(){if (_who == gCreater){int res = shmctl(_shmid, IPC_RMID, nullptr);}std::cout << "shm remove done..." << std::endl;}

以上代码能使服务端和客户端看到共享内存,如何使用在下面的代码讲解:

9. shmat 函数-将共享内存段映射到调用进程的地址空间。

shm --- shared memory,at --- attach(关联)

函数原型

void *shmat(int shm_id, const void *shmaddr, int shmflg);

参数

  • shm_id:由shmget返回的共享内存段标识符。

  • shmaddr:指定共享内存段映射到进程地址空间的位置。通常设置为NULL,让系统自动选择地址。

  • shmflg:标志位,通常设置为0。对共享内存的访问权限。

        可以设置为SHM_RND(对齐到页边界)或SHM_RDONLY(只读映射)。

返回值

  • 成功:返回映射到进程地址空间的指针(地址空间中共享内存的起始地址 ---> 类似于malloc返回的我们在堆上申请的空间的起始地址)

  • 失败:返回(void *)-1,并设置errno

示例

char *shm_ptr = (char *)shmat(shm_id, NULL, 0);
if (shm_ptr == (void *)-1) {perror("shmat error");exit(1);
}

10. shmdt 函数--解除共享内存段与进程地址空间的映射

函数原型

int shmdt(const void *shmaddr);

参数

  • shmaddr:由shmat返回的映射地址。

返回值

  • 成功:返回0。

  • 失败:返回-1,并设置errno

示例

if (shmdt(shm_ptr) == -1) {perror("shmdt error");exit(1);
}

11.挂接共享内存

    void *AttachShm(){void *shmaddr = shmat(_shmid, nullptr, 0);if(shmaddr == nullptr){perror("shmat");}std::cout << "who: "<< RoleToString(_who) <<", attach shm..." << std::endl;return shmaddr;}std::string RoleToString(int who){if(who == gCreater) return "Creater";else if(who == gUser) return "User";else return "null";}
//服务端int main()
{// 1. 服务端创建共享内存Shm shm(gpathname, gproj_id, gCreater);// 2. 服务端挂接共享内存char *addr = (char*)shm.AttachShm();sleep(5);return 0;
}//客户端
int main()
{// 1. 客户端获取共享内存Shm shm(gpathname, gproj_id, gUser);// 2. 服务端挂接共享内存char *addr = (char*)shm.AttachShm();sleep(5);return 0;
}

测试并查看效果:在系统当中每隔一秒查看一次共享内存

while :; do ipcs -m; sleep 1; done

perms: 权限   --- 666

nattch:当前共享内存的挂接数(有几个进程)   

0---1(server挂接上)---2(server、client同时挂接上)---1 (server删除共享内存)---- null 

status:状态

以上所看到的数字变化是基于在创建共享内存时,写了共享内存的权限 0666 or 0664

server:

_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | IPC_EXCL | 0666);

 client:

_shmid = GetShmHelper(_key, gShmSize, IPC_CREAT | 0666);

当没有写权限的时候,看不到挂接数量的变化

12.解除共享内存段与进程地址空间的映射

    void DetachShm(void *shmaddr){if(shmaddr == nullptr) return;shmdt(shmaddr);std::cout << "who" << RoleToString(_who) << ", detach shm... " << std::endl;}

测试代码(client同理):

    //解除共享内存段与进程地址空间的映射shm.DetachShm(addr);sleep(5);

优化以上部分代码:

1.添加成员变量地址空间中共享内存的起始地址的字段

void *_addrshm;

2.在构造函数的时候直接将地址空间中共享内存的起始地址的字段一并初始化:

    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() << std::endl;}

2.确保重新附加共享内存前,之前的映射已被正确清理

    void *AttachShm(){if (_addrshm != nullptr)DetachShm(_addrshm);void *shmaddr = shmat(_shmid, nullptr, 0);if (shmaddr == nullptr){perror("shmat");}std::cout << "who: " << RoleToString(_who) << " attach shm..." << std::endl;return shmaddr;}

为何在 AttachShm() 中提前解除映射?

  • 防止重复映射
    如果 AttachShm() 被多次调用(例如在对象生命周期内需要重新附加共享内存),新的 shmat() 调用会返回一个新的地址,而旧的地址映射仍然存在。这会导致:

    • 内存泄漏:旧的映射未被释放,占用进程地址空间。

    • 逻辑错误_addrshm 可能指向无效或过时的地址,导致后续操作异常。

  • 确保状态一致性
    _addrshm 是类成员变量,代表当前附加的共享内存地址。在重新附加前解除旧的映射,可以确保 _addrshm 始终指向最新的有效地址,避免悬空指针。

共享内存的灵活管理
共享内存的附加(shmat)和解除(shmdt)是进程级的操作,允许一个进程多次附加同一块共享内存到不同地址。若在重新附加前不解除旧映射,进程的地址空间中会积累多个无效映射,影响性能和稳定性。

类比与场景举例

  • 类比文件操作
    想象打开一个文件时,如果之前已打开过,需要先关闭旧的文件描述符,再打开新的。否则,文件描述符会泄漏。

最后在服务端和客户端去使用的时候:

只需要创建好共享内存段,以及获取共享内存地址。其余的事情都在对象的生命周期,类的内部中完成。

创建公有函数获得映射在虚拟地址空间的共享内存首地址

    void *Addr(){return _addrshm;}
    Shm shm(gpathname, gproj_id, gUser);char *shmaddr = (char*)shm.Addr();

将Shm类内部再进行调整,确保类的封装完美:

private:

    key_t GetCommKey()int GetShmHelper(key_t key, int size, int flag)std::string RoleToString(int who)void *AttachShm()void DetachShm(void *shmaddr)

public:

    Shm(const std::string &pathname, int proj_id, int who): _pathname(pathname), _proj_id(proj_id), _who(who), _addrshm(nullptr)~Shm()std::string ToHex()bool GetShmUseCreate()bool GetShmForUse()void *Addr(){return _addrshm;}

以上所做的都是使用共享内存段来实现进程间通信的准备工作

进行通信:

共享内存不提供对共享内存的任何保护机制,会导致数据不一致问题。

1.让客户端进行写入:2s写一次

    char ch = 'A';while (ch <= 'Z'){shmaddr[ch - 'A'] = ch;ch++;sleep(2);}

2.让服务端进行读取,打印客户端写入的内容:1s读取一次

    while(true){std::cout << "shm memory content: " << shmaddr << std::endl;sleep(1);}

3.在创建获取共享内存后,将共享内存清空:

    void Zero(){if(_addrshm){memset(_addrshm, 0, gShmSize);}}

运行后:

这里的问题:明显在之前的代码当中,已经让客户端sleep(3)后再开始写内容,但是服务端却已经在客户端挂接好后就直接开始读取了。

这次能看见更明显的bug:客户端已经关闭,并且一开始我们就清除了共享内存中的内容,以确保共享内存没有任何内容,但是服务端依旧在读取内容,这里可以回顾管道中所讲的当管道文件中并没有内容写入,那么读取进程就会被阻塞【Linux】深度解析Linux进程间通信:匿名管道原理、实战进程池与高频问题排查。-CSDN博客。

而在这里:客户端2s才写一次,服务端读取一次后按理来说就没有内容了,需要等待写入,却还是将上一个内容读取到了两次。

实际上以上出现的问题是因为:

共享内存不提供对共享内存的任何保护机制,会导致数据不一致问题。

共享内存是所有进程IPC速度最快的,这是因为,共享内存大大减少了数据的拷贝次数(被映射到虚拟地址空间,由用户直接使用)。

如何保护共享内存

利用管道的保护机制进行通信

服务端server:

1.创建共享内存

2.创建管道(使用之前已经封装好的命名管道,这个的讲解在上一篇文章)如图:

运行结果:

在设定共享内存的大小时,我设定的是4097,但是建议设置为4096*n

虽然我设置的是4097,实际上OS分配的共享内存是4096*2,会以4096为一个单位向上取整,但是虚拟地址空间映射过来的大小还是4097,其余的浪费在共享内存当中

共享内存等于共享内存的属性

IPC_STAT:获取共享内存段的状态。

获取共享内存的属性:

    void DebugShm(){struct shmid_ds ds;int n = shmctl(_shmid, IPC_STAT, &ds);if (n < 0)return;std::cout << "ds.shm_perm.__key : " << ToHex() << std::endl;std::cout << "ds.shm_nattch: " << ds.shm_nattch << std::endl;}

在服务端进行测试:

运行结果:

结语:

       随着这篇关于题目解析的博客接近尾声,我衷心希望我所分享的内容能为你带来一些启发和帮助。学习和理解的过程往往充满挑战,但正是这些挑战让我们不断成长和进步。我在准备这篇文章时,也深刻体会到了学习与分享的乐趣。

   

         在此,我要特别感谢每一位阅读到这里的你。是你的关注和支持,给予了我持续写作和分享的动力。我深知,无论我在某个领域有多少见解,都离不开大家的鼓励与指正。因此,如果你在阅读过程中有任何疑问、建议或是发现了文章中的不足之处,都欢迎你慷慨赐教。               

        你的每一条反馈都是我前进路上的宝贵财富。同时,我也非常期待能够得到你的点赞、收藏,关注这将是对我莫大的支持和鼓励。当然,我更期待的是能够持续为你带来有价值的内容,让我们在知识的道路上共同前行。

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

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

相关文章

UE4学习笔记 FPS游戏制作31 显示计分板

一 制作计分板 创建一个RankPanel的UI蓝图 在蓝图里拖入如下物体 覆层&#xff08;layout&#xff09;&#xff1a;让子物体跟随自己缩放&#xff0c;子物体需要设置为拉伸模式&#xff0c;有点类似于的panel&#xff0c;本身只是一个容器 调整各个物体 覆层&#xff1a; 锚…

如何在Linux CentOS上安装和配置Redis

如何在Linux CentOS上安装和配置Redis 大家好&#xff0c;我是曾续缘。欢迎来到本教程&#xff01;今天我将向您介绍在Linux CentOS上安装和配置Redis的详细步骤。Redis是一个高性能的键值存储系统&#xff0c;常用于缓存、消息队列和数据持久化等应用场景。让我们一起开始吧&…

Realsense-D400 系列手动曝光控制

文章目录 1、曝光 & 增益2、曝光 & 帧率3、调参 & 加载4、高级控制选项5、官方文档参考小结 1、曝光 & 增益 曝光exposure&#xff1a;英特尔 RealSense D400设备模组中的可见光 RGB 传感器和红外左右目传感器具有单独的曝光控制&#xff0c;对于双目红外&…

最大异或对 The XOR Largest Pair

题目来自洛谷网站&#xff1a; 思路&#xff1a; 两个循环时间复杂度太高了&#xff0c;会超时。 我们可以先将读入的数字&#xff0c;插入到字典树中&#xff0c;从高位到低位。对每个数查询的时候&#xff0c;题目要求是最大的异或对&#xff0c;所以我们选择相反的路径&am…

探索 curl ipinfo.io:从命令行获取你的网络身份卡!!!

&#x1f310; 探索 curl ipinfo.io&#xff1a;从命令行获取你的网络身份卡 &#x1faaa; &#x1f680; 简介&#xff1a;为什么需要 curl ipinfo.io&#xff1f; 当你在调试网络服务、排查访问限制或开发基于地理位置的应用时&#xff0c;公网 IP 信息就像一张网络身份证。…

Chrome(Google) 浏览器安装Vue2、Vue3 Devtools插件方法

方式一&#xff1a;本地下载安装 步骤一&#xff1a;下载 网站:极简插件官网_Chrome插件下载_Chrome浏览器应用商店 步骤二&#xff1a;下载后解压,并拖入浏览器扩展页面&#xff0c;安装插件后&#xff0c;重启浏览器。 步骤三&#xff1a;查看是否安装成功 方式二&#x…

树莓派超全系列文档--(7)RaspberryOS播放音频和视频

播放音频和视频 播放音频和视频VLC 媒体播放器vlc GUIvlc CLI使用 cvlc 在没有图形用户界面的情况下播放媒体 在 Raspberry Pi OS Lite 上播放音频和视频指定音频输出设备指定视频输出设备同时指定音频和视频输出设备提高数据流播放性能 文章来源&#xff1a; http://raspberr…

MySQL 8.0.41安装教程(附安装包)mysql8.0.41图文详细安装教程

文章目录 前言一、MySQL 8.0.41下载安装包二、MySQL 8.0.41安装教程1.启动安装程序2.选择安装模式3.选定安装组件4.确认安装设置5.执行安装操作6.安装进行中7.设置数据库密码8.继续点击下一步9.执行配置操作10.完成配置11. 再次点击下一步12.结束安装向导 三、MySQL 8.0.41配置…

centos7 linux VMware虚拟机新添加的网卡,能看到网卡名称,但是看不到网卡的配置文件

问题现象&#xff1a;VMware虚拟机新添加的网卡&#xff0c;能看到网卡&#xff0c;但是看不到网卡的配置文件 解决方案&#xff1a; nmcli connection show nmcli connection add con-name ens36 ifname ens36 type ethernet #创建一个网卡连接配置文件&#xff0c;这里con…

【LVS】负载均衡群集部署(DR模式)

部署前IP分配 DR服务器&#xff1a;192.168.166.101 vip&#xff1a;192.168.166.100 Web服务器1&#xff1a;192.168.166.104 vip&#xff1a;192.168.166.100 Web服务器2&#xff1a;192.168.166.107 vip&#xff1a;192.168.166.100 NFS服务器&#xff1a;192.168.166.108 …

服务器与客户端通讯测试

服务器与客户端通讯测试 1 服务器与客户端通讯建立1.1 Main函数1.2 开启服务器1.3 客户端连接服务器1.4 扩展类 2 测试过程2.1 测试12.2 测试22.3 测试32.4 测试4 3 测试总结 测试服务器与客户端通讯时&#xff0c;发现数据丢包问题非常严重&#xff0c;肯定是自己的问题不会是…

Next.js 中间件鉴权绕过漏洞 (CVE-2025-29927) 复现利用与原理分析

免责声明 本文所述漏洞复现方法仅供安全研究及授权测试使用&#xff1b; 任何个人/组织须在合法合规前提下实施&#xff0c;严禁用于非法目的&#xff1b; 作者不对任何滥用行为及后果负责&#xff0c;如发现新漏洞请及时联系厂商并遵循漏洞披露规则。 漏洞原理 Next.js 是一个…

基于STC89C51的太阳自动跟踪系统的设计与实现—单片机控制步进电机实现太阳跟踪控制(仿真+程序+原理图+PCB+文档)

摘 要 随着我国经济的飞速发展&#xff0c;促使各种能源使用入不敷出&#xff0c;尤其是最主要的能源&#xff0c;煤炭石油资源不断消耗与短缺&#xff0c;因此人类寻找其他替代能源的脚步正在加快。而太阳能则具有无污染﹑可再生﹑储量大等优点&#xff0c;且分布范围广&…

在 Mermaid 流程图里“驯服”quot;的魔法指南!!!

&#x1f409; 在 Mermaid 流程图里“驯服”"的魔法指南 在使用 Mermaid 画流程图时&#xff0c;是不是经常遇到想秀一波 &quot; 却被它“反杀”的情况&#xff1f;&#x1f3af; 今天就来教大家如何在这头代码野兽的嘴里&#xff0c;抢回我们的双引号实体编码&#…

SQL语句---DDL

文章目录 1、SQL语句2、DDL2.1 数据库的操作显示当前的数据库创建数据库指定编码删除数据库切换当前数据库 2.2 数据表的操作显示表创建表显示表结构修改表添加新的字段删除原有字段 修改原有字段删除数据表 2.3 Mysql数据库中常用的数据类型 1、SQL语句 结构化查询语句&#…

界面控件Telerik和Kendo UI 2025 Q1亮点——AI集成与数据可视化

Telerik DevCraft包含一个完整的产品栈来构建您下一个Web、移动和桌面应用程序。它使用HTML和每个.NET平台的UI库&#xff0c;加快开发速度。Telerik DevCraft提供完整的工具箱&#xff0c;用于构建现代和面向未来的业务应用程序&#xff0c;目前提供UI for ASP.NET MVC、Kendo…

信源的分类及数学模型

信源的分类及数学模型 按照信源发出的时间和消息分布分为离散信源和连续信源 按照信源发出符号之间的关系分为无记忆信源和有记忆信源 单符号离散信源&#xff08;一维离散信源&#xff09; 信源输出的消息数有限或可数&#xff0c;且每次只输出符号集的一个消息 样本空间&…

Flask项目部署:Flask + uWSGI + Nginx

目录 1,网络架构 2,环境安装 2.1,安装yum:Shell软件包管理器 2.2 安装python 2.3 安装uWSGI 2.4 安装Flask 3,上传工程包到服务器,打包Flask项目 4,创建和配置 uwsgi 配置文件 uwsgi.ini 4.1配置文件 4.2配置文件注释详解 5,启动服务 6,安装nginx 7,nginx配置 8,…

05-SpringBoot3入门-整合SpringMVC(配置静态资源、拦截器)

1、说明 在01-SpringBoot3入门-第一个项目-CSDN博客中&#xff0c;其实就已经整合了SpringMVC。下面讲解怎么配置静态资源和拦截器 2、配置静态资源 命名&#xff1a;static&#xff08;文件夹&#xff09; 位置&#xff1a;src/main/resources 编写一个html文件 访问 http:/…

外包干了一个月,技术明显进步。。。。。

先说一下自己的情况&#xff0c;本科生&#xff0c;19年通过校招进入南京某软件公司&#xff0c;干了接近2年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了2年的功能测试&…