当前位置: 首页 > news >正文

Linux进程间通信(个人笔记)

Linux进程通信

  • 1.进程通信介绍
    • 1.1进程间通信目的
    • 1.2进程间通信发展
    • 1.3进程间通信的具体分类
  • 2.管道
    • 2.1匿名管道
      • 2.1.1代码实例
      • 2.1.2 fork共享管道原理
      • 2.1.3 管道的读写规则与特点
      • 2.1.4 进程池
    • 2.2 命名管道
      • 2.2.1 命名管道的创建
      • 2.2.2匿名管道与命名管道的区别
      • 2.2.3代码实例
  • 3.System V共享内存
    • 3.1 共享内存数据结构
    • 3.2 共享内存函数接口
    • 3.3 共享内存代码实例
  • 4.System V消息队列
  • 5.System V信号量


1.进程通信介绍

1.1进程间通信目的

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

1.2进程间通信发展

管道-》System V进程间通信-》POSIX进程间通信

1.3进程间通信的具体分类

管道:
1.匿名管道pipe
2.命名管道

System V IPC:
1.System V 消息队列
2.System V 共享内存
3.System V 信号量

POSIX IPC
1.消息队列
2.共享内存
3.信号量
4.互斥量
5.条件变量
6.读写锁

2.管道

管道是Unix中最古老的进程间通信方式
从一个进程连接到另外一个进程的一个数据流称作一个管道
举例

who | wc -l

在这里插入图片描述

who命令显示当前登录的用户及其相关信息
| 是管道符,表示将 who 命令的输出传递给下一个命令
wc -l 命令计算输入的行数
然而整个命令的作用是输出当前登录用户的总数。

2.1匿名管道

#include<unistd.h>
//功能创建一个无名管道
int pipe(int fd[2]);
//参数
//fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写段
//返回值:成功返回0,失败返回错误代码

在这里插入图片描述

2.1.1代码实例

#include <iostream>
#include <cassert>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MAX 1024
using namespace std;// a.管道的4种情况
//    1.正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
//    2.正常情况,如果管道被写满了,写段必须等待,直到有空间为止(读端读走数据)
//    3.写段关闭,读端一直读取,读端会读到read返回值为0,表示读到文件末尾
//    4.读端关闭,写段一直写入,os会杀掉写端进程,通过向目标进程发送SIGPIPE(13)信号,终止目标进程
// b.管道的五种特性
//    1.匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用于父子,仅限于此
//    2.匿名管道,默认给读写端提供同步机制
//    3.面向字节流
//    4.管道的生命周期是随进程的
//    5.管道是单向通信的,半双工通信的一种特殊情况int main()
{// 第一步:创建管道int pipefd[2];int n = pipe(pipefd);assert(n == 0);(void)n; // 防止编译器告警,意料之中用assert,意料之外用ifcout << "pipefd[0]:" << pipefd[0] << ",pipefd[1]:" << pipefd[1] << endl;// 第二步:创建子进程pid_t id = fork();if (id < 0){perror("fork");return 1;}// 子写,父读// 第三步:父子关闭不需要的fd,形成单向通信的管道if (id == 0){// if(fork()>0) exit(0);//这里是父孙进程可以进行通信// childclose(pipefd[0]);// w-只向管道写入,没有打印int cnt = 0;while (true){// 这里是测试管道文件大小是多少,得出的结果是64KB// char c='a';// write(pipefd[1],&c,1);// cnt++;// cout<<"write....:"<<cnt<<endl;char message[MAX];snprintf(message, sizeof(message), "hello father, I am child, pid: %d, cnt: %d", getpid(), cnt);cnt++;write(pipefd[1], &message, strlen(message));sleep(1);}cout << "child close w piont" << endl;// close(pipefd[1]);//进程退出会自动关闭文件描述符exit(0);}close(pipefd[1]);// rchar buffer[MAX];while (true){ssize_t n = read(pipefd[0], buffer, strlen(buffer) - 1);if (n > 0){buffer[n] = 0; // ‘\0’,当作字符串cout << getpid() << ", " << "child say: " << buffer << "to me!" << endl;}else if (n == 0){cout << "child quit, me too !" << endl;break;}cout << "father return val(n): " << n << endl;sleep(1);break;}cout << "read point close" << endl;close(pipefd[0]);sleep(5);int status = 0;pid_t rid = waitpid(id, &status, 0);if (rid == id){cout << "wait success,child exit sig: " << (status & 0x7F) << endl;}return 0;
}

2.1.2 fork共享管道原理

在这里插入图片描述
在这里插入图片描述

2.1.3 管道的读写规则与特点

a.管道的4种情况
1.正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
2.正常情况,如果管道被写满了,写段必须等待,直到有空间为止(读端读走数据)
3.写段关闭,读端一直读取,读端会读到read返回值为0,表示读到文件末尾
4.读端关闭,写段一直写入,os会杀掉写端进程,通过向目标进程发送SIGPIPE(13)信号,终止目标进程
b.管道的五种特性
1.匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用于父子,仅限于此
2.匿名管道,默认给读写端提供同步机制
3.面向字节流
4.管道的生命周期是随进程的
5.管道是单向通信的,半双工通信的一种特殊情况

2.1.4 进程池

在这里插入图片描述

//ProcessPool.cc
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"const int num = 5;
static int number = 1;class channel
{
public:channel(int fd, pid_t id) : ctrlfd(fd), workerid(id){name = "channel-" + std::to_string(number++);}public:int ctrlfd;pid_t workerid;std::string name;
};void Work()
{while (true){int code = 0;while (true){int code = 0;ssize_t n = read(0, &code, sizeof(code));if (n == sizeof(code)){if (!init.CheckSafe(code)){continue;}init.RunTask(code);}else if (n == 0) // 这里是写端退出{break;}else{// 这里是出错处理暂不处理// do nothing}}}std::cout << "child quit" << std::endl;
}void PrintFd(const std::vector<int> &fds)
{std::cout << getpid() << "close fds: ";for (auto fd : fds){std::cout << fd << " ";}std::cout << std::endl;
}// 传参形式:
// 1.输入函数:const &
// 2.输出参数: *
// 3.输入输出参数:&void CreateChannels(std::vector<channel> *c)
{std::vector<int> old;for (int i = 0; i < num; i++){// 1.定义并创建管道int pipefd[2];int n = pipe(pipefd);assert(n == 0);(void)n;// 2.创建进程pid_t id = fork();assert(id != -1);// 3.构建单向通信信道if (id == 0){if (!old.empty()){for (auto fd : old){close(fd);}PrintFd(old);}close(pipefd[1]);dup2(pipefd[0], 0);Work();exit(0); // 会自动关闭自己打开的所有的Fd}// fatherclose(pipefd[0]);c->push_back(channel(pipefd[1], id));old.push_back(pipefd[1]);// childid,pipefd[1]}
}void PrintDebug(const std::vector<channel> &c)
{for (const auto &channel : c){std::cout << channel.name << ", " << channel.ctrlfd << ", " << channel.workerid << std::endl;}
}void SendCommand(const std::vector<channel> &c, bool flag, int num = -1)
{int pos = 0;while (true){// 1.选择任务int command = init.SelectTask();// 2.选择信道(进程)const auto &channel = c[pos++];pos %= c.size();// debugstd::cout << "send command " << init.ToDesc(command) << "[" << command << "]"<< " in "<< channel.name << " worker is : " << channel.workerid << std::endl;// 3.发送任务write(channel.ctrlfd, &command, sizeof(command));// 4.判断是否要退出if (!flag){num--;if (num <= 0){break;}}sleep(1);}std::cout << "SendCommand done..." << std::endl;
}void ReleaseChannels(std::vector<channel> c)
{// version 2// int num = c.size() - 1;// for (; num >= 0; num--)// {//     close(c[num].ctrlfd);//     waitpid(c[num].workerid, nullptr, 0);// }// version 1for (const auto &channel : c){close(channel.ctrlfd);waitpid(channel.workerid, nullptr, 0);}// for (const auto &channel : c)// {//     pid_t rid = waitpid(channel.workerid, nullptr, 0);//     if (rid == channel.workerid)//     {//         std::cout << "wait child: " << channel.workerid << " success" << std::endl;//     }// }
}int main()
{std::vector<channel> channels;// 1.创建信道,创建进程CreateChannels(&channels);// 2.开始发送任务const bool g_alway_loop = true;// SendCommand(channels,g_alway_loop);SendCommand(channels, !g_alway_loop, 10);// 3.回收资源,想让子进程退出,并且释放管道,只要关闭写端ReleaseChannels(channels);return 0;
}
//Task.hpp
#pragma once#include <iostream>
#include <functional>
#include <vector>
#include <ctime>
#include <unistd.h>// using task_t =std::function<void()>;
typedef std::function<void()> task_t;void Download()
{std::cout << "我是一个下载任务" << "处理者:" << getpid() << std::endl;
}void PrintLog()
{std::cout << "我是一个打印日志的任务" << "处理者:" << getpid() << std::endl;
}void PushVideoStream()
{std::cout << "这是一个推送视频流的任务" << "处理者" << getpid() << std::endl;
}class Init
{
public:// 任务码const static int g_download_code = 0;const static int g_printlog_code = 1;const static int g_push_videostream_code = 2;// 任务集合std::vector<task_t> tasks;public:Init(){tasks.push_back(Download);tasks.push_back(PrintLog);tasks.push_back(PushVideoStream);srand(time(nullptr) ^ getpid());}bool CheckSafe(int code){if (code >= 0 && code < tasks.size()){return true;}else{return false;}}void RunTask(int code){return tasks[code]();}int SelectTask(){return rand() % tasks.size();}std::string ToDesc(int code){switch (code){case g_download_code:return "Download";case g_printlog_code:return "PrintLog";case g_push_videostream_code:return "PushVideoStream";default:return "Unknow";}}
};Init init;

在这里插入图片描述
注意循环创建信道时,子进程的文件描述符表拷贝父进程的,会导致信道被多个文件描述符指向,在释放文件描述符的时候要尤为注意,下面把代码单独拧出来,以便思考

void CreateChannels(std::vector<channel> *c)
{std::vector<int> old;for (int i = 0; i < num; i++){// 1.定义并创建管道int pipefd[2];int n = pipe(pipefd);assert(n == 0);(void)n;// 2.创建进程pid_t id = fork();assert(id != -1);// 3.构建单向通信信道if (id == 0){if (!old.empty()){for (auto fd : old){close(fd);}PrintFd(old);}close(pipefd[1]);dup2(pipefd[0], 0);Work();exit(0); // 会自动关闭自己打开的所有的Fd}// fatherclose(pipefd[0]);c->push_back(channel(pipefd[1], id));old.push_back(pipefd[1]);// childid,pipefd[1]}
}void ReleaseChannels(std::vector<channel> c)
{// version 2// int num = c.size() - 1;// for (; num >= 0; num--)// {//     close(c[num].ctrlfd);//     waitpid(c[num].workerid, nullptr, 0);// }// version 1for (const auto &channel : c){close(channel.ctrlfd);waitpid(channel.workerid, nullptr, 0);}// for (const auto &channel : c)// {//     pid_t rid = waitpid(channel.workerid, nullptr, 0);//     if (rid == channel.workerid)//     {//         std::cout << "wait child: " << channel.workerid << " success" << std::endl;//     }// }
}

2.2 命名管道

匿名管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件

2.2.1 命名管道的创建

1.命名管道可以从命令行上创建

mkfifo filename

2.命名管道从程序中创建

int mkfifo(const char* filename,mode_t mode);

2.2.2匿名管道与命名管道的区别

匿名管道由pipe函数创建并打开
命名管道由mkfifo函数创建,打开用open
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,他们具有相同的语义

2.2.3代码实例

//comm.h
#pragma once#define FILENAME "fifo"
//Makefile
.PHONY:all
all:server cilentserver:server.ccg++ -o $@ $^ -std=c++11
cilent:cilent.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f server cilent fifo
//server.cc
#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm.h"bool MakeFifo()
{int fd = mkfifo(FILENAME, 0666);if (fd < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;return false;}std::cout << "mkfifo success... read" << std::endl;return true;
}int main()
{
Start:int rfd = open(FILENAME, O_RDONLY);if (rfd < 0){std::cerr << "errno: " << errno << ",errstring: " << strerror(errno) << std::endl;if (MakeFifo()){goto Start;}else{return 1;}}std::cout << "open fifo success..." << std::endl;char buffer[1024];while (true){ssize_t s = read(rfd, &buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;std::cout << "Client say# " << buffer << std::endl;}else if (s == 0){std::cout << "Client quit, server quit too!" << std::endl;break;}}close(rfd);std::cout << "close fifo sucess..." << std::endl;return 0;
}
//cilent.cc
#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "comm.h"int main()
{int wfd = open(FILENAME, O_WRONLY);if (wfd < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;return 1;}std::string message;while (true){std::cout << "Please Enter# ";std::getline(std::cin, message);ssize_t s = write(wfd, message.c_str(), message.size());if (s < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;break;}}close(wfd);std::cout << "close fifo success..." << std::endl;return 0;
}

3.System V共享内存

在这里插入图片描述
进程通信的前提:必须让不同的进程看到同一份资源(必须由OS提供)
OS会允许系统中同时存在多个共享内存,先描述,在组织,对共享内存进行管理,进程间是通过一个提前约定好的标识看到同一个共享内存的

3.1 共享内存数据结构

在这里插入图片描述

3.2 共享内存函数接口

shmget函数
功能:用来创建共享内存
原型:int shmget(key_t key,size_t size,int shmflg);
参数:
key:提前约定好的一个钥匙,通常是路径加数字转换而来的
size:共享内存大小
shmflg:用法和创建文件使用的mode模式标识是一样的
返回值:
成功返回一个非负整数,即共享内存的标识码;失败返回-1
在这里插入图片描述

shmat函数
功能:将共享内存段连接到进程地址空间
原型:void* shmat(int shmid,const void* shmaddr,int shmflg);
参数:
shmid:共享内存标识
shmaddr:指定连接的地址
shmflg:两个取值:SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指针指向共享内存首地址;失败返回-1
shmaddr为NULL,核心自动选择一个地址

shmdt函数
功能:将共享内存段与当前进程脱离
原型:int shmdt(const void* shmaddr);
参数:shmaddr:由shmat所返回的指针
返回值:成功返回0,失败返回-1
注意:将共享内存段与当前进程脱离,但不等于删除共享内存段

shmctl函数
功能:用于控制共享内存
原型:int shmctl(int shmid,int cmd,struct shmid_ds* buf);
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(三个取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

命令说明
IPC_STAT把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
IPC_RMID删除共享内存段

3.3 共享内存代码实例

//Makefile
.PHONY:all
all:server client
server:server.ccg++ -o $@ $^ -std=c++11
client:client.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f server client fifo
//comm.hpp
#pragma once
#include <iostream>
#include <cstdlib>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/shm.h>const std::string pathname = "/home/whb/pipe_fifo_shm";
const int proj_id = 0x11223344;const std::string filename = "fifo";// 共享内存的大小,强烈建议设置为n*4096
const int size = 4096;key_t GetKey()
{key_t key = ftok(pathname.c_str(), proj_id);if (key < 0){std::cerr << "errno: " << errno << ",errstring: " << strerror(errno) << std::endl;exit(1);}return key;
}std::string ToHex(int id)
{char buffer[1024];snprintf(buffer, sizeof(buffer), "0x%x", id);return buffer;
}int CreateShmHelper(key_t key, int flag)
{int shmid = shmget(key, size, flag);if (shmid < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;exit(2);}return shmid;
}int CreateShm(key_t key)
{return CreateShmHelper(key, IPC_CREAT | IPC_EXCL | 0644);
}int GetShm(key_t key)
{return CreateShmHelper(key, IPC_CREAT);
}bool MakeFifo()
{int n = mkfifo(filename.c_str(), 0666);if (n < 0){std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;return false;}std::cout << "mafifo success... read" << std::endl;return true;
}
//server.cc
#include <iostream>
#include <cstring>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include "comm.hpp"class Init
{
public:Init(){// bool r=MakeFifo();// if(!r)//     return;key_t key = GetKey();std::cout << "key : " << ToHex(key) << std::endl;// sleep(3);// key vs shmid// shmid:应用这个共享内存的时候,我们使用shmid来进行操作共享内存,  FILE*// key:不要在应用层使用,只用来在内核中标识shm的唯一性!,         fdshmid = CreateShm(key);std::cout << "shmid: " << shmid << std::endl;// sleep(10);std::cout << "开始将shm映射到进程的地址空间中" << std::endl;s = (char *)shmat(shmid, nullptr, 0);// fd=open(filename.c_str(),O_RDONLY);}~Init(){// sleep(5);shmdt(s);std::cout << "开始将shm从进程地址空间中移除" << std::endl;// sleep(5);shmctl(shmid, IPC_RMID, nullptr);std::cout << "开始将shm从os中删除" << std::endl;// close(fd);// unlink(filename.c_str());}public:int shmid;int fd;char *s;
};int main()
{key_t key = GetKey();// int msgid = msgget(key, IPC_CREAT | IPC_EXCL);// std::cout << "msgid: " << msgid << std::endl;// struct msqid_ds ds;// msgctl(msgid, IPC_STAT, &ds);// std::cout << ds.msg_qbytes << std::endl;// std::cout << ToHex(ds.msg_perm.__key) << std::endl;// sleep(10);// int semid = semget(key, 1, IPC_CREAT | IPC_EXCL);// std::cout << "semid: " << semid << std::endl;// sleep(4);// semctl(semid, 1, IPC_RMID);// msgctl(msgid,IPC_RMID,nullptr);Init init;struct shmid_ds ds;shmctl(init.shmid, IPC_STAT, &ds);std::cout << ToHex(ds.shm_perm.__key) << std::endl;std::cout << ds.shm_segsz << std::endl;std::cout << ds.shm_segsz << std::endl;std::cout << ds.shm_segsz << std::endl;std::cout << ds.shm_atime << std::endl;std::cout << ds.shm_nattch << std::endl;sleep(5);// TODOwhile (true){// waitint code = 0;ssize_t n = read(init.fd, &code, sizeof(code));if (n > 0){// 直接读取std::cout << "共享内存的内容: " << init.s << std::endl;sleep(1);}else if (n == 0){break;}}sleep(10);return 0;
}
//client.cc
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include "comm.hpp"int main()
{key_t key = GetKey();int shmid = GetShm(key);char *s = (char *)shmat(shmid, nullptr, 0);std::cout << "attach shm done" << std::endl;int fd = open(filename.c_str(), O_WRONLY);// sleep(10);// TODO// 共享内存的通信方式,不会提供同步机制,共享内存是直接裸露给所有使用者的,一定要注意共享内存的使用安全问题//char c = 'a';for (; c <= 'z'; c++){s[c - 'a'] = c;std::cout << "write : " << c << "done" << std::endl;sleep(1);// 通知对方int code = 1;write(fd, &code, sizeof(4));}shmdt(s);std::cout << "detach shm done" << std::endl;close(fd);return 0;
}

在这里插入图片描述

在这里插入图片描述

4.System V消息队列

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法

每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值

特性方面
IPC资源必须删除,否则不会自动清除,除非重启,所以system V IPC资源的生命周期随内核
在这里插入图片描述

5.System V信号量

信号量的本质是一个计数器
为了让进程间通信-》多个执行流看到的同一份资源,公共资源-》并发访问-》数据不一致的问题-》保护起来-》互斥和同步
互斥:任何一个时刻只允许一个执行流(进程)访问公共资源,加锁完成
同步:多个执行流执行的时候,按照一定的顺序执行
被保护起来的公共资源,临界资源
访问该临界资源的代码,我们叫做临界区
而维护临界资源,其实就是维护临界区
在这里插入图片描述

相关文章:

Linux进程间通信(个人笔记)

Linux进程通信 1.进程通信介绍1.1进程间通信目的1.2进程间通信发展1.3进程间通信的具体分类 2.管道2.1匿名管道2.1.1代码实例2.1.2 fork共享管道原理2.1.3 管道的读写规则与特点2.1.4 进程池 2.2 命名管道2.2.1 命名管道的创建2.2.2匿名管道与命名管道的区别2.2.3代码实例 3.Sy…...

SAP S/4HANA 迁移:IT 高管实用指南

新版《通往SAP S/4HANA之路》指南为计划从SAP ERP或SAP S/4HANA本地版本迁移到云端的组织提供了全面的参考。随着数字化转型的加速&#xff0c;尤其是在面临挑战的汽车行业等领域&#xff0c;企业必须采用云ERP解决方案&#xff0c;例如SAP S/4HANA云私有版&#xff0c;以应对瞬…...

Qt源码-Qt多媒体音频框架

Qt 多媒体音频框架 一、概述二、音频设计1. ALSA 基础2. Qt 音频类1. 接口实现2. alsa 插件实现 一、概述 环境详细Qt版本Qt 5.15操作系统Deepin v23代码工具Visual Code源码https://github.com/qt/qtmultimedia/tree/5.15 这里记录一下在Linux下Qt 的 Qt Multimedia 模块的设…...

卸载PLSQL及标准卸载流程

目录 1. 卸载PLSQL2. 删除注册表3. 删除数据信息 1. 卸载PLSQL 等待进度条走完 2. 删除注册表 regedit 右击删除 3. 删除数据信息 由于AppData是隐藏文件&#xff0c;需要勾选隐藏的项目。 重启电脑&#xff0c;PLSQL就卸载成功了。...

如何使用ssm实现办公OA系统0

TOC 10907ssm办公OA系统10907ssm0 第一章 绪 论 1.1背景及意义 系统管理也都将通过计算机进行整体智能化操作&#xff0c;对于办公OA系统所牵扯的管理及数据保存都是非常多的&#xff0c;例如管理员&#xff1b;主页、个人中心、公司公告管理、设备分类管理、办公设备管理、…...

IPguard与Ping32—选择合适的企业数据保护解决方案

在数字化时代&#xff0c;企业面临着各种数据安全挑战&#xff0c;选择合适的保护工具至关重要。IPguard与Ping32是两款备受关注的数据保护软件&#xff0c;但它们各自的功能和适用场景有所不同&#xff0c;企业在选择时需根据自身需求做出明智决策。 Ping32&#xff1a;全面的…...

2024 kali虚拟机安装教程,分两大步骤,图文讲解(2)

准备工作&#xff1a; 按照图文讲解&#xff08;1&#xff09;搭建好虚拟机&#xff0c;继续以下步骤 2024 kali虚拟机安装教程&#xff0c;分两大步骤&#xff0c;图文讲解&#xff08;1&#xff09;-CSDN博客 正式开始 1.开启&#xff0c;↑ ↓ 方向键&#xff0c;选择第一…...

【解决办法】git clone报错unable to access ‘xxx‘: SSL certificate problem

git clone 是 Git 版本控制系统中的一个基本命令&#xff0c;用于从远程仓库复制一个完整的版本库到本地。这个命令不仅复制远程仓库中的所有文件&#xff0c;还复制仓库的历史记录&#xff0c;使得你可以在本地进行版本控制操作&#xff0c;如提交&#xff08;commit&#xff…...

基于STM32的智能家居--硬件接线

分配GPIO 1.首先分配串口通讯引脚&#xff0c;该开发板中有三组串口引脚分别分配如图所示。 2.分配SPI。 3.其他为普通GPIO口&#xff0c;B8,B9模拟IIC协议与OLED屏幕进行通讯。...

mac电脑如何删除应用程序?怎么删除苹果电脑里的软件

在使用Mac电脑的过程中&#xff0c;随着时间的推移&#xff0c;我们可能会安装大量的应用程序。然而&#xff0c;这些应用程序中有很多可能只是临时使用&#xff0c;或者已经不再需要了。这些无用的应用程序不仅占据了宝贵的硬盘空间&#xff0c;还可能拖慢Mac系统的运行速度。…...

Hive优化操作(一)

Hive SQL 优化指南 在使用 Hive 进行数据分析时&#xff0c;提高查询性能至关重要。以下是一些具体的优化策略&#xff0c;帮助我们在工作中更有效地管理和查询数据。 一、 减少数据量进行优化 1. 分区表优化 分区是一种表的子集&#xff0c;用于按某一列&#xff08;如日期…...

Vue中常用指令——(详解,并附有代码)

文章目录 一.指令合集1.0 概述1.1 插值表达式1.2 v-text/v-html1.3 v-show/ v-if1.4 v-on1.4.1 内联语句1.4.2 事件处理函数 1.5 v-bind1.6 Test1.7 v-for 一.指令合集 内容渲染指令&#xff08;v-html、v-text&#xff09;条件渲染指令&#xff08;v-show、v-if、v-else、v-e…...

redistemplate实现点赞相关功能

使用Redis的SET数据结构来存储每个实体的点赞用户ID列表&#xff0c;方便进行点赞数量的计数和用户点赞状态的检查。以下是一个小demo&#xff0c;只提供简单思路。 Service public class LikeService {Autowiredprivate RedisTemplate redisTemplate;//点赞public Long like(…...

C++ 算法学习——7.4.1 优化算法——双指针

双指针法&#xff08;Two Pointers&#xff09;是一种常用的算法技巧&#xff0c;通常用于解决数组或链表中的问题。这种技巧通过维护两个指针&#xff0c;通常分别指向数组或链表的不同位置&#xff0c;来协同解决问题。双指针法一般有两种类型&#xff1a;快慢指针和左右指针…...

镁光DDR3的命名

64M16的解释如图。 125是指一个时钟周期需要1.25ns走完&#xff0c;1us对应 1MHZ, 1ns对应1000MHZ ,那么1.25ns对应的时钟频率&#xff0c;就先用 1/1.25得到 1.25us对应的时钟频率 0.8 &#xff0c;然后再乘以1000&#xff0c;得到800就是MHZ 带宽的计算就是 800M…...

[Git] Git下载及使用 从入门到精通 详解(附下载链接)

前言 目录 Git概述 简介 下载 Git代码托管服务 Git常用命令 Git全局配置 获取Git仓库 在本地初始化一个Git仓库 从远程仓库克隆 基本概念 工作区文件状态 本地仓库操作 远程仓库操作 分支操作 标签操作 在IDEA中使用Git 在IDEA中配置Git 本地仓库操作 远程仓…...

Linux源码阅读笔记-USB驱动分析

基础层次详解 通用串行总线&#xff08;USB&#xff09;主要用于连接主机和外部设备&#xff08;协调主机和设备之间的通讯&#xff09;&#xff0c;USB 设备不能主动向主机发送数据。USB 总线采用拓扑&#xff08;树形&#xff09;&#xff0c;主机侧和设备侧的 USB 控制器&a…...

【超级详细解释】力扣每日一题 134.加油站 48. 旋转图像

134.加油站 力扣 这是一个很好的问题。这个思路其实基于一种贪心策略。我们从整个路径的油量变化来理解它&#xff0c;结合一个直观的“最低点法则”&#xff0c;来确保找到正确的起点。 问题的核心&#xff1a;油量差值的累积 对于每个加油站&#xff0c;我们有两个数组&…...

数据挖掘基本架构知识点

数据挖掘的基本架构主要包含以下几个部分&#xff1a; 一、数据获取 1. 数据源 - 可以是数据库&#xff08;如关系型数据库MySQL、Oracle等&#xff09;、文件系统&#xff08;如CSV文件、XML文件等&#xff09;、网络数据&#xff08;如网页内容、社交媒体数据&#xff09;等…...

LangChain中使用Prompt01

1.引入提示模板 from langchain.prompts import (SystemMessagePromptTemplate,AIMessagePromptTemplate,HumanMessagePromptTemplate, )2.设置系统提示 system_template_text"你是一位专业的翻译&#xff0c;能够将{input_language}翻译成{output_language}&#xff0c…...

VB.net复制Ntag213卡写入UID

本示例使用的发卡器&#xff1a;https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

【力扣数据库知识手册笔记】索引

索引 索引的优缺点 优点1. 通过创建唯一性索引&#xff0c;可以保证数据库表中每一行数据的唯一性。2. 可以加快数据的检索速度&#xff08;创建索引的主要原因&#xff09;。3. 可以加速表和表之间的连接&#xff0c;实现数据的参考完整性。4. 可以在查询过程中&#xff0c;…...

聊聊 Pulsar:Producer 源码解析

一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台&#xff0c;以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中&#xff0c;Producer&#xff08;生产者&#xff09; 是连接客户端应用与消息队列的第一步。生产者…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业

6月9日&#xff0c;国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解&#xff0c;“超级…...

spring:实例工厂方法获取bean

spring处理使用静态工厂方法获取bean实例&#xff0c;也可以通过实例工厂方法获取bean实例。 实例工厂方法步骤如下&#xff1a; 定义实例工厂类&#xff08;Java代码&#xff09;&#xff0c;定义实例工厂&#xff08;xml&#xff09;&#xff0c;定义调用实例工厂&#xff…...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

Xen Server服务器释放磁盘空间

disk.sh #!/bin/bashcd /run/sr-mount/e54f0646-ae11-0457-b64f-eba4673b824c # 全部虚拟机物理磁盘文件存储 a$(ls -l | awk {print $NF} | cut -d. -f1) # 使用中的虚拟机物理磁盘文件 b$(xe vm-disk-list --multiple | grep uuid | awk {print $NF})printf "%s\n"…...

论文笔记——相干体技术在裂缝预测中的应用研究

目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术&#xff1a;基于互相关的相干体技术&#xff08;Correlation&#xff09;第二代相干体技术&#xff1a;基于相似的相干体技术&#xff08;Semblance&#xff09;基于多道相似的相干体…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...