【Linux】详解IPC:共享内存

目录

IPC

共享内存

1.理解

2.运用

1. 创建 ipc - shmget

2. 创建 key - ftok

⭕shmid vs key

3. 连接 - shmat

4. 脱离 - shmdt

5. 控制/删除 - shmctl

总结

代码示例

3.实验

comm.hpp

processa.cc

processb.cc

log.hpp

makefile

测试

4.思考


IPC

进程间通信 IPC有多种实现方式,包括但不限于:

  1. 管道(Pipes):包括匿名管道和命名管道(FIFOs)。匿名管道只能在具有亲缘关系的进程之间使用,而命名管道可以用于任意进程间的通信。借助文件
  2. 共享内存(Shared Memory):允许两个或多个进程访问同一块内存区域,从而实现快速的数据交换。借助物理内存映射
  3. 消息队列(Message Queues):允许进程之间通过消息队列传递数据,消息可以是任意类型的数据,包括文本和二进制数据。借助物理内存映射
  4. 信号量(Semaphores):用于进程间的同步和互斥访问共享资源。信号量可以是二进制信号量(用于互斥访问)或计数信号量(用于资源的计数控制)。
  5. 套接字(Sockets):通常用于网络通信,但在某些操作系统中也用于进程间通信。
  6. 文件映射(Memory Mapped Files):允许进程通过内存地址来访问文件,从而实现进程间数据的共享。

每种IPC机制都有其优缺点,选择合适的IPC机制取决于具体的应用需求和场景。

本篇文章来学习systemv 共享内存:

  1. 系统调用接口
  2. 使用共享内存
  3. 了解它的属性

共享内存

1.理解

进程间通信的本质是:先不同的进程,看到同一份资源

共享内存区是最快的IPC形式

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

注意:共享内存没有进行访问控制(同步与互斥)

如果要释放共享内存:1. 去关联 2. 释放共享内存

上面的操作都是进程 直接 做吗?不是。直接由操作系统来做

需求方 --系统调用--> 执行方

操作系统如何管理共享内存呢?

先描述,再组织内核结构体描述共享内存

你怎么保证让不同的进程看到同一共享内存呢?你怎么知道这个共享内存存在还是不存在呢?

设置了标识 key

2.运用

1. 创建 ipc - shmget

man shmget

相当于传的就是:地址,大小,操作

功能:
创建一个新的共享内存段,或者获取一个已存在的共享内存段。

原型:

int shmget(key_t key, size_t size, int shmflg);

参数:

  • key:共享内存段的标识符。用于命名共享内存段,在服务器和客户端之间共享。
  • size:共享内存段的大小,建议是页大小(一般是4096字节)的整数倍。
  • shmflg权限标志和控制标志,可以组合使用:(如何实现操作?)
    • IPC_CREAT如果共享内存段不存在,则创建它;如果存在,则返回其标识符。
    • IPC_CREAT | IPC_EXCL如果共享内存段不存在则创建它;如果已存在,则返回错误。
    • 权限标志(如文件权限)给出访问权限(如 0666 表示用户、组、其他都可读写)。

返回值:
成功返回一个非负整数,即共享内存段的标识码(shmid);失败返回-1。

测试:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>int main() {key_t key = 1234; // 任意选定一个keyint shmid;// 创建共享内存段shmid = shmget(key, 4096, 0644 | IPC_CREAT);if (shmid == -1) {perror("shmget failed");exit(1);}printf("共享内存创建成功,ID: %d\n", shmid);return 0;
}

2. 创建 key - ftok

man ftok

功能:
用文件路径和项目ID创建一个System V IPC key。

原型:

key_t ftok(const char *pathname, int proj_id);

参数:

  • pathname路径名,指向一个存在且可访问的文件。
  • proj_id项目ID,通常为小整数。

返回值:
成功返回生成的 key;失败返回 -1。

测试:

#include <stdio.h>
#include <sys/ipc.h>
#include <stdlib.h>int main() {key_t key;// 使用 ftok 生成一个唯一的keykey = ftok("shmfile", 'R');if (key == -1) {perror("ftok failed");exit(1);}printf("ftok 生成的key: %d\n", key);return 0;
}

🏷️谈谈 key:

  1. key 是一个数字。这个数字是几,不重要。关键在于它必须在内核中具有唯一性,能够让不同的进程进行唯一性标识
  2. 第一个进程可以通过 key 创建共享内存,第二个之后的进程,只要拿着同一个 key 就可以和第一个共享内存
  3. 对于一个已经创建好的共享内存,key 在哪?key 在共享内存的描述对象中!(保存信物)
  4. key--类似(理念)--路径--唯一

第一次创建的时候,必须有一个 key 了. 怎么有?

  • ftok 是一套算法,pathname 和 proj_id 进行了数值计算即可,由用户自己指定!

key 为什么要用户来生成?自己设完之后还有可能出现冲突

  • 用户约定的,原理定义时提到过(命名管道:同路径下同一个文件名=路径+文件名),借用 ftok 并不会冲突

共享内存被删除后,则其他线程直接无法通信?

  • 错误共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除
⭕shmid vs key
  • key 保证了操作系统内的唯一性,shmid 只在你的进程内,用来表示资源的唯一性
  • 只有 shmget 时候用key,大部分情况用户访问共享内存,都用的是shmid

3. 连接 - shmat

功能:
将共享内存段连接到进程地址空间。

原型:

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

参数:

  • shmid:共享内存段标识符。
  • shmaddr:连接地址,若为NULL,系统自动选择地址;若为非NULL,按提供的地址连接。
  • shmflg
    • 0:默认连接方式。
    • SHM_RDONLY:只读连接。
    • SHM_RND:将地址向下取整为某个整数倍。

返回值:
成功返回指向共享内存段的指针;失败返回 -1。

存取返回值,调用实现写入

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>int main() {key_t key = 1234;int shmid;char *data;// 获取共享内存段shmid = shmget(key, 4096, 0644 | IPC_CREAT);if (shmid == -1) {perror("shmget failed");exit(1);}// 将共享内存段连接到当前进程data = (char *)shmat(shmid, NULL, 0);if (data == -1)) {perror("shmat failed");exit(1);}// 写入数据strncpy(data, "Hello, Shared Memory!", 4096);printf("数据已写入共享内存: %s\n", data);return 0;
}

4. 脱离 - shmdt

功能:
将共享内存段与当前进程脱离。

原型:

int shmdt(const void *shmaddr);

参数:

  • shmaddr:由 shmat 返回的指向共享内存的指针。

返回值:
成功返回0;失败返回-1。

经典格式:获取,修改,查看共享内存

5. 控制/删除 - shmctl

功能:
控制共享内存段。

原型:

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

参数:

  • shmid:共享内存段标识符,由 shmget 返回。内核当中获取共享内存的属性
  • cmd
    • IPC_STAT:获取共享内存段的当前关联值。
    • IPC_SET:设置共享内存段的当前关联值(需要足够权限)。
    • IPC_RMID:删除共享内存段。
  • buf:指向 shmid_ds 结构的指针。struct shmid_ds buf;创建一下

返回值:
成功返回0;失败返回-1。

总结

  • ftok 测试代码生成一个唯一的key。
  • shmget 测试代码创建一个共享内存段
  • shmat 测试代码将共享内存段连接到当前进程并写入数据。
  • shmdt 测试代码将共享内存段与当前进程脱离
  • shmctl 测试代码获取共享内存段的状态,并最终删除它

代码示例

下面是一个简单的示例展示如何创建和使用共享内存:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <string.h>
#define SHM_SIZE 4096
int main()
{key_t key;int shmid;char* data;struct shmid_ds buf;//ftokprintf("make key\n");key=ftok("/home/lvy",'R');if(key==-1){perror("ftok failed");exit(1);}printf("ftok 生成的 key:%d\n",key);//creat. shmgetshmid=shmget(key,SHM_SIZE,0644 | IPC_CREAT);if(shmid==-1){perror("shmget failed");exit(1);}printf("shmget,ID:%d\n",shmid);//shmatdata=(char*)shmat(shmid,NULL,0);if (data == (char *)(-1)) {perror("shmat failed");exit(1);}printf("共享内存段连接成功。\n");//writestrncpy(data, "Hello, Shared Memory!", SHM_SIZE);printf("数据已写入共享内存: %s\n", data);//shmdt// 将共享内存段与当前进程脱离printf("将共享内存段与当前进程脱离...\n");if (shmdt(data) == -1) {perror("shmdt failed");exit(1);}printf("共享内存已脱离。\n");// shmctlprintf("获取共享内存段的状态...\n");if(shmctl(shmid,IPC_STAT,&buf)==-1){perror("shmctl (IPC_STAT) failed");exit(1);}//打印结构中内容printf("共享内存大小: %zu\n", buf.shm_segsz);printf("最后访问时间: %ld\n", buf.shm_atime);printf("最后分离时间: %ld\n", buf.shm_dtime);//shmctlprintf("删除共享内存段...\n");if (shmctl(shmid, IPC_RMID, NULL) == -1) {perror("shmctl (IPC_RMID) failed");exit(1);}printf("共享内存段已删除。\n");return 0;
}

展示了如何使用 shmgetshmat、和 shmdt 来创建、连接、和脱离共享内存段,以及使用 ftok 生成唯一的 key。

3.实验

tip: 之前讲过堆栈空间的优劣,在这共享内存也会多申请的一部分空间叫 cookie

上面有没有通信呢?没有

下面来进行通信,直接用。一旦有了共享内存,挂接到自己的地址空间中,你直接把他当成你的内存空间来用即可!不需要调用系统调用

我们来尝试在小项目中使用共享内存,思路:5 步操作存储 shmaddr(=shmat return) 后,对共享地址 read 和 write~

comm.hpp

#ifndef __COMM_HPP__
#define __COMM_HPP__#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>#include "log.hpp"using namespace std;
Log log;
// 共享内存的大小一般建议是4096的整数倍
// 4097,实际上操作系统给你的是4096*2的大小
const int size = 4096; 
const string pathname="/home/lvy";
const int proj_id = 0x6666;
key_t GetKey()
{key_t k=ftok(pathname.c_str(),proj_id);if(k<0){log(Fatal, "ftok error: %s", strerror(errno));exit(1);}log(Info, "ftok success, key is : 0x%x", k);return k;
}int GetShareMemHelper(int flag)
{key_t k = GetKey();int shmid = shmget(k, size, flag);if(shmid < 0){log(Fatal, "create share memory error: %s", strerror(errno));exit(2);}log(Info, "create share memory success, shmid: %d", shmid);return shmid;
}int CreateShm()
{return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}int GetShm()
{return GetShareMemHelper(IPC_CREAT); 
}#define FIFO_FILE "./myfifo"
#define MODE 0664enum
{FIFO_CREATE_ERR = 1,FIFO_DELETE_ERR,FIFO_OPEN_ERR
};class Init
{
public:Init(){// 创建管道int n = mkfifo(FIFO_FILE, MODE);if (n == -1){perror("mkfifo");exit(FIFO_CREATE_ERR);}}~Init(){int m = unlink(FIFO_FILE);if (m == -1){perror("unlink");exit(FIFO_DELETE_ERR);}}
};
#endif

processa.cc

tip: 共享内存的大小一般建议是 4096 的整数倍

4097,实际上操作系统给的是 4096*2 的大小,但是多的并不能用

#include "comm.hpp"extern Log log;int main()
{Init init;int shmid = CreateShm();char *shmaddr = (char*)shmat(shmid, nullptr, 0);// ipc code 在这里!!// 一旦有人把数据写入到共享内存,其实我们立马能看到了!!// 不需要经过系统调用,直接就能看到数据了!int fd = open(FIFO_FILE, O_RDONLY); // 等待写入方打开之后,自己才会打开文件,向后执行, open 阻塞了!if (fd < 0){log(Fatal, "error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);}struct shmid_ds shmds;while(true){char c;ssize_t s = read(fd, &c, 1);if(s == 0) break;else if(s < 0) break;cout << "client say@ " << shmaddr << endl; //直接访问共享内存sleep(1);shmctl(shmid, IPC_STAT, &shmds);cout << "shm size: " << shmds.shm_segsz << endl;cout << "shm nattch: " << shmds.shm_nattch << endl;printf("shm key: 0x%x\n",  shmds.shm_perm.__key);cout << "shm mode: " << shmds.shm_perm.mode << endl;}shmdt(shmaddr);shmctl(shmid, IPC_RMID, nullptr);close(fd);return 0;
}

processb.cc

#include "comm.hpp"int main()
{int shmid = GetShm();char *shmaddr = (char*)shmat(shmid, nullptr, 0);int fd = open(FIFO_FILE, O_WRONLY); // 等待写入方打开之后,自己才会打开文件,向后执行, open 阻塞了!if (fd < 0){log(Fatal, "error string: %s, error code: %d", strerror(errno), errno);exit(FIFO_OPEN_ERR);}// 一旦有了共享内存,挂接到自己的地址空间中,你直接把他当成你的内存空间来用即可!// 不需要调用系统调用// ipc codewhile(true){cout << "Please Enter@ ";fgets(shmaddr, 4096, stdin);write(fd, "c", 1); // 通知对方}shmdt(shmaddr);close(fd);return 0;
}

log.hpp

调用之前写好了的

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen;path = "./log/";}void Enable(int method){printMethod = method;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void logmessage(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);// 打印日志printLog(level, logtxt);}void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt;break;case Onefile:printOneFile(LogFile, logtxt);break;case Classfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname;int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // "log.txt"if (fd < 0) {perror("open");return;}write(fd, logtxt.c_str(), logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt);}~Log() {}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);// 打印日志printLog(level, logtxt);}private:int printMethod;std::string path;
};int main() {Log logger;// 设置日志输出方法logger.Enable(Screen);logger.logmessage(Info, "This is an information message.");logger(Warning, "This is a warning message.");// 切换到单文件输出方法logger.Enable(Onefile);logger(Debug, "This is a debug message written to a file.");// 切换到按级别分类输出方法logger.Enable(Classfile);logger(Fatal, "This is a fatal error message written to separate class files.");return 0;
}

makefile

.PHONY:all
all:processa processbprocessa:processa.ccg++ -o $@ $^ -g -std=c++11
processb:processb.ccg++ -o $@ $^ -g -std=c++11
.PHONY:clean
clean:rm -f processa processb

测试

4.思考

共享内存的特性--扩展代码

  1. 共享内存没有同步互斥之类的保护机制,因为双方不知道对方
  2. 共享内存是所有进程间通信中,速度最快的!--拷贝少
  3. 共享内存内部的数据,由用户自己维护

想看看共享内存的属性!获取内核属性 man shmctl

对于没有同步机制,我们可像上面实验一样,借用管道来通知对方,来实现对方不输入就进行的等待

ipc 指令的使用

ipcs -m shmid #实现查看ipc资源
ipcrm   #删除共享内存

复习:

  1. cp 的高级用法

cp ../../XXX . #来实现拷贝到当前路径下

  1. 位段(Bitfields)和大小端(Big-Endian vs Little-Endian)以及处理器架构(如x86和x64)是有关联的,但它们之间的关系并不是直接的。

位段(Bitfields):位段是C语言中用于在结构体或联合中指定数据成员的位大小的一种方式。它们允许您指定哪些位应该用于存储特定的数据,从而节省内存空间。位段的布局取决于编译器和目标平台的字节序。

字节序(Byte Ordering):字节序描述了多字节数据类型(如整数、浮点数等)在内存中的存储方式。有两种常见的字节序:

  • 大端字节序(Big-Endian):在这种字节序中,最高有效字节(Most Significant Byte)位于内存地址的最小位置。
  • 小端字节序(Little-Endian):在这种字节序中,最低有效字节(Least Significant Byte)位于内存地址的最小位置。

处理器架构:不同的处理器架构默认支持不同的字节序。例如,x86架构通常使用小端字节序,而ARM架构则通常使用大端字节序。x64是x86架构的64位扩展,它也支持小端字节序。

  1. 缺页中断和报错

在计算机操作系统中,缺页中断(Page Fault)和越界 是两种不同的概念,它们在不同的上下文中发生。

  1. 缺页中断
    • 定义:缺页中断是在虚拟内存系统中发生的一种异常,当程序试图访问一个不存在的内存页时触发。
    • 原因:可能是因为程序试图访问一个未分配或已释放的内存页(如何识别出?这些信息os结构体都有存),或者是因为程序试图访问一个受保护的内存区域。
    • 处理:操作系统会处理缺页中断,它会检查页面是否已经在物理内存中,如果是,则重新加载页面;如果不在,则会从磁盘加载页面到物理内存中,并更新内存管理表(如页表)。
  1. 越界(Out of Bounds)
    • 定义:越界是指程序尝试访问内存地址范围之外的内存区域。
    • 原因:越界通常是由于程序错误地处理了内存地址,例如,访问数组的越界元素,或者访问了一个不存在的内存地址。
    • 处理:越界通常由程序本身处理,例如,在数组访问中,如果访问的索引超出了数组的大小,程序会触发一个越界错误。越界错误不会由操作系统处理,而是由程序的错误处理机制处理。
      区别
  • 触发条件:缺页中断是由于尝试访问不存在的内存页,而越界是由于尝试访问内存地址范围之外的内存区域。
  • 处理机制:缺页中断由操作系统处理,而越界错误由程序本身处理。
  • 后果:缺页中断可能导致程序暂停或重新启动,而越界错误可能导致程序崩溃或执行不正确。
    在实际编程中,程序员需要确保处理越界错误,以防止程序崩溃或产生不正确的结果。操作系统则负责处理缺页中断,以保持系统的稳定性和性能。

不存在的内存页和内存地址之外的内存区是怎么区分的?

  • 映射关系:每个内存地址都映射到一个内存页。当程序访问一个内存地址时,操作系统会通过页表查找对应的内存页。
  • 访问单位:在编程中,内存地址是操作的单位,但在内存管理中,内存页是分配和管理的单位。
  • 虚拟内存与物理内存的桥梁:内存页在虚拟内存和物理内存之间架起了一座桥梁,它允许操作系统将虚拟内存的地址空间映射到物理内存或磁盘上的交换文件。

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

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

相关文章

vue3引入模块报错:无法找到模块“xxx”的声明文件

使用vue3ts导入vue文件的时候&#xff0c;报错&#xff1a;找不到模块“./XXX.vue”或其相应的类型声明 这是由于&#xff1a;Vue 文件并不是标准的 JavaScript 模块&#xff0c;因此 TypeScript 需要通过这种声明方式来理解和处理这些文件 我是使用vite创建的项目&#xff0…

【生信入门linux篇】如何安装一个linux虚拟机用于学习

一.虚拟机 虚拟机&#xff08;Virtual Machine&#xff0c;简称VM&#xff09;是一种软件实现的计算机系统&#xff0c;它能够在物理计算机上模拟出多个独立的计算机环境。每个虚拟机都可以运行自己的操作系统和应用程序&#xff0c;就像在独立的物理计算机上一样。虚拟机技术…

小试牛刀-区块链Solana多签账户

目录 1.什么是多签账户 2.多签账户的特点 2.1 多个签名者 2.2 最小签名要求 2.3 常见应用场景 3.多签账户实现 3.1 账户的创建 3.1.1 创建新账户 3.1.2 获取创建和初始账户事务 3.1.3 账户的签名 3.2 代币转移操作 Welcome to Code Blocks blog 本篇文章主要介绍了 …

LeetCode_sql_day16(601.体育馆的人流量)

描述&#xff1a;601. 体育馆的人流量 - 力扣&#xff08;LeetCode&#xff09; 编写解决方案找出每行的人数大于或等于 100 且 id 连续的三行或更多行记录。 返回按 visit_date 升序排列 的结果表。 输入Stadium表: ----------------------------- | id | visit_date | peop…

电子电气架构 --- 车辆模式管理

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

CUDA-MODE 第一课: 如何在 PyTorch 中 profile CUDA kernels

我的课程笔记&#xff0c;欢迎关注&#xff1a;https://github.com/BBuf/how-to-optim-algorithm-in-cuda/tree/master/cuda-mode 第一课: 如何在 PyTorch 中 profile CUDA kernels 这里是课程规划&#xff0c;有三位讲师 Andreas, Thomas, Mark&#xff0c;然后大概2周出一个 …

Elasticsearch:用例、架构和 6 个最佳实践

1. 什么是 Elasticsearch&#xff1f; Elasticsearch 是一个开源分布式搜索和分析引擎&#xff0c;专为处理大量数据而设计。它建立在 Apache Lucene 之上&#xff0c;并由Elastic 支持。Elasticsearch 用于近乎实时地存储、搜索和分析结构化和非结构化数据。 Elasticsearch 的…

4.3.2 C++ 平面拟合的实现

4.3.2 C 平面拟合的实现 参考教程&#xff1a; gaoxiang12/slam_in_autonomous_driving: 《自动驾驶中的SLAM技术》对应开源代码 (github.com) Eigen打印输出_打印eigen矩阵-CSDN博客 1. 编写 Plane fitting 1.1 创建文件夹 通过终端创建一个名为Plane_fitting的文件夹以保…

文件操作与IO(下)

✨个人主页&#xff1a; 不漫游-CSDN博客 目录 前言 流对象 InputStream OutputStream 运用 在控制台进行输入并写入文件 进行普通文件的复制 前言 之前的文章文件操作与IO&#xff08;上&#xff09;已经介绍了文件系统的相关操作&#xff0c;这次的主角是文件内容的相关…

SpringBoot 框架学习笔记(七):Thymeleaf、拦截器 和 文件上传实现(解决了文件重名 和 按日期分目录存放问题)

1 Thymeleaf 1.1 基本介绍 &#xff08;1&#xff09;官方文档&#xff1a;Tutorial: Using Thymeleaf &#xff08;2&#xff09;Thymeleaf 是什么 Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎&#xff0c;可完全替代 JSPThymeleaf 是一个 java 类库&#xf…

.net core webapi 自定义异常过滤器

1.定义统一返回格式 namespace webapi;/// <summary> /// 统一数据响应格式 /// </summary> public class Results<T> {/// <summary>/// 自定义的响应码&#xff0c;可以和http响应码一致&#xff0c;也可以不一致/// </summary>public int Co…

vue 打包时候的分包

export default defineConfig({plugins: [vue()],resolve: {alias: {: fileURLToPath(new URL(./src/, import.meta.url))}},// 分包&#xff0c;node_modules中的单独打包成名字为vendor的js文件build: {rollupOptions: {manualChunks(id) {if (id.includes(node_modules)) {r…

EF8 学习过程中的问题和解决方案

一、varchar类型字段如果为null 无法使用contains来判断是否包含字符串 1. 有问题的代码&#xff1a; contractList _dbcontext.contractHeads.Where(u > u.code.Contains(queryStr) || u.name.Contains(queryStr) || u.companyName.Contains(queryStr) || u.customerNa…

uniapp开启数据压缩的坑-SpringBoot-gzip

1、服务器配置 服务端开启的数据压缩配置 server:port: ${port:8881}servlet:# 应用上下文路径context-path: /orderserverundertow:threads:io: 4worker: 500buffer-size: 2048# 开启Gzip压缩&#xff0c;compression:# 开启压缩enabled: true# 对json格式内容进行压缩mime-…

KCTF 闯关游戏:1 ~ 7 关

前言 看雪CTF平台是一个专注于网络安全技术竞赛的在线平台&#xff0c;它提供了一个供网络安全爱好者和技术专家进行技术交流、学习和竞技的环境。CTF&#xff08;Capture The Flag&#xff0c;夺旗赛&#xff09;是网络安全领域内的一种流行竞赛形式&#xff0c;起源于1996年…

嵌入式全栈开发学习笔记---数据结构(排序算法)

目录 排序的分类 稳定排序与不稳定排序 内部排序和外部排序 算法的复杂性 常见的排序算法 直接插入排序 希尔排序 快速排序 简单选择排序 堆排序 归并排序 基数排序 常见的排序总结 到目前为止&#xff0c;数据结构的线性结构和树状结构就都讲完了&#xff0c;本节…

使用 MongoDB 构建 AI:Flagler Health 的 AI 旅程如何彻底改变患者护理

Flagler Health 致力于为慢性病患者提供支持&#xff0c;为其匹配合适的医生以提供合适的护理。 通常&#xff0c;身患严重病痛的患者面临的选择有限&#xff0c;他们往往需要长期服用阿片类药物&#xff0c;或寻求成本高昂的侵入性外科手术干预。遗憾的是&#xff0c;后一种方…

SQL语句创建数据库(增删查改)

SQL语句 一.数据库的基础1.1 什么是数据库1.2 基本使用1.2.1 连接服务器1.2.2 使用案例 1.2 SQL分类 二.库的操作2.1 创建数据库2.2 创建数据库示例2.3 字符集和校验规则2.3.1 查看系统默认字符集以及校验规则2.3.2查看数据库支持的字符集2.3.3查看数据库支持的字符集校验规则2…

Android系统Android.bp文件详解

文章目录 1. 基本语法结构2. 常见模块类型3. 模块属性常见属性包括&#xff1a; 4. 具体示例5. 高级功能5.1. 条件编译5.2. 变量定义与使用5.3. 模块继承 6. 总结 Android.bp 是 Android 构建系统&#xff08;Android Build System&#xff09;中的配置文件&#xff0c;用于描述…

go之命令行工具urfave-cli

一、urfave/cli urfave/cli 是一个声明性的、简单、快速且有趣的包&#xff0c;用于用 Go 构建命令行工具。 二、快速使用 2.1 引入依赖 go get github.com/urfave/cli/v2 2.2 demo package mainimport ("fmt""log""os""github.com/ur…