【Linux】进程间通信(匿名管道和命名管道通信、共享内存通信)
文章目录
- 1、进程间通信
- 1.1 进程的通信
- 1.2 如何让进程间通信?
- 1.3 进程间通信的本质
- 2、管道通信
- 2.1 匿名管道
- 2.2 匿名管道通信
- 2.3 命名管道
- 2.4 命名管道的通信
- 3、SystemV中的共享内存通信
- 3.1 共享内存
- 3.2 共享内存的通信
- 3.3 共享内存的缺点以及数据保护
- 3.4 共享内存的优点
- 3.5 信号量以及与共享内存有关的概念
1、进程间通信
1.1 进程的通信
在一些应用场景下,一定要求着进程之间进行通信,而通信有以下这些:
数据传输:进程间将数据传递给彼此。
资源共享:多个进程共享同一份资源。
通知事件:一个进程向另一个进程发送信息,通知它发生了某种事件。(比如父进程向子进程传递信号,让子进程退出。)
进程控制:一个进程控制另一个进程。(比如debug)
比如在linux中通过命令,ps ajx | grep hello,两个命令分别对应着两个进程,通过管道进行进程通信。(也就是说,为了完成某些业务,我们是需要多进程进行协同的)
1.2 如何让进程间通信?
Linux的主流通信标准有以下:
- POSIX (让通信过程可以跨主机)
- System V (聚焦在本地通信这种方式,这种标准有着三种通信方式:共享内存、消息队列、信号量(这里只谈共享内存,因为SystemV标准不常用)。)
第二套通信方案
管道是最基本的通信方案,管道基于文件系统。
管道有着匿名管道和命名管道。
1.3 进程间通信的本质
首先,进程是具有独立性的,所以如果要让进程进行通信肯定是不能进程与进程直接联系的。
那么就需要以下:
1、OS需要直接或间接的给通信双方的进程提供"共享空间"。
2、要让通信的进程都看到同一份共享空间。
(而不同的通信种类,本质就是这边共享空间的不同,区别在OS哪个模块提供的。比如通过文件系统提供的资源就是管道。)
综上,我们需要做的就是:
1、需要让进程看到同一份资源。(这个是主要的)
2、通信
2、管道通信
2.1 匿名管道
理解管道通信:
在讲文件的时候说到过,一个进程的PCB中有一个指针,指向文件描述符表,被打开的文件被其中文件描述符下标指向。
如果有一个父进程打开了一个文件,那么文件描述符表中对应就会指向这个文件对应的结构体对象,通过这个文件结构体中有着文件的操作方法以及一个内核缓冲区。
父进程创建子进程后,子进程拷贝父进程,对应文件描述符表中对应也会指向这个文件对应结构体对象。
综上,两个进程指向同一文件资源,这个文件就是一个"共享空间",也是一个管道。

另一个问题,普通文件传输数据是需要经过磁盘的,如果进程间通过文件传输数据经过磁盘,那么就是内存与外设的交互,效率很低。那么OS能不能只创建一个文件结构体对象,只在内存中交互呢?
答案是肯定的,这个文件不会真的存在,OS会申请一个文件结构体对象。
同时,这个文件是一个内存级文件没有名字也没有对应inode,所以称之为匿名管道。
管道只能单向通信
首先创建管道将读写端返回给进程。

为了让子进程也看到读写端,父进程fork创建子进程。

因为管道需要单向通信,所以对应读端要关闭写端,对应写端要关闭读端。

综上,匿名管道就是一个父进程通过读和写的方式打开一个内存级文件后,通过创建子进程,关闭各自读写端,所构成的通信信道,这个信道基于文件,是一个内存级文件,所以称为匿名管道。
2.2 匿名管道通信
pipe接口:



pipe系统接口,可以创建一个单向管道用于进程间通信,pipefd[2]参数是一个返回型参数,返回两个文件描述符,一个读端fd[0]和一个写端fd[1],指向管道文件的两边。
知道了如何创建管道后,下面需要让两个进程都看到这个"共享空间" 因为管道返回的是文件描述符,所以通过文件描述符就可以访问管道。
综上,思路就很简单:
1. 创建管道,接收管道文件描述符。
2. 父进程创建子进程,进程会获取和父进程一样的文件描述符。
3. 可以让父进程读,子进程写,然后父进程需要关闭写端,子进程关闭读端。
4. 通过文件操作进行通信。
测试代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;
int main()
{//第一步:创建管道文件,打开读写端int fds[2];int n = pipe(fds);assert(n == 0);//第二步:forkpid_t id = fork();assert(id >= 0);if(id == 0){//子进程写入close(fds[0]);//子进程的通信代码int cnt = 0;while(true){char buffer[1024];snprintf(buffer, sizeof buffer, "child->parent say: %s[%d][%d]","我是子进程,我正在发消息", cnt++, getpid());//一直往管道输入端写,写满也会阻塞write(fds[1], buffer, strlen(buffer));cout << "count: " << cnt << endl;sleep(2); //每隔2s, 写一次 }close(fds[1]); //写完 子进程关闭写端cout << "子进程关闭自己写端" << endl;sleep(5);exit(0);}//父进程读取close(fds[1]);//父进程的通信代码while(true){char buffer[1024];//如果管道中没有了数据,读端在读,默认会直接阻塞当前正在读取的进程!ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);if(s > 0){buffer[s] = 0;cout << "Get Message# " << buffer << "| my pid:"<< getpid() << endl;//break;}else if(s == 0){//读到文件末尾cout << "read: " << s << endl;break;}//父进程没有sleep}close(fds[0]);int status = 0;n = waitpid(id, &status, 0);cout << "waitpid:" << n << " sig:" << (status & 0x7f) << endl;assert(n == id);return 0;
}
匿名管道读写特性:
之前,主要做的是让进程间看到同一份资源,而通信的过程是可以任意想象的,但是大概就以下几个情况。
1、写的快,读的慢。(那么每次读取的时候,就会从缓冲区读一大堆)
2、写的慢,读的快。
3、写后关闭写端,读端继续。(当读完后,子进程退出,父进程需要回收子进程)
4、一直写,读端关闭。(这种情况的出现,让数据没有意义)
前三个很好理解,如果第四种情况出现,写端一直写,读端却不读,那么写的数据就没有任何意义,造成了空间浪费。
当写端还在,读端关闭这个条件发生时,操作系统为了避免空间浪费,就会给父进程发送SIGPIPE(13)信号,关闭父进程。
管道特点:
- 管道的生命周期随进程。
- 管道可以用来进行具有"血缘关系"的进程间进行通信,常用于父子间通信。
- 管道是面向字节流的。
- 管道只允许单向通信 半双工的。(任何时候都是一方向另一方发送数据)
- 互斥和同步机制。(共享资源保护机制)(这个可以和后面共享内存进行比较)
2.3 命名管道
前面的匿名管道,是有"血缘关系"的进程间进行通信的。
如果想让毫不相关的两个进程通过管道通信呢?命名管道就能解决这个问题。
命名管道相较于匿名管道就是在磁盘有一个特殊的管道文件,这个文件在打开后也拥有着自己的struct file。
两个不相关的进程是如何看到同一份资源的呢?
可以让不同进程,通过文件名(路径+文件名)打开。(匿名管道的结构体对象中的地址没有名称,命名管道的结构体对象中的地址拥有名称)
值得注意的是,进程传递到内核缓冲区的数据不会刷新到磁盘上。
2.4 命名管道的通信
函数调用mkfifo:


函数调用mkfifo用于创建命名管道,也是一个特殊的文件。
pathname: 表示创建文件的路径,如果只是一个文件名就和进程在同一路径。
mode: 表示创建的文件名拥有的初始权限。(前提设置umask(0),不然结果就是mode & ~umask)
创建成功返回0,失败返回-1。
函数调用unlink:

unlink在此用于删除管道文件,只需传管道路径,成功返回0,错误返回-1。
对应通信也很简单。
1、创建管道文件,进程1打开文件,向文件里写入。
2、进程2打开文件向文件里读取,读完后删除管道文件。
//comm.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <fcntl.h>#define NAMED_PIPE "mypipe"bool createFifo(const std::string& path)
{umask(0);int n = mkfifo(path.c_str(), 0666);if(n == 0){return true;}else{std::cout << "errno: " << errno << "err stirng:" << strerror(errno) << std::endl;return false;}
}void removeFifo(const std::string& path)
{int n = unlink(path.c_str());assert(n == 0);(void)n;
}#include "comm.hpp"int main()
{createFifo(NAMED_PIPE);int wfd = open(NAMED_PIPE, O_WRONLY, 0666);int cnt = 10;while(cnt--){char buffer[1024];std::cout << "client begin:" << std::endl;fgets(buffer, sizeof buffer, stdin);std::cout << "client end:" << std::endl;ssize_t w = write(wfd, buffer, strlen(buffer) - 1);std::cout << std::endl;}close(wfd);return 0;
}#include "comm.hpp"int main()
{//createFifo(NAMED_PIPE);int rfd = open(NAMED_PIPE, O_RDONLY, 0666);while(true){std::cout << "server begin:" << std::endl;char buffer[1024];ssize_t r = read(rfd, buffer, sizeof(buffer));if(r > 0){buffer[r] = 0;std::cout << buffer << std::endl;std::cout << "server end:" << std::endl;std::cout << std::endl;}else{std::cout << "read:" << r << std::endl;break;}}close(rfd);removeFifo(NAMED_PIPE);return 0;
}
3、SystemV中的共享内存通信
3.1 共享内存
共享内存的理解很简单。
用户通过接口,让OS在物理空间申请一块内存。
不同的进程将进程地址空间通过页表和这块内存进行映射。
这就做到了共享同一份空间。

下面通过接口,看进程是怎么和共享内存联系起来的。
认识接口
shmget:

shmget让OS在物理空间上申请一个共享内存
key是一个系统层面的标识符,用于标识共享内存唯一性。(这key是由一个函数生成)
size 表示要创建多大的共享内存。
shmflg 是一个二进制标识符,一般为IPC_CREAT 表示共享内存没有时创建,有时不创建;为IPC_CREAT | IPC_EXCL 表示没有时创建,有时会报错。
成功返回一个用户层面的标识符,用于标识共享内存唯一性,错误返回-1。
也正是因为这个返回值和文件描述符没有关联,使得SystemV标准作为自立的一套标准
ftok:

前面shmget所说的参数key,就是ftok的返回值,用于标识共享内存唯一性。
pathname和proj_id,可以是符合类型的任意值,ftok会根据自己算法将这两个参数转换成返回值key。
这也说明,如果多个进程的pathname和proj_id都用一样的话,就能通过返回值key访问同一个共享内存。
shmctl:
有了创建共享内存的函数就有可以释放共享内存的函数。

值得注意的是shmclt不仅可以释放共享内存,也有着访问共享内存的属性作用,这里只说怎么释放内存。
shmid :这个值是shmget的返回值,指的是用户层面上标识共享内存唯一性的。
cmd :是个二进制标识符,IPC_RMID 代表释放共享内存。
buf 是用于访问共享内存属性的参数,这里可以置空。
成功返回0,失败返回-1.
有了shmget和ftok,就可以创建共享内存了,接下来就需要考虑如何将进程和共享内存关联起来。
不过在此之前先再认识下共享内存:
首先前面说过,内存中一定会在一定时刻存在多个共享内存,多个共享内存就一定需要被操作系统管理,管理就一定需要结构化。
共享内存 = 物理空间块 + 自身属性。
而管理共享内存的结构体struct shm其中就保存了作为系统层面的key,用于标识唯一性。
3.2 共享内存的通信
共享内存的系统命令:
通过ipcs -m 可以查看当前开辟的共享内存
如果要通过命令释放共享内存,可以通过ipcrm -m 对应shmid
通过while :; do ipcs -m; sleep 2; done 也可以一直看到共享内存信息。
共享内存的通信:
首先进程和共享内存的联系,也是通过函数进行联系的。
shmat(attach 附加) 和 shmdt(detach 分离)

shmat 将内存段连接到进程地址空间
shmid 是shmget返回的用户层面上的唯一标识符
一般都是shmat(id, nullptr, 0),让它自动连接将当前进程联系到共享内存段。
返回值返回一个指针,指向共享内存的第一个内存段。如果失败返回(void*)-1
shmdt 取消进程与共享内存的关联。
只需要传一个shmat返回的地址就行,就能取消联系。
成功返回0,失败返回-1。
共享内存进行通信
//comm.hpp
#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <unistd.h>
#include <cassert>#define SHMSIZE 4026
#define PATHNAME "."
#define PROJ_ID 0x66key_t getKey(const char* path, int proj_id)
{key_t k = ftok(path, proj_id);if(k < 0){std::cout << "ftok:" << errno << ":" << strerror(errno) << std::endl;exit(-1);}return k;
}int getShmHelp(key_t k, int shmflg)
{int id = shmget(k, SHMSIZE, shmflg);if(id < 0){std::cout << "shmget: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}int createShm(key_t k)
{umask(0);return getShmHelp(k, IPC_CREAT | IPC_EXCL | 0600);
}int getShm(key_t k)
{return getShmHelp(k, IPC_CREAT);
}void* attachShm(int shmId)
{void* mem = shmat(shmId, nullptr, 0);if((long long)mem == -1L) //linux 64位{std::cout << "shmat: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}void detachShm(void* start)
{if(shmdt(start) == -1){std::cout << "shmdt: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}void delShm(int shmId)
{if(shmctl(shmId, IPC_RMID, nullptr) == -1){std::cout << "shmctl: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}///
//client
#include "comm.hpp"int main()
{//先获取key,用于唯一标识共享内存key_t key = getKey(PATHNAME, PROJ_ID);printf("key:0x%x\n", key);//用户创建共享内存int shmId = createShm(key);printf("shmId:%d\n", shmId);//进程与内存关联char* start = (char*)attachShm(shmId);printf("attach success, address start: %p\n", start);//进程通信const char* message = "hello server, 我是另一个进程,正在和你通信";pid_t id = getpid();int cnt = 1;int circnt = 5;while(circnt--){sleep(5);snprintf(start, SHMSIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);}//sleep(10);//删除前最好取消关联detachShm(start);//删除共享内存//delShm(shmId);return 0;
}///
//server
#include "comm.hpp"int main()
{//先获取key,用于唯一标识共享内存key_t key = getKey(PATHNAME, PROJ_ID);printf("key:0x%x\n", key);//用户创建共享内存int shmId = getShm(key);printf("shmId:%d\n", shmId);//进程与内存关联char* start = (char*)attachShm(shmId);printf("attach success, address start: %p\n", start);//进程通信while(true){struct shmid_ds sd;shmctl(shmId, IPC_STAT, &sd);printf("client say : %s, cpid[%d], key[0x%x]\n", start, sd.shm_cpid, sd.shm_perm.__key); sleep(1);}//sleep(9);//删除前最好取消关联detachShm(start);//删除共享内存delShm(shmId);return 0;
}
输出结果:

3.3 共享内存的缺点以及数据保护
根据上一节的输出结果,进行分析
首先client进程每五秒写到共享内存一次,而server每隔一秒向共享内存中读取。
这也说明共享内存方式通信是有缺点的:不给我进行同步和互斥操作的,对数据没有进行保护。
也就是不像管道那样,一份数据写完,读端再读。
对共享内存进行保护,需要写完,通知读端,再读。
没通知server的时候,让server进行阻塞状态。
通过在外层再设计一层命名管道,写端写入一个字符,如果读端收到那么就从共享内存中读取,没有收到就进入阻塞状态(read读不到数据)
#include <iostream>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <cassert>
#include <string>#define SHMSIZE 4026
#define PATHNAME "."
#define PROJ_ID 0x66
#define FIFOPATH "mypipe"void createFifo(const std::string& path)
{umask(0);int n = mkfifo(path.c_str(), 0666);if(n < 0){std::cout << "mkfifo:" << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}void removeFifo(const char* path)
{if(unlink(path) == -1){std::cout << "unlink:" << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}key_t getKey(const char* path, int proj_id)
{key_t k = ftok(path, proj_id);if(k < 0){std::cout << "ftok:" << errno << ":" << strerror(errno) << std::endl;exit(-1);}return k;
}int getShmHelp(key_t k, int shmflg)
{int id = shmget(k, SHMSIZE, shmflg);if(id < 0){std::cout << "shmget: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}int createShm(key_t k)
{umask(0);return getShmHelp(k, IPC_CREAT | IPC_EXCL | 0600);
}int getShm(key_t k)
{return getShmHelp(k, IPC_CREAT);
}void* attachShm(int shmId)
{void* mem = shmat(shmId, nullptr, 0);if((long long)mem == -1L) //linux 64位{std::cout << "shmat: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}void detachShm(void* start)
{if(shmdt(start) == -1){std::cout << "shmdt: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}void delShm(int shmId)
{if(shmctl(shmId, IPC_RMID, nullptr) == -1){std::cout << "shmctl: " << errno << ":" << strerror(errno) << std::endl;exit(-1);}
}///
//client
#include "comm.hpp"int main()
{//建立命名管道createFifo(FIFOPATH);//先获取key,用于唯一标识共享内存key_t key = getKey(PATHNAME, PROJ_ID);printf("key:0x%x\n", key);//用户创建共享内存int shmId = createShm(key);printf("shmId:%d\n", shmId);//进程与内存关联char* start = (char*)attachShm(shmId);printf("attach success, address start: %p\n", start);//进程通信const char* message = "hello server, 我是另一个进程,正在和你通信";pid_t id = getpid();//向命名管道中写入数据rint wfd = open(FIFOPATH, O_WRONLY, 0666);int cnt = 1;int circnt = 5;while(circnt--){sleep(5);char sig = 'r';ssize_t s = write(wfd, &sig, sizeof(sig));assert(s == sizeof(char));(void)s;snprintf(start, SHMSIZE, "%s[pid:%d][消息编号:%d]", message, id, cnt++);}//sleep(10);//删除前最好取消关联detachShm(start);close(wfd);//删除共享内存//delShm(shmId);return 0;
}//
//server
#include "comm.hpp"int main()
{//createFifo(FIFOPATH);//先获取key,用于唯一标识共享内存key_t key = getKey(PATHNAME, PROJ_ID);printf("key:0x%x\n", key);//用户创建共享内存int shmId = getShm(key);printf("shmId:%d\n", shmId);//进程与内存关联char* start = (char*)attachShm(shmId);printf("attach success, address start: %p\n", start);//进程通信//如果从命名管道中读到了r,就从共享内存中读取数据int rfd = open(FIFOPATH, O_RDONLY, 0666);while(true){char sig;ssize_t s = read(rfd, &sig, sizeof(sig));if(s == 0){printf("read:%d\n", s);break;}if(sig == 'r' && s > 0){struct shmid_ds sd;shmctl(shmId, IPC_STAT, &sd);printf("client say : %s, cpid[%d], key[0x%x]\n", start, sd.shm_cpid, sd.shm_perm.__key); }sleep(1);}//sleep(9);//删除前最好取消关联detachShm(start);//删除共享内存delShm(shmId);close(rfd);//删除命名管道removeFifo(FIFOPATH);return 0;
}

3.4 共享内存的优点
共享内存是所有通信方式中速度最快的。
因为其能大大降低数据的拷贝次数。
同样的数据通信,管道实现和共享内存实现。考虑键盘、显示器,共享内存会有2+2次拷贝。
只需要将数据从输入拷贝到共享内存,再从内存直接放到标准缓冲区,再到屏幕。

而管道通信,在进入管道多了要经过用户层面上的缓冲区(FILE),多了两次拷贝。

所以如果只考虑管道和共享内存通信传输大量的数据的话,共享内存能快不少。
3.5 信号量以及与共享内存有关的概念
什么是信号量?
信号量本身是一个计数器,通常用来表示公共资源中,资源数量多少的问题的。
公共资源:是可以被多个进程访问的资源。
1、值得注意的是公共资源是需要保护的,不然会出现数据不一致的问题。(而对于数据保护,提出了一些方法,但这些方法也会造成问题,所以问题没有解决,最后只是到了被接受的程度)
2、被保护起来的公共资源称为临界资源。
3、进程访问临界资源的代码,称为临界区,而其它的称为非临界区。
4、共享资源要么是一个整体,要么划分一个一个资源部分。
如何保护共享资源呢?
互斥与同步。
互斥也就是当一方访问时,另一方阻塞。
同步的情况是一种原子性,比如银行的两个账户各有1000元,一个账号向另一个账号转账200时,一方要扣200,另一方要加200,而如果转账失败,要保证各个账户金额不变。
信号量的作用
打个比方,比如电影院买票,一部电影每场次座位的数量就是一种信号量,在人们买票的时候都要先访问这个信号量。
也就是进程在访问公共资源前,都必须申请sem信号量。
相关文章:
【Linux】进程间通信(匿名管道和命名管道通信、共享内存通信)
文章目录1、进程间通信1.1 进程的通信1.2 如何让进程间通信?1.3 进程间通信的本质2、管道通信2.1 匿名管道2.2 匿名管道通信2.3 命名管道2.4 命名管道的通信3、SystemV中的共享内存通信3.1 共享内存3.2 共享内存的通信3.3 共享内存的缺点以及数据保护3.4 共享内存的…...
漏洞分析: WSO2 API Manager 任意文件上传、远程代码执行漏洞
漏洞描述 某些WSO2产品允许不受限制地上传文件,从而执行远程代码。以WSO2 API Manager 为例,它是一个完全开源的 API 管理平台。它支持API设计,API发布,生命周期管理,应用程序开发,API安全性,速…...
详解Android 13种 Drawable的使用方法
前言关于自定义View,相信大家都已经很熟悉了。今天,我想分享一下关于自定义View中的一部分,就是自定义Drawable。Drawable 是可绘制对象的一个抽象类,相对比View来说,它更加的纯粹,只用来处理绘制的相关工作…...
MakeFile教程
前言 当我们需要编译一个比较大的项目时,编译命令会变得越来越复杂,需要编译的文件越来越多。其 次就是项目中并不是每一次编译都需要把所有文件都重新编译,比如没有被修改过的文件则不需要重 新编译。工程管理器就帮助我们来优化这两个问题…...
Spring使用mongoDB步骤
1. 在Linux系统使用docker安装mongoDB 1.1. 安装 在docker运行的情况下,执行下述命令。 docker run \ -itd \ --name mongoDB \ -v mongoDB_db:/data/db \ -p 27017:27017 \ mongo:4.4 \ --auth执行docker ps后,出现下列行,即表示mongoDB安…...
【蓝牙mesh】access层(接入层)协议介绍
【蓝牙mesh】access层(接入层)协议介绍 Access层简介 Access层定义了应用层如何使用upper协议层的接口,它不仅定义了应用层的格式,还定义了应用数据在upper层的加密和解密。当收到下层的数据包时,它会检查数据的netke…...
【一天一门编程语言】JavaScript 语言程序设计极简教程
JavaScript 语言程序设计极简教程 用 markdown 格式输出答案。 不少于3000字。细分到2级目录。 一、JavaScript 简介 1.1 什么是 JavaScript JavaScript 是一种由Netscape的LiveScript发展而来的脚本语言,是一种动态类型、弱类型、基于原型的语言,内…...
CMake调试器出炉:调试你的CMake脚本
Visual Studio 开发团队一直和 Kitware 紧密合作,致力于开发一个用于调试 CMake 脚本的调试器。 我们将继续这个工作,以便开发人员社区可以通过添加新功能和对其他 DAP 功能的支持来共同改进它。 我们很高兴地宣布,CMake 调试器的预览版现在…...
题解 # 二维矩阵最大矩形问题#
题目: 小明有一张N*M的方格纸,且部分小方格中涂了颜色,部分小方格还是空白。 给出N (2<Ns30)和M(2sMs30)的值,及每个小方格的状态((被涂了颜色小方格用数字1表示,空白小方格用数字0表示); 请…...
奔四的路上,依旧倔强的相信未来
本文首发于2022年12月31日 原标题: 奔四的路上,依旧倔强的相信未来!–我的2022年终总结 读大学那几年,一直保持着写日记和做计划的习惯,还记得大学毕业刚开始打工的时候,我的床头的墙上一定会画一张表,写上一个月的计划和一周的计划 计划也会有完不成的时候,但加深了…...
61 k8s + rancher + karmada容器化部署
文章目录 一、什么是rancher二、为什么使用rancher三、rancher安装1、细部介绍四、图形化操作1、执行2、补充五、 karmada1、官网2、细部介绍一、什么是rancher 1、Rancher 是一个 全栈式 的 Kubernetes 容器管理平台,为你提供在任何地方都能成功运行 Kubernetes 的工具。 二…...
Vue3的新特性变化,上手指南!
文章目录一、Vue3相比Vue2,更新了什么变化?二、Proxy 代理响应式原理三、组合式 API (Composition API)setup()函数:ref()函数reactive()函数组合式 setup 中使用 Props 父向子传递参数计算属性watch(数据监视)watchEffect&#x…...
OllyDbg
本文通过吾爱破解论坛上提供的OllyDbg版本为例,讲解该软件的使用方法 F2对鼠标所处的位置打下断点,一般表现为鼠标所属地址位置背景变红F3加载一个可执行程序,进行调试分析,表现为弹出打开文件框F4执行程序到光标处F5缩小还原当前…...
记一次键盘维修,最终修复
我的笔记本是华硕的K45VD,是我亲人在高二那年买的,之后就一直给我用,距今2023年已经差不多13年,它承载了太多记忆。在大学期间也给它升级,重要的零部件基本没问题。只在大学时加了8G内存和一个240G固态,换了…...
LeetCode 155.最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。实现 MinStack 类:MinStack() 初始化堆栈对象。void push(int val) 将元素val推入堆栈。void pop() 删除堆栈顶部的元素。int top() 获取堆栈顶部的元素。int getMin(…...
C++学习笔记-重载运算符和重载函数
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。 C 允许在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重…...
Java —— JDBC
引入mysql链接 创建表格 Navicat查看建表代码双击要打开的表,右侧顶端点击ddl小方框 CREATE TABLE s (id int(6) NOT NULL,name varchar(20) COLLATE utf8_bin DEFAULT NULL,age int(11) DEFAULT NULL,gender varchar(2) COLLATE utf8_bin DEFAULT NULL,dept var…...
备战金三银四,熬夜半个月汇集大厂 Java 岗 1600 页面试真题
如果你不停地加班。却很少冒险,也很少学习,那你极大可能会陷入到内卷中。 为什么这么说呢?我们先来捋清楚「内卷」的概念: 「内卷化」简而言之就是:日复一日,越混越掉坑里。 所谓内卷化,指一种…...
9、面向对象、泛型与反射
目录一、构造函数二、继承与重写三、泛型四、反射1 - 反射的基本概念2 - 反射的基础数据类型3 - 反射APIa - 获取Type类型b - 获取struct成员变量的信息c - 获取struct成员方法的信息d - 获取函数的信息e - 判断类型是否实现了某接口五、reflect.Valuea - 空value判断b - 获取V…...
Python使用百度通用API进行翻译
想汉化StarUML这个软件,感觉工作量太大,想要用Python自动翻译。 结果网上找的一个个用不了,或者用一会儿就断。 于是自己手写了一个简单的,只有两个类:APIConfig和Translater 使用 demo my_api_config APIConfig(…...
利用ngx_stream_return_module构建简易 TCP/UDP 响应网关
一、模块概述 ngx_stream_return_module 提供了一个极简的指令: return <value>;在收到客户端连接后,立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量(如 $time_iso8601、$remote_addr 等)&a…...
ubuntu搭建nfs服务centos挂载访问
在Ubuntu上设置NFS服务器 在Ubuntu上,你可以使用apt包管理器来安装NFS服务器。打开终端并运行: sudo apt update sudo apt install nfs-kernel-server创建共享目录 创建一个目录用于共享,例如/shared: sudo mkdir /shared sud…...
vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
零基础设计模式——行为型模式 - 责任链模式
第四部分:行为型模式 - 责任链模式 (Chain of Responsibility Pattern) 欢迎来到行为型模式的学习!行为型模式关注对象之间的职责分配、算法封装和对象间的交互。我们将学习的第一个行为型模式是责任链模式。 核心思想:使多个对象都有机会处…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
免费PDF转图片工具
免费PDF转图片工具 一款简单易用的PDF转图片工具,可以将PDF文件快速转换为高质量PNG图片。无需安装复杂的软件,也不需要在线上传文件,保护您的隐私。 工具截图 主要特点 🚀 快速转换:本地转换,无需等待上…...





