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

【Linux的文件篇章 - 管道文件】

Linux学习笔记---013

  • Linux的管道文件
    • 1、进程间通信
      • 1.1、进程为什么要通信?
      • 1.2、进程如何通信?
      • 1.3、进程通信的方式?
    • 2、匿名管道
      • 2.1、理解一种现象
      • 2.2、基本概念和管道原理
    • 3、管道的使用
      • 3.1、代码样例
      • 3.2、如何使用管道通信呢?
      • 3.3、管道的4种情况
      • 3.4、管道的5种特征
      • 3.5、管道的应用场景
    • 4、命名管道
      • 4.1、原理
      • 4.2、创建命名管道函数的使用
    • 5、system V的共享内存
      • 5.1、原理
      • 5.2、代码理解
      • 5.3、共享内存的理解
      • 5.4、共享内存的相关接口
    • 6、消息队列
      • 6.1、基本概念
      • 6.2、涉及的常用接口
    • 7、信号量
      • 7.1、5个概念
      • 7.2、对于信号量的理论理解
      • 7.3、原子操作
      • 7.4、常用信号量的指令

Linux的管道文件

前言:
前篇开始进行了解学习Linux的磁盘文件等相关知识内容,接下来学习关于Linux的管道文件、共享内存、消息队列和信号量的基本知识,深入地了解这个强大的开源操作系统。
/知识点汇总/

1、进程间通信

1.1、进程为什么要通信?

进程间也是需要某种协同的,如何协同的前提条件就是通信。
数据是有类别的,通知就绪的,单纯的要传递的信息,以及控制信息。

事实:进程是具有独立性的。
进程 = 内核的数据结构 + 代码和数据

1.2、进程如何通信?

a、进程间通信,成本可能会稍微高一些。(因为进程是独立的)

比如:进程a把数据给进程b,进程具有独立性,所以数据无法直接传递的。(父子进程fork的方式,只是处于只读,传递信息和一直可以传递信息是有区别的,所以frok是处于可以传递信息,但不能一直传递,因为是基于写时拷贝的)

b、进程间通信的前提:先让不同的进程,看到同一份(操作系统的)资源(“一段内存”)

因为进程a和进程b,进程间具有独立性,相互之间的空间和数据等资源无法共享,就通过操纵系统实现,让它们能够在“一段内存中交换和访问数据”。

那么操作系统怎么知道什么时候创建共享区域呢?

1.一定是某一个进程先需要通信,让OS创建一个共享资源。
2.OS必须提供很多的系统调用。 – OS创建的共享资源不同,系统调用接口不同 ---- 进程间通信的方式就会存在不同。

1.3、进程通信的方式?

a、存在一定约定的标准(专利)
b、消息队列、共享内存、信号量

直接复用内核代码直接通信呢?
进程间独立,对于文件系统无关。引出管道

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

2、匿名管道

2.1、理解一种现象

为什么父子进程会向同一个显示器终端打印数据。

因为父子进程中,子进程会继承父进程的文件描述符表,进而指向同一个显示器文件,用同一个进程inode,也就把数据写入同一个缓冲区里,所以系统刷新时,就刷新到同一个显示器中。

进程默认会打开三个标准输入/输出:0,1,2怎么做到的呢?

都属于bash的子进程,所以是bash打开了。
进程默认也就打开了,我们只要约定好即可。

close():为什么我们子进程主动close(0/1/2),不影响父进程继续使用显示器文件呢?

本质是由于,之前了解到的引用计数,通过引用计数能够知道有多少文件指针指向它,那么就通过引用计数的指针依次释放指定的次数。

2.2、基本概念和管道原理

那么在通过操作系统,基于文件系统上,在内存中建立的“一段共享内存”就称为管道资源。 – 管道文件

注意:

1.管道只允许单向通信 — 半双工通信
2.管道与文件的操作区别,就在于不用刷新到磁盘了。

既然父子进程会关闭不需要的fd,那么为什么在创建父子进程时,要默认打开呢?可以选择不关闭吗?

答:为了让子进程继承下去(父进程只有,那么子进程就只有读,父进程有读/写,那么子进程就继承读/写)。
可以不关闭,建议关闭,防止误读或误写,以及系统资源的浪费。

既然管道不用再刷新到磁盘中,那么需要重新设计通信接口吗?

答:创建管道的系统调用,底层实际就是open,只是不用了磁盘部分。
int pipe(int pipefd[2]);
不需要文件路径和文件名,其次也被称为匿名文件 – 匿名管道

管道只能实现单向通信。我实际就想要实现双向通信呢?

答:就创建两个管道。

为什么管道是单向通信的呢?

答:a.方便复用代码,减少开发成本。
b.数据易混淆,涉及数据的区分等复杂的操作,所以不采用双向,只需要满足传输数据。单向即可满足。

3、管道的使用

3.1、代码样例

测试代码: 子进程交给父进程的通信

//管道的使用#include <iostream>
#include <unistd.h>
//c++版本的errno.h,和c++版本的string.h
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string>using namespace std;const int Size = 1024;string getOtherMessage()
{static int cnt = 0;string messageid = to_string(cnt);cnt++;pid_t self_id getpid();string  stringpid = to_string(self_id);//拼接string message = "messageid: ";message += messageid;message += " my pid is : ";message += stringpid;return message;
}//子进程写入
void SubProcessWrite(int wfd)
{string message = "father,I am your son process!";while (true){string info = message + getOtherMessage();//拼接得到,子进程写入管道的信息write(wfd, info.c_str(), info.size());//写入管道时,用的是系统调用write,没有写入'\0',也没有必要不使用时一同写入‘\0’sleep(5);//情况2:管道满64kb,ubantu 20.02版本char c = 'A';write(wfd, &c, 1);cout << "pipesize" << ++pipesize << endl;break;}cout << "child quit ..." << endl;
}//父进程读取
void FatherProcessRead(int rfd)
{char inbuffer[Size];//while (true){//sleep(5);ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);//因为没有写入'\0',所以sizeof读取到时要减1if (n > 0){inbuffer[n] = 0;//所以需要时,要手动添加'\0'cout << "father get message" << inbuffer << endl;}cout << "father get return val: " << n << endl;}
}int main()
{//1.创建管道int pipefd[2];int n = pipe(pipefd);//pipe的参数属于输出型参数,rfd和wfdif (n != 0){cerr << "errno:" << errno << ": " << "srrstring : " << strerror(errno) << endl;return 1;}//打印文件描述符,预测是3和4,因为文件描述符默认代开三个0,1,2.cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << endl;sleep(1);//得到的是管道的读写端://pipefd[0] --> 0 -->r//pipefd[1] --> 1 -->w//2.创建子进程//3.关闭不需要的文件描述符(以子进程写,父进程读为例)pid_t id = fork();if (id == 0){cout << "子进程关闭不需要的fd,准备开始发消息" << endl;sleep(1);//子进程 -- write//....//关闭不需要的文件描述符close(pipefd[0]); // 关闭读//写入SubProcessWrite(pipefd[1]);close(pipefd[1]); // 写完后,关闭写exit(0);}cout << "父进程关闭不需要的fd,准备开始收消息" << endl;sleep(1);//父进程 -- read//....//关闭不需要的文件描述符close(pipefd[1]); // 关闭写//读取FatherProcessRead(pipefd[0]);close(pipefd[0]); // 读完后,关闭读//到此仍然没有进行通信,只是在建议一个共享的内存空间 --管道//即:让不同的进程看到同一块资源。//以上那么多操作,也就说明了进程间的通信是需要一定的成本的。(因为进程间具有独立性)//4.进程间通信//SubProcessWrite() //FatherProcessRead()//5.防止僵尸进程pid_t rid = waitpid(id, nullptr, 0);if (rid > 0){cout << "wait child process done" << endl;}return 0;
}

3.2、如何使用管道通信呢?

int pipe(int pipefd[2]); — 参数int pipefd[2],属于输出型参数,表示管道的输入或输出的端口
既然管道也是文件,那么文件的操作依然通用于管道文件。
read / write

根据之前的知识,知道的fork之后,子进程是拿到父进程的数据的,是属于通信吗?

严格意义上讲并不是属于通信,对于子进程来讲,它是只读的(无法修改,无法阻止接收通信,只能父进程交给子进程),完全是由父进程决定得到的资源,是单向的数据。
所以再结合写时拷贝,对方是看不见通信信息的。
所以简单的通过全局变量的缓冲区,使得双方获取对方数据是行不通的。

代码验证,测试代码:

#include <iostream>
#include <unistd.h>
//c++版本的errno.h,和c++版本的string.h
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <string>using namespace std;const int Size = 1024;string getOtherMessage()
{static int cnt = 0;string messageid = to_string(cnt);cnt++;pid_t self_id getpid();string  stringpid = to_string(self_id);//拼接string message = "messageid: ";message += messageid;message += " my pid is : ";message += stringpid;return message;
}//子进程写入
void SubProcessWrite(int wfd)
{string message = "father,I am your son process!";char c = 'A';while (true){//情况5:cerr << " ++++++++++++++++++++++ " << endl;string info = message + getOtherMessage();//拼接得到,子进程写入管道的信息write(wfd, info.c_str(), info.size());//写入管道时,用的是系统调用write,没有写入'\0',也没有必要不使用时一同写入‘\0’//sleep(5);cerr << info << endl;//情况2:管道满64kb,ubantu 20.02版本//	write(wfd, &c, 1);//	cout << "pipesize" << ++pipesize << "write charctor" << c << endl;//	c++;//	if (c == 'G') break;//	sleep(1);}cout << "child quit ..." << endl;
}//父进程读取
void FatherProcessRead(int rfd)
{char inbuffer[Size];//while (true){sleep(2);cout << " -------------- " << endl;ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);//因为没有写入'\0',所以sizeof读取到时要减1if (n > 0){inbuffer[n] = 0;//所以需要时,要手动添加'\0'cout << "father get message" << inbuffer << endl;}else if (n == 0){cout << "client quit,father get return vsl: " << n << " father quit too!" << endl;break;}else if (n < 0){cerr << "read error" << endl;break;}//情况:5sleep(1);break;}
}int main()
{//1.创建管道int pipefd[2];int n = pipe(pipefd);//pipe的参数属于输出型参数,rfd和wfdif (n != 0){cerr << "errno:" << errno << ": " << "srrstring : " << strerror(errno) << endl;return 1;}//打印文件描述符,预测是3和4,因为文件描述符默认代开三个0,1,2.cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << endl;sleep(1);//得到的是管道的读写端://pipefd[0] --> 0 -->r//pipefd[1] --> 1 -->w//2.创建子进程//3.关闭不需要的文件描述符(以子进程写,父进程读为例)pid_t id = fork();if (id == 0){cout << "子进程关闭不需要的fd,准备开始发消息" << endl;sleep(1);//子进程 -- write//....//关闭不需要的文件描述符close(pipefd[0]); // 关闭读//写入SubProcessWrite(pipefd[1]);close(pipefd[1]); // 写完后,关闭写exit(0);}cout << "父进程关闭不需要的fd,准备开始收消息" << endl;sleep(1);//父进程 -- read//....//关闭不需要的文件描述符close(pipefd[1]); // 关闭写//读取FatherProcessRead(pipefd[0]);cout << "5s, father close rfd" << endl;sleep(5);close(pipefd[0]); // 读完后,关闭读//到此仍然没有进行通信,只是在建议一个共享的内存空间 --管道//即:让不同的进程看到同一块资源。//以上那么多操作,也就说明了进程间的通信是需要一定的成本的。(因为进程间具有独立性)//4.进程间通信//SubProcessWrite() //FatherProcessRead()//5.防止僵尸进程int status = 0;pid_t rid = waitpid(id, nullptr, 0);if (rid > 0){cout << "wait child process done,exit sig: " << (status&0x7f) << endl;cout << "wait child process done,exit code(ign): " << ((status>>8)&0xFF) << endl;}return 0;
}

3.3、管道的4种情况

1.可能会存在被多个进程同时访问的情况(并发),数据都不一致问题。
2.如果管道内部是空的 && write fd 没有关闭,读取条件不具备,读进程会被阻塞 — wait — 读取条件具备再写入数据。
3.管道被写满了 && read fd 不读且没有关闭,管道被写满,写进程会被阻塞(管道被写满 – 写条件不具备) – wait — 写条件具备 --》读取数据,管道一直在读 && 写端关闭了wfd,读端read返回值读到了0,表示读到了文件结尾。
5.rfd直接关闭,写端wfd一直再进行写入?处于水管出口堵塞了,还一直灌水,属于无用功。对于操作系统不会做这种浪费时间浪费空间的事情,没有意义。操作系统会直接杀掉马,这种坏管道。
所以对于此类出异常的管道,操作系统会主动发送信号,kill SIGPIPE杀掉该管道。

3.4、管道的5种特征

1.匿名管道:只限于具有血缘关系的进程之间,进行通信,常用于父子进程之间的通信。(因为父子进程有一个“天生的”前提条件:都能看到同一份(操作系统的)资源(“一段内存”))
2.管道内部,自带进程之间同步的机制。(多执行流执行代码时,具有明显的顺序性)
3.管道文件的生命周期是随进程的。
4.管道文件在通信的时候,是面向字节流的(有些挑战)
面向字节流最典型的特点就是:
write的次数与读取的次数不是一一匹配的。
5.管道通信的模式,是一种特殊的半双工模式。

补充:PIPE_BUF

因为管道通信属于特殊的半双工,所以有关于管道大小的两点:
1.写入的字节大小小于PIPE_BUF的大小时,会被认为是原子的,也就是小于规定的范围的或者说属于一个单元的,即这种情况下是安全的,不会出现写到一半被读取走。
2.PIPE_BUF的大小通常是512byte,而Linux中是4096byte.

3.5、管道的应用场景

1.命令行中的|,就是匿名管道的应用。
2.进程池
比如提前创建fork一批子进程,有任务就通过每一个管道,对接每一个子进程;
从而父进程对接每一个管道的写端,每一个子进程对应与其对应的读端。
这种提前创建好进程的方式就是进程池,大大节约了成本,使其不用单独创建单独的进程了,直接通过各个管道派遣任务就行了。
并且管道里没有数据时,各个子进程(work进程)就处于阻塞等待,等待分配的任务;
所以父进程(master)向哪一个管道写入,就会唤醒哪一个进程来处理任务。(进程间+管道就处于的概念就是,进程的协同)
其中,父进程最好要将任务均衡的划分给每一个子进程,就称为负载均衡。

测试代码:

#include <iostream>
#include <sys/types.h>
#include <string>
#include <unistd.h>
#include <vector>
#include "Task.hpp"
#include <sys/wait.h>using namespace std;class Channel
{
public:Channel(int wfd, pid_t id, const string &name):_wfd(wfd), _subprocessid(id),_name(name){}int GetWfd(){return _wfd;}pid_t GetProcessId(){return _subprocessid;}string GetName(){return _name;}void CloseChannel(){close(_wfd);}void Wait(){pid_t rid = waitpid(_subprocessid,nullptr,0);if (rid > 0){cout << "wait " << rid << " success" << endl;}}~Channel(){}
private:int _wfd;pid_t _subprocessid;string _name;
};void work(int rfd)
{while (true){int command = 0;int n = read(rfd, &command, sizeof(command));if (n == sizeof(int)){cout << "pid is: " << getpid() << " handler task" << endl;ExcuteTask(command);}else if (n == 0){cout << "sub process: " << getpid() << " quit" << endl;break;}}
}//创建信道和子进程
/**/
void test_pipepool(int argc, char* argv[])
{if (argc != 2){cerr << "Usage: " << argv[0] << " processnum" << endl;return ;}int num = stoi(argv[1]);vector<Channel> channels;//创建信道和子进程for (int i = 0; i < num; i++){//1.创建管道int pipefd[2] = { 0 };int n = pipe(pipefd);if (n < 0) exit(1);//3.创建子进程pid_t id = fork();if (id == 0){//child --- read 处理任务close(pipefd[1]);//关闭写端work(pipefd[0]);close(pipefd[0]);//读完读端exit(0);}//3.构建一个channel1名称string channel_name = "Channel-" + to_string(i);//father --- writeclose(pipefd[0]);//关闭读端//a、子进程的Pid, b、父进程关心的管道的写端channels.push_back(Channel(pipefd[1], id, channel_name));}//for testfor (auto& channel : channels){cout << "==========================" << endl;cout << channel.GetName() << endl;cout << channel.GetProcessId() << endl;cout << channel.GetWfd() << endl;}
}//优化
/**/
//形参类型和命名规范
//const   ---> 只读型参数
//const & --> 输出型参数
//& --->  输入输出型参数
//* ---> 输出型参数
void CreateChannelAndSub(int num, vector<Channel>* channels)
{//创建信道和子进程for (int i = 0; i < num; i++){//1.创建管道int pipefd[2] = { 0 };int n = pipe(pipefd);if (n < 0) exit(1);//3.创建子进程pid_t id = fork();if (id == 0){//child --- read 处理任务close(pipefd[1]);//关闭写端work(pipefd[0]);close(pipefd[0]);//读完读端exit(0);}//3.构建一个channel1名称string channel_name = "Channel-" + to_string(i);//father --- writeclose(pipefd[0]);//关闭读端//a、子进程的Pid, b、父进程关心的管道的写端channels->push_back(Channel(pipefd[1], id, channel_name));}}//轮询方案
int NextChannel(int channelnum)
{static int next = 0;int channel = next;next++;next %= channelnum;return channel;
}void SendTaskCommand(Channel& channel, int taskcommand)
{write(channel.GetWfd(), &taskcommand, sizeof(taskcommand));
}//优化1
void test_pipepool2(int argc, char* argv[])
{if (argc != 2){cerr << "Usage: " << argv[0] << " processnum" << endl;return ;}int num = stoi(argv[1]);LoadTask();//装载任务vector<Channel> channels;//1.创建信道和子进程CreateChannelAndSub(num, &channels);//2.通过channel控制子进程while (true){sleep(1);//a、选择一个任务int taskcommand = SelectTask();//b、选择一个信道和进程int channel_index = NextChannel(channels.size());//c、发送任务SendTaskCommand(channels[channel_index], taskcommand);cout << "-----------------" << endl;cout << "taskcommand: " << taskcommand << " channel: " << channels[channel_index].GetName() << " sub process: " << channels[channel_index].GetProcessId() << endl;}//3.回收管道和子进程
}//优化2
void ctrlProcessOnce(vector<Channel>& channels)
{sleep(1);//a、选择一个任务int taskcommand = SelectTask();//b、选择一个信道和进程int channel_index = NextChannel(channels.size());//c、发送任务SendTaskCommand(channels[channel_index], taskcommand);cout << "-----------------" << endl;cout << "taskcommand: " << taskcommand << " channel: " << channels[channel_index].GetName() << " sub process: " << channels[channel_index].GetProcessId() << endl;
}void ctrlProcess(vector<Channel>& channels, int times = -1)
{if (times > 0){while (times--){ctrlProcessOnce(channels);}}else{while (true){ctrlProcessOnce(channels);}}
}void CleanUpChannel(vector<Channel>& channels)
{//关闭管道for (auto& channel : channels){channel.CloseChannel();}//注意:防止僵尸进程//回收子进程for (auto& channel : channels){channel.Wait();}
}void test_pipepool3(int argc, char* argv[])
{if (argc != 2){cerr << "Usage: " << argv[0] << " processnum" << endl;return;}int num = stoi(argv[1]);LoadTask();//装载任务vector<Channel> channels;//1.创建信道和子进程CreateChannelAndSub(num, &channels);//2.通过channel控制子进程ctrlProcess(channels);//3.回收管道和子进程//a、关闭所有的写端,返回值为0 --》子进程就自动退出,最后回收即可//b、回收子进程CleanUpChannel(channels);
}//先描述,再组织
int main(int argc, char* argv[])
{//创建信道和子进程test_pipepool(argc, argv);//优化test_pipepool2(argc, argv);//优化test_pipepool3(argc, argv);return 0;
}

通过函数指针数组管理任务码,分配子进程完成任务功能.

可规定一个固定长度的4字节数组下标,写和读都以4字节为单位识别。 – 任务码

测试代码:

.hpp默认属于开源程序,因为声明和定义是写在一起的

//.hpp默认属于开源程序,因为声明和定义是写在一起的
#pragma once#include <iostream>
#include <ctime>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>using namespace std;#define TaskNum 3typedef void (*task_t)();//task_t 函数指针void Print()
{cout << "T am a print task" << endl;
}void Douwnload()
{cout << "T am a download task" << endl;
}void Flush()
{cout << "T am a flush task" << endl;
}task_t tasks[TaskNum];void LoadTask()
{srand(time(nullptr) ^ getpid());tasks[0] = Print;tasks[1] = Douwnload;tasks[2] = Flush;
}void ExcuteTask(int number)
{if (number < 0 || number > 2)return;tasks[number]();
}int SelectTask()
{return rand() % TaskNum;
}//回调函数 --- work本质也是任务
void work()
{while (true){int command = 0;int n = read(0, &command, sizeof(command));//重定向到标准输入去读取了if (n == sizeof(int)){cout << "pid is: " << getpid() << " handler task" << endl;ExcuteTask(command);}else if (n == 0){cout << "sub process: " << getpid() << " quit" << endl;break;}}
}

4、命名管道

4.1、原理

两个进程毫无关系怎么建立通信呢?

通过命名管道,一方写另一方读

那么怎么保证两个不相关的进程,能够准确打开同一个文件呢?

答:每一个文件都有一个唯一路径(具有唯一性)

mkfifo命令

用于创建一个命名管道 mkfifo myfifo得到一个p管道文件

建立一次进程通信:

echo “hello named pipe” >myfifo
cat myfifo

循环通信:

while :;do sleep 1;echo “hello named pipe” >>myfifo; done
cat < myfifo

4.2、创建命名管道函数的使用

创建管道文件
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char* pathname.mode_t mode);
删除指定的管道文件
#include <unistd.h>
int unlink(const char* pathname);

测试代码:
client.cc

#include "namedPipe.hpp"// write
int main()
{NamePiped fifo(comm_path, User);if (fifo.OpenForWrite()){std::cout << "client open namd pipe done" << std::endl;while (true){std::cout << "Please Enter> ";std::string message;std::getline(std::cin, message);fifo.WriteNamedPipe(message);}}return 0;
}

server.cc

#include "namedPipe.hpp"// server read: 管理命名管道的整个生命周期
int main()
{NamePiped fifo(comm_path, Creater);// 对于读端而言,如果我们打开文件,但是写还没来,我会阻塞在open调用中,直到对方打开// 进程同步if (fifo.OpenForRead()){std::cout << "server open named pipe done" << std::endl;sleep(3);while (true){std::string message;int n = fifo.ReadNamedPipe(&message);if (n > 0){std::cout << "Client Say> " << message << std::endl;}else if (n == 0){std::cout << "Client quit, Server Too!" << std::endl;break;}else{std::cout << "fifo.ReadNamedPipe Error" << std::endl;break;}}}return 0;
}

namedPipe.hpp

#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 = "./myfifo";
#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);}// const &: const std::string &XXX// *      : std::string * //输出型// &      : std::string & //输入输出型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;
};

5、system V的共享内存

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

5.1、原理

1.所有说到的操作都是由OS完成的
2.OS提供上面1,2步骤的系统调用,供用户进程A,B来进行调用 – 系统调用
3.AB,CD,EF…共享内存在系统中可以同时存在多份,每份可不同个数,不同对的进程同时进行通信。
4.OS注定了要对共享内存进行管理,–》先描述再组织 --》共享内存,不是简单的一段内存空间,也要有描述并管理共享内存的数据结构匹配的算法。
5.共享内存 = 内存空间(放数据) + 共享内存的属性

5.2、代码理解

测试代码:
shm目录 – 共享内存
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/whb/code/111/code/lesson22/4.shm";
const int gproj_id = 0x66;
const int gShmSize = 4097; // 4096*nclass Shm
{
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 RoleToString(int who){if (who == gCreater)return "Creater";else if (who == gUser)return "gUser";elsereturn "None";}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;}void DetachShm(void* shmaddr){if (shmaddr == nullptr)return;shmdt(shmaddr);std::cout << "who: " << RoleToString(_who) << " detach shm..." << std::endl;}public: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(){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;}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;}void DebugShm(){struct shmid_ds ds;int n = shmctl(_shmid, IPC_STAT, &ds);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:key_t _key;int _shmid;std::string _pathname;int _proj_id;int _who;void* _addrshm;
};#endif

shmnamedPipe.hpp

#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 = "./myfifo";
#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);}// const &: const std::string &XXX// *      : std::string *// &      : std::string & 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;
};

shmclient.cc

#include "Shm.hpp"
#include "shmnamedPipe.hpp"int main()
{// 1. 创建共享内存Shm shm(gpathname, gproj_id, gUser);shm.Zero();char* shmaddr = (char*)shm.Addr();sleep(3);// 2. 打开管道NamePiped fifo(comm_path, User);fifo.OpenForWrite();// 当成stringchar ch = 'A';while (ch <= 'Z'){shmaddr[ch - 'A'] = ch;std::string temp = "wakeup";std::cout << "add " << ch << " into Shm, " << "wakeup reader" << std::endl;fifo.WriteNamedPipe(temp);sleep(2);ch++;}return 0;
}

shmserver.cc

include "Shm.hpp"
#include "shmnamedPipe.hpp"int main()
{// 1. 创建共享内存Shm shm(gpathname, gproj_id, gCreater);char* shmaddr = (char*)shm.Addr();shm.DebugShm();// // 2. 创建管道// NamePiped fifo(comm_path, Creater);// fifo.OpenForRead();// while(true)// {//     // std::string temp;//     // fifo.ReadNamedPipe(&temp);//     std::cout << "shm memory content: " << shmaddr << std::endl;// }sleep(5);return 0;
}

5.3、共享内存的理解

申请一个systeam V版本的动态内存

int shmget(key_t key,size_t size,int shmflg);
参数:
1.size_t size — 创建的共享内存大小
2.int shmflg — 标志位(常用IPC_CREAT 和 IPC_EXEL) – 可以位图的形式传参
IPC_CREAT:如果你要创建的共享内存不存在,就创建,如果存在,获取该共享内存并返回。(总能获取到)
IPC_EXEL:单独使用没有意义,只有和IPC_CREAT组合使用才有意义。
IPC_CREAT | IPC_EXEL:如果你要创建的共享内存不存在,就创建,否则出错返回。(获取的是全新的shm)
3.key_t key — 由用户自定义的key值,设置为唯一标识符,只要具备唯一性即可
a、key_t key是什么?是由用户自定义的key值,用于设置为唯一标识符
b、为什么?因为让不同的进程看到同一个共享内存
c、怎么办?利用ftok随机设置key值,便于用户使用
返回值:
返回唯一的标识符
#include <sys/typse.h>
#include <sys/ipc.h>
key_t ftok(const char* pathname, int proj_id);

我们怎么确定,OS内的共享内存是否存在了呢?

答:struct Shm中会有一个标识共享内存的唯一性标识符

能让OS自动生成,标识符呢?

答:不能, 共享内存,不随着进程的结束而自动释放,需要手动释放。(或系统调用释放)

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

查看共享内存的信息:

ipcs -m
删除/释放指定的共享内存:
ipcrm -m shmid(返回给用户的标识符)

补充:IPC的知识

key VS shmid
key:属于用户形成的,属于内核使用的一个特定字段,具有唯一性,用户不能使用key来对shm进行管理,内核进行区分shm的唯一性(struct file*)
shmid:内核给用户返回的一个标识符,用来进行用户级对共享内存进行管理的id值(fd).

5.4、共享内存的相关接口

a、shmctl

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmod_ds *buf);
功能:
共享内存的控制,增删改查…
参数:
int shmid:内核给用户返回的id值
int cmd:对共享内存要执行的操作(常用IPC_RMID,删除/释放当前共享内存)
struct shmod_ds *buf:共享内存结构体的属性成员

b、shmat

#include <sys/typse.h>
#include <sys/shm.h>
void* shmat(int shmid, const void* shmaddr, int shmflg);
功能:将对应的地址空间挂接到共享内存中。
返回值:
地址空间中,共享内存的起始地址

c、shmdt

int shmdt(const void* shmaddr);
功能:取消挂接关联

6、消息队列

6.1、基本概念

一个进程,向另一个进程发送有类型的数据块的方式。结合之前的理解,msg_queue自带属性信息。
消息队列的生命周期也是随内核的,不随进程。

6.2、涉及的常用接口

消息队列常用接口
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
key_t ftok(const char* pathname,int proj_id);
int msgget(key_t key, int msgflg);
int msgctl(int msgid,int cmd,struct msgid_ds* buf);
int msgsnd(int msgid, const void* msgp, size_t msgsz, int msgflg);
int mshrcv(int msgid, void* msgp,size_t msgsz, long msgtyp,int msgflg);

7、信号量

7.1、5个概念

1.多个执行流(进程)都能看到的一份资源,称为共享资源
2.被保护起来的资源 – 称为临界资源 – 同步和互斥
3.互斥:任何时刻只能有一个进程访问共享资源。
4.资源 — 要被程序员访问 – 资源被访问,简单理解就是就是通过代码访问,代码 = 访问共享资源的代码(临界区) + 不访问共享资源的代码(非临界区)
5.所谓的对共享资源进行保护 – 临界资源 – 本质是对访问共享资源的代码进行代码。

7.2、对于信号量的理论理解

临界区 <=加锁/解锁=> 非临界区

1.用于保护临界资源,本质是一个计数器。

信号量的计数数量,标志对共享资源的预定机制。 担心超出资源量的个数,管理属性资源总数,限制资源不被多余预定。
类比:电影院系统
电影院:共享资源(临界资源)
买票:申请信号量
票数:信号量的初始值

申请信号量的本质:就是对公共资源的一种预定机制

申请信号量
访问共享资源
释放信号量

对共享资源整体的使用,其实不就是资源只有一个么?

1/0,二元信号量,互斥

既然信号量是一个计数器,可以使用一个全局的变量(如:gcongt)来充当对共享资源的保护吗?

答:不能,
1.因为全局变量不能被所有进程能够看到。
2.并且gcount++,不是原子的。

7.3、原子操作

所以IPC信号量:

1.与共享内存一样,使不同的进程之间都能看到同一个信号量(计数器),控制不同进程的同步或互斥
2.意味着信号量本身也属于共享资源
3.既然本身也属于临界资源,却要保护别的临界资源安全,前提是不是需要自己肯定是安全的呢?—提出原子操作
4.允许用户一次性申请多个信号量集 – 用数组来维护的。

步骤:

1.申请信号量
2.访问公共资源(共享内存)
3.释放信号量

对信号量(计数器)的操作,就被设置为原子操作:

P和V操作 – 安全的 – 原子性
– —》本身是安全的 P
++ —》本身是安全的 V

7.4、常用信号量的指令

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key,int nsems, int semflg);
int semctl(int semid,int semnum,int cmd,…);
int semop(int semid,struct sembuf* sops,size_t nsops);
查看信号量指令:
incs -s
删除指定信号量:
ipcrn -s semid

相关文章:

【Linux的文件篇章 - 管道文件】

Linux学习笔记---013 Linux的管道文件1、进程间通信1.1、进程为什么要通信&#xff1f;1.2、进程如何通信&#xff1f;1.3、进程通信的方式&#xff1f; 2、匿名管道2.1、理解一种现象2.2、基本概念和管道原理 3、管道的使用3.1、代码样例3.2、如何使用管道通信呢&#xff1f;3…...

C# 局部静态函数,封闭方法中的最佳选择

C# 局部静态函数&#xff0c;封闭方法中的最佳选择 简介特性 应用场景辅助计算递归与尾递归优化筛选与过滤操作查找与映射操作 生命周期静态局部函数 vs 普通局部函数性能封装性可读性 简介 C# 局部静态函数&#xff08;Local Static Functions&#xff09;是一种函数作用域内…...

【MySQL】MySQL 8.4.0 长期支持版(LTS)安装

就在2024年 “5.1” 节前&#xff0c;MySQL官方发布了8.4.0长期支持版&#xff08;LTS - Long Term Support&#xff09;。根据官方提供的文档&#xff0c;在本地虚拟机进行安装测试。 安装、配置和启动过程记录如下&#xff1a; 第一步&#xff0c;上传到安装包&#xff08;my…...

nest中的ORM

在 Nest.js 中执行 SQL 查询通常涉及使用 TypeORM 或 Sequelize 这样的 ORM&#xff08;对象-关系映射&#xff09;库。这些库使得在 Nest.js 应用程序中连接和操作 SQL 数据库变得更加简单和直观。 以下是一个使用 TypeORM 在 Nest.js 中执行 SQL 查询的示例代码&#xff1a;…...

TCP(Transmission Control Protocol,传输控制协议)如何保证数据的完整性?

TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;通过一系列机制来保证数据传输的可靠性和无错性&#xff0c;这些机制主要包括&#xff1a; 校验和&#xff1a;TCP报文段包含一个校验和字段&#xff0c;用于检测数据在传输过程中是否出错。…...

Numpy库介绍

NumPy&#xff08;Numerical Python的缩写&#xff09;是Python中用于科学计算的一个强大的库。它提供了高性能的多维数组对象&#xff08;即ndarray&#xff09;、用于处理这些数组的工具以及用于数学函数操作的函数。让我为你介绍一下它的一些主要功能&#xff1a; 1. 多维数…...

临时有事无法及时签字盖章?试试用契约锁设置“代理人”

遇到“领导休假中、在开重要会议、外出考察或者主任医生手术中等”一段时间内不方便或者无法及时签字盖章的情况怎么办&#xff1f;业务推进不了只能干等&#xff1f; 契约锁电子签及印控平台支持印章、签名“临时授权”、“代理签署”&#xff0c;实现指定人、指定时间段、指定…...

数据库权限管理

1.查看系统级权限&#xff08;global level) Select * from mysql.user\G; 2.查看数据库中所有表的权限 Select * from mysql.db\G 3.远程连接数据库 第一步在有数据库服务上的主机上&#xff1a;授权 grant all on *.* to root192.168.40.83 identified by Zxy20234; 第…...

如何创建一个 Django 应用并连接到数据库

简介 Django 是一个用 Python 编写的免费开源的 Web 框架。这个工具支持可扩展性、可重用性和快速开发。 在本教程中&#xff0c;您将学习如何为一个博客网站建立与 MySQL 数据库的初始基础。这将涉及使用 django-admin 创建博客 Web 应用程序的骨架结构&#xff0c;创建 MyS…...

【算法刷题day44】Leetcode:518. 零钱兑换 II、377. 组合总和 Ⅳ

文章目录 Leetcode 518. 零钱兑换 II解题思路代码总结 Leetcode 377. 组合总和 Ⅳ解题思路代码总结 草稿图网站 java的Deque Leetcode 518. 零钱兑换 II 题目&#xff1a;518. 零钱兑换 II 解析&#xff1a;代码随想录解析 解题思路 先遍历物品&#xff0c;再遍历背包。 代码…...

『51单片机』AT24C02[IIC总线]

存储器的介绍 ⒈ROM的功能⇢ROM的数据在程序运行的时候是不容改变的&#xff0c;除非你再次烧写程序&#xff0c;他就会改变&#xff0c;就像我们的书本&#xff0c;印上去就改不了了&#xff0c;除非再次印刷&#xff0c;这个就是ROM的原理。 注→在后面发展的ROM是可以可写可…...

Jenkins与Rancher的配合使用

Jenkins和Rancher是两个常用的DevOps工具&#xff0c;可以很好地配合使用来实现持续集成和持续部署。 Jenkins是一个开源的自动化构建工具&#xff0c;可以实现自动化的代码构建、测试和部署等一系列操作。可以通过Jenkins来触发构建任务&#xff0c;例如从代码仓库中拉取最新的…...

GIS入门,常用的多边形平滑曲线算法介绍和JavaScript的多边形平滑曲线算法库chaikin-smooth的实现原理和使用

前言 本章介绍一下常用的多边形平滑曲线算法及其使用案例。 多边形平滑算法通常用于图形处理或计算机图形学中,以使线条或曲线在连接处平滑过渡,而不出现明显的棱角或断裂。多边形平滑算法有多种实现方法,其中一些常见的有下面几种: 贝塞尔曲线插值(Bezier Curve Interpo…...

气膜体育馆内部的采光效果如何?—轻空间

气膜体育馆内部的采光效果如何&#xff1f;这是许多人对这种创新建筑的一个关键关注点。 首先&#xff0c;气膜体育馆的采光性非常好。阳光透过屋顶时以漫射光的方式进入室内&#xff0c;这种透射方式使得室内的光线柔和而均匀。从内部观察&#xff0c;整个屋顶就像一个连续的明…...

矩阵的对称正定性判决(复习)

文章目录 本科学的数学知识忘的太快了 如何判断一个实矩阵是否是对称正定 在线性代数中&#xff0c;一个实对称矩阵是否为正定可以通过以下方法判断&#xff1a; 对称性&#xff1a; 首先&#xff0c;确认矩阵是否对称&#xff0c;即矩阵的转置是否等于其本身。 特征值检查&…...

网络安全之DHCP详解

DHCP&#xff1a;Dynamic Host Configration Protocol 动态主机配置协议 某一协议的数据是基于UDP封装的&#xff0c;当它想确保自己的可靠性时&#xff0c;这个协议要么选确认重传机制&#xff0c;要么选周期性传输。 DHCP是确认重传&#xff0c;【UDP|DHCP】,当DHCP分配完地…...

【Proteus】LED呼吸灯 直流电机调速

1.LED呼吸灯 #include <REGX51.H> sbit LEDP2^0; void delay(unsigned int t) {while(t--); } void main() {unsigned char time,i;while(1){for(time0;time<100;time){for(i0;i<20;i){LED0;delay(time);LED1;delay(100-time);}}for(time100;time>0;time--){fo…...

今天遇到一个GPT解决不了的问题

问题描述 你好&#xff0c;postman的一个post请求&#xff0c;编辑器里面放了一个很长的json数据&#xff0c;报Tokenization is skipped for long lines for performance reasons. This can be configured via editor.maxTokenizationLineLength.&#xff0c;但是同样的数据&a…...

优化SQL的方法

来自组内分享&#xff0c;包含了比较常使用到的八点&#xff1a; 避免使用select * union all代替union 小表驱动大表 批量操作 善用limit 高效的分页 用连接查询代替子查询 控制索引数量 一、避免使用select * 消耗数据库资源 消耗更多的数据库服务器内存、CPU等资源。 消…...

库存管理系统开源啦

软件介绍 ModernWMS是一个针对小型物流仓储供应链流程的开源库存管理系统。该系统的开发初衷是为了满足中小型企业在有限IT预算下对仓储管理的需求。通过总结多年ERP系统研发经验&#xff0c;项目团队开发了这套适用于中小型企业的系统&#xff0c;以帮助那些有特定需求的用户。…...

【java】接口

什么是接口 接口当中存在的是对方法的定义&#xff0c;而不是对方法的具体实现。 为什么不实现这个方法呢&#xff1f; 继承的本质是代码的复用。当一个父类会经常被继承&#xff0c;并且子类都要自己实现方法时&#xff0c;父类中的方法就会显得累赘&#xff0c;并且占用了…...

Java中的类型转换

一、类型转换 对类型转换来说分为向上类型转换和向下类型转换&#xff1a; 向上类型转换是自动完成的&#xff0c;一般是小类型向大类型转换。在引用类型中是子类型向父类型转换。向下类型转换是强制完成的&#xff0c;一般是大类型向小类型转换。在引用类型中是父类型向子类…...

定义范围对PFMEA分析的重要性——SunFMEA软件

在进行PFMEA分析时&#xff0c;定义范围是一个至关重要的步骤。这是因为&#xff0c;通过明确分析的范围&#xff0c;可以确保团队关注到最关键、最可能影响产品质量的过程&#xff0c;从而更有效地识别和解决潜在问题。今天SunFMEA软件和大家一起讨论定义范围对PFMEA操作的重要…...

json返回工具类|世界协调时间(UTC)

一、问题 世界协调时间&#xff08;UTC&#xff09;是一个标准的时间参考&#xff0c;通常被用于跨越不同时区的时间标准。要将 UTC 时间转换为中国时间&#xff08;中国标准时间&#xff09;&#xff0c;你需要将时间加上8个小时&#xff0c;因为中国位于 UTC8 时区。 初中知…...

MySQL·内置函数

目录 函数 日期函数 案例1&#xff1a;创建一张表&#xff0c;记录生日 案例2&#xff1a;创建一个留言表 案例3&#xff1a;请查询在2分钟内发布的帖子 字符串函数 案例1&#xff1a; 获取emp表的ename列的字符集 案例2&#xff1a;要求显示exam_result表中的信息&am…...

vue根据文字动态判断溢出...鼠标悬停显示el-tooltip展示

使用自定义el- tooltip 组件 定义 Tooltip是一种小型弹出框,它显示有关特定页面元素的信息,例如按钮、链接或图标。Tooltip通常以半透明的气泡形式呈现,并出现在页面元素的旁边或下方。 它可以改善用户体验,使用户更容易理解页面元素的功能和意图。用户可以通过将鼠标悬停…...

使用Tkinter实现数据预测工具的GUI界面展示

如果构建好预测模型后&#xff0c;想将预测模型通过一个交互式的页面显示&#xff0c;可以通过下边两种方式实现。 本文中代码有详细解析注释&#xff0c;便不再如往期一样分开讲解了&#xff0c;有需要的朋友可以直接拿去使用&#xff0c;代码可以直接运行&#xff0c;把预测…...

机器学习笔记-22

终章 至此吴恩达老师的机器学习课程已经完成啦&#xff0c;总结一下&#xff1a; 1.监督学习的算法&#xff1a;线性回归、逻辑回归、神经网络和向量机 2.无监督学习的算法&#xff1a;K-Means、PCA、异常检测 3.推荐系统、大规模数据处理、正则化、如何评估算法 4.上限分析、…...

车间为什么选择蒸发式冷风机?

蒸发式冷风机具有以下特点&#xff1a; 节能环保&#xff1a;蒸发式冷风机不使用压缩机和化学制冷剂&#xff0c;而是通过水的蒸发来降低温度&#xff0c;因此它是无压缩机、无冷媒、无污染的环保型产品。降温效果显著&#xff1a;在较潮湿地区&#xff0c;它一般能达到5-9℃的…...

5分钟速通大语言模型(LLM)的发展与基础知识

✍️ 作者&#xff1a;哈哥撩编程&#xff08;视频号同名&#xff09; 博客专家全国博客之星第四名超级个体COC上海社区主理人特约讲师谷歌亚马逊演讲嘉宾科技博主极星会首批签约作者 &#x1f3c6; 推荐专栏&#xff1a; &#x1f3c5; 程序员&#xff1a;职场关键角色通识宝…...

vue项目开发流程

vue项目开发流程 环境配置 asdf plugin add nodejs asdf install nodejs 16.20.2创建项目 npm create vitelatest my-vue-app -- --template vue npm install npm run dev修改调试端口 修改vite.config.js,修改如下所示&#xff0c;添加server的host和port。 import { de…...

【Django学习笔记(十)】Django的创建与运行

Django的创建与运行 前言正文1、安装Django2、创建项目2.1 基于终端创建项目2.2 基于Pycharm创建项目2.3 两种方式对比 3、默认项目文件介绍4、APP5、启动运行Django5.1 激活App5.2 编写URL和视图函数对应关系5.3 启动Django项目5.3.1 命令行启动5.3.2 Pycharm启动5.3.3 views.…...

即时通讯技术文集(第37期):IM代码入门实践(Part1) [共16篇]

为了更好地分类阅读 52im.net 总计1000多篇精编文章&#xff0c;我将在每周三推送新的一期技术文集&#xff0c;本次是第37 期。 [- 1 -] 一种Android端IM智能心跳算法的设计与实现探讨&#xff08;含样例代码&#xff09; [链接] http://www.52im.net/thread-783-1-1.html […...

UV胶具有哪些特点和优势

1. 快速固化&#xff1a;UV胶在紫外线照射下能够迅速固化&#xff0c;固化时间通常在几秒钟到几分钟之间&#xff0c;大大提高了生产效率。 2. 高粘接强度&#xff1a;UV胶固化后&#xff0c;具有较高的粘接强度&#xff0c;能够在各种材料上实现可靠的粘接&#xff0c;提供持…...

python面试之mysql引擎选择问题

MySQL数据库提供了多种存储引擎&#xff0c;每种存储引擎有其特定的优势和场景适用。以下是几种常见的MySQL存储引擎及其特点&#xff1a; InnoDB&#xff1a; 支持事务&#xff0c;有回滚和提交事务的功能。 支持行级锁定&#xff0c;提供更高的并发。 支持外键约束&#…...

MT3031 AK IOI

思路&#xff1a;把每个节点存到堆&#xff08;大根堆&#xff09;里。 如果节点放入后总时间没有超过m则放入堆中&#xff1b;如果总时间超过了&#xff0c;就看堆头元素是否比新元素大。如果大&#xff0c;则删除堆头&#xff08;反悔贪心&#xff09;。 注意别忘记开long l…...

UE5自动生成地形二:自动生成插件

UE5自动生成地形二&#xff1a;自动生成插件 Polycam使用步骤 本篇主要讲解UE5的一些自动生成地形的插件 Polycam 此插件是通过现实的多角度照片自动建模生成地形数据&#xff0c;也是免费的。这里感谢B站up主古道兮峰的分享 Polycam网站 插件下载地址 插件网盘下载 提取码&a…...

二分图(染色法与匈牙利算法)

二分图当且仅当一个图中不含奇数环 1.染色法 简单来说&#xff0c;将顶点分成两类&#xff0c;边只存在于不同类顶点之间&#xff0c;同类顶点之间没有边。 e.g. 如果判断一个图是不是二分图&#xff1f; 开始对任意一未染色的顶点染色。 判断其相邻的顶点中&#xff0c;若未…...

ReactFlow的ReactFlow实例事件传参undefined处理状态切换

1.问题 ReactFlow的ReactFlow实例有些事件我们在不同的状态下并不需要&#xff0c;而且有时候传参会出现其它渲染效果&#xff0c;比如只读状态下我们不想要拖拉拽onEdgesChange连线重连或删除的功能。 2.思路 事件名称类型默认值onEdgesChange(changes: EdgeChange[]) >…...

Dockerfile 和 Docker Compose

Dockerfile 和 Docker Compose 是 Docker 生态系统中两个重要的组成部分&#xff0c;它们分别服务于不同的目的&#xff0c;但共同协助开发者和运维人员高效地管理和部署容器化应用。 Dockerfile Dockerfile 是一个文本文件&#xff0c;包含了构建 Docker 镜像所需的一系列指…...

多个文件 import 的相同模块里的对象

多个文件 import 的相同模块里的对象&#xff0c;是否永远都是同一个对象&#xff1f; 在store的index.js中 import vue from ‘vue’ import Vuex from ‘vuex’ 并配置有关对象 然后再app.vue中配置vm 在不同的文件中 import一个vue对象&#xff0c;在任何情况下&#…...

面试经典150题——验证回文串

面试经典150题 day25 题目来源我的题解方法一 双指针方法二 双指针 空间优化 题目来源 力扣每日一题&#xff1b;题序&#xff1a;125 我的题解 方法一 双指针 首先去除掉字符串中的无用字符&#xff0c;并将英文字符转换为小写&#xff0c;然后使用双指针来判断是否是回文串…...

YOLOv8的训练、验证、预测及导出[目标检测实践篇]

这一部分内容主要介绍如何使用YOLOv8训练自己的数据集&#xff0c;并进行验证、预测及导出&#xff0c;采用代码和指令的两种方式&#xff0c;参考自官方文档&#xff1a;Detect - Ultralytics YOLOv8 Docs。实践篇不需要关注原理&#xff0c;只需要把流程跑通就行&#xff0c;…...

光伏远动通讯屏的组成

光伏远动通讯屏的组成 远动通讯屏主要用于电力系统数据采集与转发&#xff0c;远动通讯屏能够采集站内的各种数据&#xff0c;如模拟量、开关量和数字量等&#xff0c;并通过远动通讯规约将必要的数据上传至集控站或调度系统。这包括但不限于主变和输电线路的功率、电流、电压等…...

营销H5测试综述

H5页面是营销域最常见的一种运营形式&#xff0c;业务通过H5来提供服务&#xff0c;可以满足用户对于便捷、高效和低成本的需求。H5页面是业务直面用户的端点&#xff0c;其质量保证工作显得尤为重要。各业务的功能实现具有通用性&#xff0c;相应也有共性的测试方法&#xff0…...

【C++随记4】C++二进制位操作运算符

在C中&#xff0c;二进制位操作运算符允许你直接对整数类型的变量的位进行操作。这些运算符包括&#xff1a; 按位与&#xff08;Bitwise AND&#xff09;: & 按位或&#xff08;Bitwise OR&#xff09;: | 按位异或&#xff08;Bitwise XOR&#xff09;: ^ 按位取反&…...

风电厂数字孪生3D数据可视化交互展示构筑智慧化电厂管理体系

随着智慧电厂成为未来电力企业发展的必然趋势&#xff0c;深圳华锐视点紧跟时代步伐&#xff0c;引领技术革新&#xff0c;推出了能源3D可视化智慧管理系统。该系统以企业现有的数字化、信息化建设为基础&#xff0c;融合云平台、大数据、物联网、移动互联、机器人、VR虚拟现实…...

大模型市场爆发式增长,但生成式AI成功的关键是什么?

进入2024年&#xff0c;大模型市场正在爆发式增长。根据相关媒体的总结&#xff0c;2024年1-4 月被统计到的大模型相关中标金额已经达到2023年全部中标项目披露金额的77%左右&#xff1b;其中&#xff0c;从项目数量来看&#xff0c;应用类占63%、算力类占21%、大模型类占13%、…...

leetcode LCR088.使用最小花费爬楼梯

思路&#xff1a;DP 这道题相对来说比较基础&#xff0c;但是有时候容易出错的一点就是在dp递推的时候&#xff0c;由于我们的思路是从最后一步向着初始状态推的&#xff0c;所以在编写程序的时候也容易就直接推着走了。其实实际上我们倒着想只是为了推理&#xff0c;真正要递…...

【DevOps】怎么提升Elasticsearch 的搜索性能

一、怎么提升Elasticsearch 搜索性能 提升 Elasticsearch (ES) 的搜索性能可以从多个角度进行优化&#xff0c;包括硬件选择、配置调整、查询优化等。以下是一些具体的方法和建议&#xff1a; 1. 硬件优化 使用 SSDs&#xff1a; 使用固态硬盘&#xff08;SSD&#xff09;而…...

Pencils Protocol Season 2 收官在即,Season 3 携系列重磅权益来袭

此前Scroll生态LaunchPad &聚合收益平台Pencils Protocol&#xff08;原Penpad&#xff09;&#xff0c;推出了首个资产即其生态代币PDD的Launch&#xff0c;Season 2活动主要是用户通过质押ETH代币、组件战队等方式&#xff0c;来获得Point奖励&#xff0c;并以该Point为依…...

面对《消费者告知法》严查与技术BUG频发,亚马逊卖家如何巧妙应对挑战?

五一假期期间&#xff0c;亚马逊大量发送《美国消费者告知法案》验证邮件通知&#xff0c;在这个本该是卖家们忙碌而喜悦的时刻&#xff0c;亚马逊平台上的卖家们却遭遇了一场前所未有的“灾难”——《消费者告知法》验证问题的爆发&#xff0c;以及随之而来的一系列技术BUG&am…...

06_机器学习算法_朴素贝叶斯

1. 朴素贝叶斯的介绍与应用 1.1 朴素贝叶斯的介绍 朴素贝叶斯算法(Naive Bayes, NB)是应用最为广泛的分类算法之一。它是基于贝叶斯定义和特征条件独立假设的分类方法。由于朴素贝叶斯法基于贝叶斯公式计算得到,有着坚实的数学基础,以及稳定的分类效率。NB模型所需估计的…...

DDNS配置详解

正文共&#xff1a;1111 字 8 图&#xff0c;预估阅读时间&#xff1a;1 分钟 前面配置了DDNS&#xff08;拨号有公网IP地址了&#xff0c;肯定要通过DDNS用起来啊&#xff01;&#xff09;&#xff0c;有不少小伙伴咨询具体的配置问题。为了方便大家深入理解DDNS的技术原理&am…...

Github 2024-05-12 php开源项目日报 Top10

根据Github Trendings的统计,今日(2024-05-12统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量PHP项目10Filament: 加速Laravel开发的完美起点 创建周期:1410 天开发语言:PHP协议类型:MIT LicenseStar数量:12228 个Fork数量:1990 次关…...

使用list和tuple

list list是有序集合&#xff0c;可以随时添加和删除其中元素 >>> classmates [Michael, Bob, Tracy] >>> classmates [Michael, Bob, Tracy]len()查看list元素个数&#xff1a; >>> len(classmates) 3索引访问从0开始&#xff1a; >>>…...