【Linux】进程间通信(一)
目录
- 一、进程间通信
- 1.1 进程间通信目的
- 1.2 理解进程间通信
- 1.3 进程间通信发展
- 1.4 进程间通信分类
- 二、管道
- 2.1 什么是管道
- 2.2 管道的原理
- 2.3 匿名管道
- 2.3.1 pipe函数
- 2.3.2 匿名管道的实现
- 2.3.3 匿名管道小结
- 2.3.3.1 匿名管道的四种情况
- 2.3.3.2 匿名管道的五种特性
- 2.3.4 匿名管道实现进程池
- 2.4 命名管道
- 2.4.1 指令级
- 2.4.1.1 创建命名管道
- 2.4.1.2 使用命名管道
- 2.4.2 代码级
- 2.4.2.1 创建命名管道
- 2.4.2.2 使用命名管道
- 结尾
由于进程间通信的篇幅有点大,所以进程间通信这部分将分为两篇文章进行讲述,本篇文章讲述进程间通信的目的、理解、发展及分类和管道相关的知识,下一篇文章将讲述system V共享内存、消息队列、信号量以及内核是如何看待IPC资源的。
一、进程间通信
1.1 进程间通信目的
为了多进程之间的协同,主要分为以下场景:
- 数据传输:一个进程需要将它的数据发送给另一个进程
- 资源共享:多个进程之间共享同样的资源。
- 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
- 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变
1.2 理解进程间通信
我们都知道进程具有独立性,那么进程1是如何将数据交给进程2的呢?进程1不可能直接将自己的数据交给进程2,这里举个例子来帮助大家理解:
小时候爸爸妈妈可能都吵过架,这时候他们认为自己都没有错,所以两人就不再交流。
当晚上妈妈做好了饭以后,就对你说:“儿/女儿,去叫你爸吃饭”
你就去跟你爸爸说:“爸,妈叫你去吃饭”,
你爸爸又对你说:“告你你妈,我不吃”,
你就回去给你妈妈说:“爸说他不吃饭”,
你妈就让你去给你爸说:“告诉你爸,爱吃不吃”。
在这个例子中,爸爸和妈妈并没有直接交流,而是通过你来进行数据的传递,这样就分别维护了他们两个人的独立性。这里的爸爸和妈妈就分别对应这进程1和进程2,虽然进程1和进程2不能直接将数据交给对方,但是可以通过操作系统也就是“你”来将数据传输给对方。
进程间通信的本质:就是让不同的进程看到同一份资源,这个资源通常由操作系统提供。
对应上面的例子来说,虽然父母不能直接交流,并且你也不在家,但是父母可以把你叫回来,让你做他们两个的中间人,相对于就是想你申请资源。
1.3 进程间通信发展
- 管道
- System V进程间通信
- POSIX进程间通信
1.4 进程间通信分类
管道
- 匿名管道pipe
- 命名管道
System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
二、管道
2.1 什么是管道
- 管道是Unix中最古老的进程间通信的形式。
- 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
2.2 管道的原理
当我们运行一个程序的时候,程序加载到内存变为进程,操作系统会为进程创建PCB,还会创建一个files_struct,PCB中有一个指针会指向files_struct,进程运行起来会默认打开三个标准流,当我们创建一个新文件时,操作系统会为其创建一个struct file结构体,files_struct中的文件描述符表中会有一个位置指向file对象,file对象会指向三个重要的内容,自己的inode对象,方法集和文件缓冲区。我们以该进程为父进程创建一个子进程,操作系统会以父进程为模版为子进程创建自己的PCB,又files_struct是进程的一部分,所以操作系统也会为子进程创建一个files_struct,并将父进程中files_struct的内容以浅拷贝的方式拷贝到子进程的files_struct中,所以父子进程就指向了同一个同一个文件,也就是不同的进程看到了同一份资源。如果说新建的文件是普通文件,最终操作系统会将文件缓冲区中的数据刷新到磁盘中,但是我们不想数据被刷新到磁盘中,我们想要通过文件将一个进程的数据交给另一个数据,所以这个文件就要是特殊的文件,这个文件要是内存级别的文件,我们称这个文件为管道文件。
管道未来只能是单向通信,管道必须有一端是读端,一端是写段,父进程就需要两个文件描述符分别代表读端和写端,因为只有这样在创建子进程时,子进程的文件描述符表中才回拥有读端和写端,若父进程只有读端,创建出的子进程也只有读端,不能形成单向信道,只有写端也是同样如此,所以我们需要将管道文件以读写的方式分别打开一次。父子进程都有了读写端后,只需要一个进程关闭读端,一个进程关闭写端,就能够形成单向信道。
文件的属性大部分是在inode中的,少部分存储在file结构体中。
这里我让一个进程分别以读写的方式打开文件,由于file结构体中有一个属性记录着文件的读写位置,所以以两种方式打开文件就会有两个file结构体,我们认定以读方式打开的文件的fd为读端,以写方式打开的文件为的fd写端。
这里我们以父进程为模版创建一个子进程,子进程的files_struct是浅拷贝父进程的files_struct而来的,所以父子进程下同一个fd指向的是同一个file结构体,通过这样的方式,父子进程就各自拥有了一个读写端。
file结构体对象中有一个引用计数,用来记录有多少个文件描述符指向我这个file结构体。
进程关闭读写端本质上就是将对应的指向file结构体的指针在文件描述符表中清除,再将对应的file结构体对象中的引用计数减一,当file结构体对象的引用计数为0时,这个文件才会被操作系统关闭,关闭一个文件与进程没有关系,进程只需要将files_struct中文件描述符表中对应的指针清除,再将file对象中的引用计数减一,就可以认为进程已经关闭对应的文件了,实际上文件是否被操作系统关闭,看到是file结构体中的引用计数,引用计数为0了自然就被关闭了,这个引用计数就很好的支撑了进程管理与文件管理的解耦。
这里我想让子进程写,父进程读,所以我们关闭父进程的写端,子进程的读端,由于file结构体中引用计数的存在,文件不会被关闭,file结构体也不会被清除,这样我们就就做到了父子进程各自维护一个file对象,指向同一个资源,这就是让不同进程看到同一份资源。
2.3 匿名管道
2.3.1 pipe函数
#include <unistd.h>
int pipe(int fd[2]);
功能:创建一无名管道
参数:fd:文件描述符数组,这里参数是输出型参数,返回读写对应的文件描述符,其中fd[0]表示读端,fd[1]表示写端
返回值:
- 成功返回0。
- 失败返回-1,并设置错误码
2.3.2 匿名管道的实现
#include <iostream>
#include <unistd.h>
#include <cassert>
#include <cstring>
#include <sys/types.h>
#include <sys/wait.h>#define MAX 1024using namespace std;int main()
{// 第一步,建立管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0);(void)n; // 在release版本调试下,assert会被注释掉,本句代码只做防止编译器告警的作用cout << "pipefd[0]:" << pipefd[0] << ", pipefd[1]:" << pipefd[1] << endl;// 第二步,创建子进程pid_t id = fork();if (id < 0){perror("fork");return 1;}if (id == 0){// child// w - 这里只是向管道文件中写入,并没有向显示屏中写入close(pipefd[0]);int cnt = 10;while (cnt){char message[MAX];snprintf(message,sizeof(message),"Hello father , i am child , pid : %d , cnt : %d" , getpid(),cnt);write(pipefd[1],message,strlen(message));cnt--;sleep(1);}exit(0);}// 第三步,关闭父子进程不需要的fd,形成单向信道// 父进程读,子进程写// father// r - 这里向管道中读取数据close(pipefd[1]);char buffer[MAX];while(1){ssize_t num = read(pipefd[0],buffer,sizeof(buffer)-1);// 我们这里默认读到字符串,如果缓冲区中的数据长度超过了1024// 我们需要在结尾处留一个位置,用来存放/0if(num > 0){buffer[num] = '\0';cout << getpid() << " , child say :" << buffer << " to me!" << endl; }}pid_t rid = wait(NULL);if (rid == id){cout << "wait success" << endl;}return 0;
}
通过上面代码的实现,我们可以通过一个程序向另一个程序动态的写入数据。之前我们学到过通过创建子进程的方式,父进程可以以继承的方式将数据交给子进程,但是不能将变化的数据交给子进程,并且子进程无论如何都是无法将自己的数据交给父进程的。
如果说写端写的很慢,导致管道中没有数据了,会发生什么情况呢?
这里我让写端每100秒向管道中写入数据,运行程序观察现象,我们发现进程“卡住”了,实际上这是读端在等待。所以说写端写的很慢,导致管道中没有数据了,读端必须等待,直到管道中有数据了为止。
如果说写端写的快一点,读端读的慢一点,会发生什么情况呢?
这里我让写端一直写,读端两秒钟读一次,运行程序观察现象,我们发现写端是一行一行写的,但是读端确实一下子将管道中所有的数据全部读出来,这就是匿名管道的特性之一:面相字节流,写端并不会因为你怎么写,就约束读端怎么读,读端想怎么读就怎么读。
如果说读端读的很慢,导致管道写满了,会发生什么情况呢?
这里我让写端一直写,读端200秒读一次,运行程序观察现象,我们发现写端在写了一段时间后就卡住不动了,这就是管道被写满了,在等读端将管道中的数据读走,所以如果说读端读的很慢,导致管道写满了,写端必须等待,直到有空间为止。
通过这里和上面的实验现象,我们发现读端和写端都会互相等待,这就是匿名管道的特性之一:默认给读写端提供同步机制。
那么一个管道是多大呢?
这里我让写端一直写,并且每次写一个字符,读端一直不读,查看写端向管道中写入多少次,运行程序观察现象,这里我们发现写端写入了65536次,每次写入一个字节,所以管道的大小是64KB。我们还可以通过命令ulimit -a
来查看管道的大小,我们发现管道的大小是4KB,实际上这并不是真正的管道大小。
如果写端关闭,读端一直读取,会出现什么情况呢?
这里我让写端每1秒写入一次,写入三次后就直接关闭,读端每1秒读一次一直读,每读一次就输出read的返回值,运行程序观察现象,我们发现写端写入三次以后,read的返回值变为了0,代表读到了文件结尾,也表示写端已经关闭,那么读端也没有存在的意义了,最好也一起关闭了。如果写端关闭,读端一直读取,读端会读到read的返回值为了0,代表读到了文件结尾,表示写端已经关闭。
如果我们不显示的将写端关闭,会发生什么情况呢?
与上面的情况一致,这也体现了管道的特性之一:管道的生命周期是跟随进程的。
如果读端关闭,写端一直写入会发生什么情况呢?
这里我让写端每1秒写入一次一直写,读端每一秒读一次,只读一次后关闭读端,关闭后休眠5秒,,运行程序和脚本观察结果。我们发现写端在读端关闭以后,就直接变为了僵尸状态,也就是说写端被进程杀掉了。所以如果读端关闭,写端一直写入,操作系统会直接杀掉写端对应的进程,操作系统是通过给进程发送SIGPIPE(13)信号将目标进程杀掉的。
父进程是可以查看子进程的退出信息的,这里我们使用wait函数获取子进程的退出信息,退出信息中的低八位就是子进程的退出信号,我们将其打印出来,发现确实是13号信号。
2.3.3 匿名管道小结
2.3.3.1 匿名管道的四种情况
- 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端向管道写入数据了)
- 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走管道的数据了)
- 写端关闭,读端一直读取,读端会读到read返回值为0,表示读到文件结尾
- 读端关闭,写端一直写入,os会直接杀掉写端进程,操作系统通过想目标进程发送SIGPIPE(13)来终止程序。
2.3.3.2 匿名管道的五种特性
- 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用于父子,仅限于此
- 匿名管道,默认给读写端要提供同步机制
- 管道是面向字节流的
- 管道的生命周期是随进程的
- 管道是单向通信的,半双工通信的一种特殊情况
2.3.4 匿名管道实现进程池
需要注意下面第一种建立进程间通信的前提时,会导致如下图除最后一个创建的管道只有一个父进程的fd指向写端,一个子进程的fd指向读端,前面的所有管道都有一个子进程的fd指向读端,一个父进程的fd和多个子进程的fd指向写端所以在回收资源的时候需要注意一下回收顺序,要么先将所有管道的写端全部关闭都再回收子进程,要么从后往前边关闭管道的写端,边回收子进程。
而下面第二种建立进程间通信的前提时,会保证所有管道都有一个子进程的fd指向读端,一个父进程的fd和多个子进程的fd指向写端。使用任意方式关闭写端和回收子进程。
// ProcessPool.cpp
#include <iostream>
#include <unistd.h>
#include <vector>
#include <string>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"using namespace std;const int num = 5;
static int number = 1;class channel
{
public:int _ctrlfd; // 写端描述符int _workerid; // 对应写入的进程string _name; // 管道的名称
public:channel(int ctrlfd, int workerid): _ctrlfd(ctrlfd), _workerid(workerid){_name = "channel-" + to_string(number++);}
};void Work()
{while (true){int command = 0;ssize_t n = read(0, &command, sizeof(command));if (!init.CheckSafe(command))continue;if (n == sizeof(command))init.RunTask(command);else if (n == 0)break;else{// nothing to do}}cout << "child quit" << endl;
}// 传参形式建议
// 输入参数:const &
// 输出参数:*
// 输入输出参数:&// 创建方式1会导致除最后一个创建的管道只有一个父进程的fd指向写端,一个子进程的fd指向读端
// 前面的所有管道都有一个子进程的fd指向读端,一个父进程的fd和多个子进程的fd指向写端
// 所以在回收资源的时候需要注意一下回收顺序
// void CreateChannels(vector<channel> *channels)
// {
// // 定义并创建管道
// for (int i = 0; i < num; i++)
// {
// int pipefd[2] = {0};
// int n = pipe(pipefd);
// assert(n == 0);
// (void)n;// // 创建子进程// pid_t id = fork();
// assert(id >= 0);// // 关闭不需要的fd,形成单向管道
// if (id == 0)
// {
// // child// close(pipefd[1]);
// dup2(pipefd[0], 0); // 这里输入重定向,就是为了让Work向0中读取,让Work少一个参数,仅此而已
// Work();// exit(0);
// }// // father
// close(pipefd[0]);
// channels->push_back(channel(pipefd[1], id));
// }
// }// 建方式2保证了所有管道都有一个子进程的fd指向读端,一个父进程的fd和多个子进程的fd指向写端
void CreateChannels(vector<channel> *channels)
{vector<int> oldfd;// 定义并创建管道for (int i = 0; i < num; i++){int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0);(void)n;// 创建子进程pid_t id = fork();assert(id >= 0);// 关闭不需要的fd,形成单向管道if (id == 0){// child// 关闭当前文件的写端close(pipefd[1]);// 关闭之前管道文件的写端if(!oldfd.empty()){for(auto& fd : oldfd){close(fd);}}dup2(pipefd[0], 0); // 这里输入重定向,就是为了让Work向0中读取,让Work少一个参数,仅此而已Work();exit(0);}// fatherclose(pipefd[0]);channels->push_back(channel(pipefd[1], id));oldfd.push_back(pipefd[1]);}
}void Print(const vector<channel> &channels)
{for (const auto &channel : channels){cout << channel._name << " " << channel._ctrlfd << " " << channel._workerid << endl;}
}const bool g_always_loop = 1;// 一直执行任务,则num为-1,否则num为执行任务的次数
void SendCommand(const vector<channel> &channels, int flag, int num = -1)
{int pos = 0;while (true){// 1.选择任务 --- 随机int command = init.SelectTask();// 2.选择信道(进程)--- 轮询 --- 将任务较为平均的交给进程channel c = channels[pos++];pos %= channels.size();cout << "sent command " << init.ToDesc(command) << "[" << command << "]"<< " in " << c._name << " worker is:" << c._workerid << endl;// 3.发送任务write(c._ctrlfd, &command, sizeof(command));// 4.判断是否退出if (flag != g_always_loop){num--;if (num <= 0){break;}}sleep(1);}cout << "SendCommand done..." << endl;
}void ReleaseChannel(const vector<channel> &channels)
{// 每个管道都只有一个fd指向写端时,可以使用该方式回收资源for (const auto &channel : channels){close(channel._ctrlfd);pid_t rid = waitpid(channel._workerid, nullptr, 0);}// 在多个fd指向读端的情况下,回收资源的方法2// 逆序边关闭写端,再回收对应的子进程// for (int i = channels.size() - 1; i >= 0; i--)// {// close(channels[i]._ctrlfd);// pid_t rid = waitpid(channels[i]._workerid, nullptr, 0);// }// 在多个fd指向读端的情况下,回收资源的方法1// 先将所有写端全部关闭,就可以随意回收子进程了// for (const auto &channel : channels)// {// close(channel._ctrlfd);// }// for (const auto &channel : channels)// {// pid_t rid = waitpid(channel._workerid, nullptr, 0);// if (rid == channel._workerid)// {// cout << "wait child " << rid << " success" << endl;// }// }
}
int main()
{vector<channel> channels;// 创建信道创建进程CreateChannels(&channels);// 开始完成任务// SendCommand(channels,g_always_loop);SendCommand(channels, !g_always_loop, 10);// 回收资源,释放管道,关闭写端,等待回收子进程ReleaseChannel(channels);// Print(channels);// sleep(10);return 0;
}
// Task.hpp
#include <iostream>
#include <vector>
#include <functional>using namespace std;typedef function<void()> task_t;void Download()
{cout << "我是一个下载任务" << " 处理者:" << getpid() << endl;
}void PrintLog()
{cout << "我是一个打印日志的任务" << " 处理者:" << getpid() << endl;
}void PushVideoStream()
{cout << "这是一个推送视频流的任务" << " 处理者:" << getpid() << endl;
}class Init
{
public:const static int g_download_code = 0;const static int g_printlog_code = 1;const static int g_pushvideostream_code = 2;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){return code >= 0 && code < tasks.size();}void RunTask(int command){tasks[command]();}int SelectTask(){return rand() % tasks.size();}string ToDesc(int command){switch (command){case g_download_code:{return "Download";break;}case g_printlog_code:{return "PrintLog";break;}case g_pushvideostream_code:{return "PushVideoStream";break;}default:{return "Unkown";break;}}}
};Init init;
2.4 命名管道
匿名管道只能让具有血缘关系的进程进行进程间通信,如果我们想让两个毫不相关的进程进行进程间通信,那就只能使用命名管道了。
2.4.1 指令级
2.4.1.1 创建命名管道
Linux操作系统中有一个指令叫做mkfifo,mkfifo+管道名就可以创建对应的管道了。
2.4.1.2 使用命名管道
当我们使用echo指令向显示屏中写入一段数据后,我们发现这段数据确实显示到了显示屏上,当我们向命名管道中写入一段数据时,我们发现进程卡住了,使用另一台机器发现fifo文件的大小为0,再使用cat指令从命名管道中读取数据,发现将刚刚写入的命名管道中的数据读取出来了。命名管道文件大小为0的原因是管道属于内存级别的文件,并不会将数据写入到磁盘中。
当我们使用echo指令向管道文件中写入数据时,它就变为了一个进程,当使用cat指令向命名管道文件中读取数据的时候,它也变为了一个进程,这样我们就让两个进程看到了同一份资源,并且这两个进程毫无关系。
我们如何保证这两个进程会看到同一份资源的呢?
是通过路径来保证的,由于路径具有唯一性,所以路径+文件名就可以唯一的让不同进程看到同一份资源。
2.4.2 代码级
2.4.2.1 创建命名管道
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
功能:mkfifo 函数可以在Linux操作系统中用于创建命名管道。
参数:
- filename :是一个指向以 null 结尾的字符串的指针,表示要创建的命名管道的文件名。
- mode :是一个位掩码,指定了新创建的命名管道文件的权限。这些权限位与 chmod 和 stat 系统调用中使用的权限位相同。
返回值:
- 成功时,它返回 0。
- 如果失败,则返回 -1 并设置全局变量 errno 以指示错误类型。
2.4.2.2 使用命名管道
使用管道需要两个进程,这里我们就写两个源文件,一个server.cpp代表服务端,一个client.cpp代表客户端,具体实现大家可以看一下下面的代码。
这里为了强调进程间通信的本质就是让不同进程看到同一份资源,我这里创建一个头文件comm.h,头文件中记录着命名管道的文件名。
// comm.h
#pragma once#define FILENAME "fifo"
// Makefile同时编译形成多个可执行程序.PHONY:all
all:server clientserver:server.cppg++ $^ -o $@ -std=c++11
client:client.cppg++ $^ -o $@ -std=c++11.PHONY:clean
clean:rm -f server client fifo
// server.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "comm.h"using namespace std;// 创建命名管道
bool MakeFifo()
{int n = mkfifo(FILENAME, 0666);if (n < 0){cerr << "errno" << errno << "strerror" << strerror(errno) << endl;return false;}cout << "create fifo success..." << endl;return true;
}int main()
{
Start:int rfd = open(FILENAME, O_RDONLY);// 有命名管道直接打开,没有则创建,创建完后还需要再一次打开管道if (rfd < 0){cerr << "errno:" << errno << " strerror:" << strerror(errno) << endl;if (MakeFifo())goto Start;elsereturn 1;}cout << "open fifo success... read" << endl;while (1){cout << "Client Say# ";char buffer[1024];// 将管道中的数据读入到缓冲区中ssize_t num = read(rfd, buffer, sizeof(buffer) - 1);// 我们默认向管道中写入的是字符串,所以需要在结尾处加上0if (num > 0){buffer[num] = 0;}// num == 0 代表写端已经关闭,读端也没必要存在了,关闭读端else if (num == 0){cout << endl;cout << "client close , server close too..." << endl;break;}cout << buffer << endl;}close(rfd);cout << "close fifo success...read" << endl;return 0;
}
// client.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <string>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "comm.h"using namespace std;int main()
{// 客户端以只写的方式打开管道int wfd = open(FILENAME, O_WRONLY);if (wfd < 0){// 打开失败则退出进程cerr << "errno" << errno << "strerror" << strerror(errno) << endl;return 1;}cout << "open fifo success... write" << endl;while (1){string message;cout << "Please Enter# ";// 不以空格为结束符的方式,写入一段数据到字符串messagegetline(cin, message);// 将字符串中的数据写入到管道中ssize_t n = write(wfd, message.c_str(), message.size());if (n < 0){// 写入失败则退出进程cerr << "errno" << errno << "strerror" << strerror(errno) << endl;return 2;}}// 关闭写端close(wfd);cout << "close fifo success...write" << endl;return 0;
}
我们使用make以后就会多出来两个可执行程序,当我们使用一号机器运行server(服务端)时,若当前目录下有命名管道就直接打开,否则先创建再打开,当我们再使用二号机器运行客户端时,就形成了单向通信的管道了。当我们在客户端中输入信息时,服务端就可以读取到客户端输入的信息了。
结尾
如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!🌹🌹
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹
相关文章:
【Linux】进程间通信(一)
目录 一、进程间通信1.1 进程间通信目的1.2 理解进程间通信1.3 进程间通信发展1.4 进程间通信分类 二、管道2.1 什么是管道2.2 管道的原理2.3 匿名管道2.3.1 pipe函数2.3.2 匿名管道的实现2.3.3 匿名管道小结2.3.3.1 匿名管道的四种情况2.3.3.2 匿名管道的五种特性 2.3.4 匿名管…...
Fama MacBeth两步法与多因子模型的回归检验
Fama MacBeth两步法与多因子模型的回归检验 – 潘登同学的因子投资笔记 本文观点来自最近学习的石川老师《因子投资:方法与实践》一书 文章目录 Fama MacBeth两步法与多因子模型的回归检验 -- 潘登同学的因子投资笔记 多因子回归检验时序回归检验截面回归检验Fama–…...
Postman[4] 环境设置
作用:不同的环境可以定义不同的参数,在运行请求时可以根据自己的需求选择需要的环境 1.创建Environment 步骤: Environment-> ->命名->添加环境变量 2.使用Environment 步骤:Collection- >右上角选择需要的环境...
【paddle】初次尝试
张量 张量是 paddlepaddle, torch, tensorflow 等 python 主流机器学习包中唯一通货变量,因此应当了解其基本的功能。 张量 paddle.Tensor 与 numpy.array 的转化 import paddle as paddle import matplotlib.pyplot as plt apaddle.to_t…...
开源架构中的数据库选择优化版
上一篇文章推荐: 开源架构学习指南:文档与资源的智慧锦囊(New) 我管理的社区推荐:【青云交社区】和【架构师社区】 推荐技术圈福利社群:点击快速加入 开源架构中的数据库选择优化版 一、引言二、关系型开源…...
Echarts+vue电商平台数据可视化——webSocket改造项目
websocket的基本使用,用于测试前端能否正常获取到后台数据 后台代码编写: const path require("path"); const fileUtils require("../utils/file_utils"); const WebSocket require("ws"); // 创建WebSocket服务端的…...
【网络安全实验室】SQL注入实战详情
如果额头终将刻上皱纹,你只能做到,不让皱纹刻在你的心上 1.最简单的SQL注入 查看源代码,登录名为admin 最简单的SQL注入,登录名写入一个常规的注入语句: 密码随便填,验证码填正确的,点击登录…...
【信息系统项目管理师】第14章:项目沟通管理过程详解
更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 一、规划沟通管理1、输入2、工具与技术3、输出二、管理沟通1、输入2、工具与技术3、输出三、监督沟通1、输入2、工具与技术3、输出一、规划沟通管理 定义:规划沟通管理是基于每个干系人或干系人群体的信息需求…...
YOLOv5部署到web端(flask+js简单易懂)
文章目录 前言最终实现效果图后端实现 主界面检测函数检测结果显示 前端实现 主界面(index.html)显示图片界面 总结 前言 最近,老板让写一个程序把yolov5检测模型部署到web端,在网页直接进行目标检测。经过1个星期的努力,终于实…...
什么是自治系统和非自治系统
自治系统 自治系统的特征是其状态方程不依赖于时间。举个简单的例子,考虑一阶常微分方程: d x d t − x \frac{dx}{dt} -x dtdx−x 这是一个经典的指数衰减过程,其中状态 (x) 随时间 (t) 衰减。这个系统是自治的,因为它的演…...
使用 CSS 的 `::selection` 伪元素来改变 HTML 文本选中时的背景颜色
定义 ::selection 伪元素: 在你的 CSS 文件中,添加 ::selection 伪元素,并设置 background-color 属性来改变选中文本的背景颜色。 示例代码: ::selection {background-color: yellow; /* 你可以根据需要更改颜色 */color: black…...
从0入门自主空中机器人-3-【环境与常用软件安装】
关于本课程: 本次课程是一套面向对自主空中机器人感兴趣的学生、爱好者、相关从业人员的免费课程,包含了从硬件组装、机载电脑环境设置、代码部署、实机实验等全套详细流程,带你从0开始,组装属于自己的自主无人机,并让…...
jmeter分布式启动
https://www.cnblogs.com/qtclm/p/11082081.html 1、代理机:输入“ipconfig”,找到IP地址,在Jmeter/bin/jmeter.properties设置remote host 启动jmeter server 1、控制机:输入“ipconfig”,找到IP地址,在J…...
【Linux】HTTP cookie与session
在登录B站时,有登录和未登录两种状态, 问题:B站是如何认识我这个登录用户的?问题:HTTP是无状态、无连接的,怎么能够记住我? HTTP协议是无状态、无连接的。比如客户端(浏览器&#…...
20. 【.NET 8 实战--孢子记账--从单体到微服务】--简易权限--补充--自动添加接口地址
在同学学习过程,部分同学向我反馈说每次新增接口都要在接口表里手动添加一条接口很麻烦,因此我把项目代码做了一个改动,使我们不需要手动添加,每次项目运行起来后就会自动把新的接口地址添加进去。 一、实现 首先,我…...
[Linux] 服务器CPU信息
(1)查看CPU信息(型号) cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c输出:可以看到有128个虚拟CPU核心,型号是后面一串 128 Intel(R) Xeon(R) Platinum 8336C CPU 2.30GHz(2&…...
java_使用阿里云oss服务存储图片
什么情况下可以使用阿里云oss服务存储图片? 对图片的访问速度有高要求时使用,方便用户快速的(比如在网页页面中)访问到图像 参考:41 尚上优选项目-平台管理端-商品信息管理模块-阿里云OSS介绍_哔哩哔哩_bilibili 1.…...
Dali 1.1.4 | 解锁版AI图像生成器,无限生成
Dali是一款先进的AI图像生成器应用程序,能够根据您的描述生成不同风格的独特图像。它不仅限于生成艺术作品,还可以创建创新的纹身设计、独一无二的标志以及超写实照片。该软件使用尖端技术,将想象力转化为现实,提供迷人的数字艺术…...
快手视频不让下载怎么保存到相册
快手,作为国内领先的短视频平台之一,吸引了无数用户发布创意视频、分享生活点滴。随着短视频版权保护和用户隐私问题的日益严重,越来越多的视频内容在平台内都采取了“不让下载”的限制。面对这一情况,很多用户都希望能够保存自己…...
Linux环境下CUDA与对应版本CuDNN的安装指南
转载:Linux环境下CUDA与对应版本CuDNN的安装指南-百度开发者中心...
mybatisPlus打印sql配置
MyBatis-Plus 提供了方便的配置方式来打印 SQL 查询语句,以便进行调试和性能分析。可以通过配置 log 来输出 SQL 语句以及执行的参数。 方法 1:通过 application.properties 或 application.yml 配置打印 SQL 可以通过配置 application.properties 或 a…...
InstructGPT:基于人类反馈训练语言模型遵从指令的能力
大家读完觉得有意义记得关注和点赞!!! 大模型进化树,可以看到 InstructGPT 所处的年代和位置。来自 大语言模型(LLM)综述与实用指南(Amazon,2023) 目录 摘要 1 引言 …...
曾仕强解读《易经》
曾仕强对《易经》的解读内容丰富、深入浅出,以下是一些主要方面: 讲解《易经》基本原理 - 阴阳之道:曾仕强将阴阳比作白天与黑夜、男人与女人等,指出阴阳看似对立,实则相辅相成,强调为人处世要把握阴阳…...
http报头解析
http报文 http报文主要有两类是常见的,第一类是请求报文,第二类是响应报文,每个报头除了第一行,都是采用键值对进行传输数据,请求报文的第一行主要包括http方法(GET,PUT, POST&#…...
什么是Sight Words(信号词)
🧡什么是Sight Words(信号词) 简单来说,Sight Words就是我们在日常英语中常用的一些基本词汇。可以把它想象成是学练英语的“基础词汇”,这些词在各种考试中经常出现,也是在生活中必不可少的。 …...
tiny RISCV项目学习
参考视频:第1期 开发环境准备 —— RISC-V囫囵吞枣式学习_哔哩哔哩_bilibili 项目地址:tinyriscv: 一个从零开始写的极简、非常易懂的RISC-V处理器核。...
LeetCode 力扣 热题 100道(二十七)除自身以外数组的乘积(C++)
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂…...
Kotlin在医疗大健康域的应用实例探究与编程剖析(上)
一、引言 1.1 研究背景与意义 在当今数字化时代,医疗行业正经历着深刻的变革。随着信息技术的飞速发展,尤其是人工智能、大数据、物联网等新兴技术的广泛应用,医疗行业数字化转型已成为必然趋势。这种转型旨在提升医疗服务的效率和质量,优化医疗资源配置,为患者提供更加…...
【Spring】事务
在软件开发中,事务确保一组操作要么全部成功,要么全部失败,这对于数据库操作尤为重要,因为任何单一操作的失败都可能导致数据不一致。Spring 事务管理通过 Transactional 注解实现,能够轻松地在数据层和业务层维护数据…...
canvas+fabric实现时间刻度尺(二)
前言 我们前面实现了时间刻度尺,鼠标移动显示时间,接下来我们实现鼠标点击某个时间进行弹框。 效果 实现 1.监听鼠标按下事件 2.编写弹框页面 3.时间转换 <template><div><canvas id"rulerCanvas" width"1200"…...
IPv6的过度技术
如何界定手动与自动? 主要是隧道目标地址能否自动获取 👯1. 双栈 必须支持IPv4和IPv6协议 链接双栈网络的接口必须同时配置v4和v6地址 路由器能够根据二层标记识别协议,type:0x0800代表IPV4,type:0x…...
介绍 Apache Spark 的基本概念和在大数据分析中的应用。
Apache Spark是一个开源的大数据处理框架,可用于高速处理和分析大规模数据集。它可以在分布式集群上运行,并且具有内存计算的能力,因此可以比传统的批处理框架更快地处理数据。 在Spark中,数据被表示为弹性分布式数据集ÿ…...
VA01/VA02检查增强
VA01/VA02检查增强 一、增强描述 VA01/VA02创建或修改SO时候,在点击“保存”按钮的节点,客户需求对一些约束条件进行检查,此处以 SO行项目对应的“利润中心”字段必输为例。通过查询更多的增强:SPRO–销售和分销–系统修正–用户…...
基于SpringBoot和Leaflet的全球机场空间分布可视化实战
目录 前言 一、航空机场的空间模型 1、空间表简介 2、数据查询 二、机场WebGIS空间分布可视化 1、后台数据查询 2、Leaflet页面开发 三、WebGIS分析 1、全球航空格局 2、我国机场影像 四、总结 前言 时光轻轻挥别2024,来到了2025年。在崭新的2025年里&am…...
FPGA交通灯实现
1 原理 FPGA(现场可编程门阵列)交通灯实现原理主要是基于硬件描述语言(如VHDL或Verilog)编程,通过FPGA内部的逻辑单元和寄存器来实现交通灯的控制功能。以下是对FPGA交通灯实现原理的详细解释: 一、交通灯的基本功能 交通灯的主要功能包括红灯、黄灯和绿灯的显示,以及…...
厦门大学联合网易提出StoryWeaver,可根据统一模型内给定的角色实现高质量的故事可视化
厦门大学联合网易提出StoryWeaver,可以根据统一模型内给定的角色实现高质量的故事可视化。可根据故事文本生成与之匹配的图像,并且确保每个角色在不同的场景中保持一致。本文的方法主要包括以下几个步骤: 角色图构建:设计一个角色…...
【Rust自学】8.1. Vector
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(・ω・) 8.1.0. 本章内容 第八章主要讲的是Rust中常见的集合。Rust中提供了很多集合类型的数据结构&…...
华为OD机试真题---服务器广播
华为OD机试中的“服务器广播”题目是一个经典的算法问题,通常涉及图论和连通分量的概念。以下是对该题目的详细解析: 一、题目描述 服务器之间可以通过网络进行连接,连接方式包括直接相连和间接连接。给出一个NN的数组(矩阵&…...
又一年。。。。。。
2024,浑浑噩噩的一年。 除了100以内的加减法(数据,数据,还是数据。。。。。。),似乎没做些什么。 脸盲症越来越重的,怕是哪天连自己都不认得自己的了。 看到什么,听到什…...
【JAVA高级篇教学】第六篇:Springboot实现WebSocket
在 Spring Boot 中对接 WebSocket 是一个常见的场景,通常用于实现实时通信。以下是一个完整的 WebSocket 集成步骤,包括服务端和客户端的实现。本期做个简单的测试用例。 目录 一、WebSocket 简介 1. 什么是 WebSocket? 2. WebSocket 的特…...
Kotlin在医疗大健康域的应用实例探究与编程剖析(下)
四、Kotlin医疗编程实例分析 4.1 移动医疗应用实例 4.1.1 患者健康监测应用 在当今数字化医疗时代,患者健康监测应用为人们提供了便捷的健康管理方式。利用Kotlin开发的患者健康监测应用,能够实时采集患者的各类生理数据,如心率、血压、血氧饱和度等,并通过直观的可视化…...
Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(3):配置备用数据库
Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(3):配置备用数据库 目录 Oracle Dataguard(主库为 Oracle 11g 单节点)配置详解(3):配置备用数据库一、…...
LeetCode算法题——移除元素
题目描述 给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作࿱…...
七大设计原则之开闭原则
目录 一、什么是开闭原则? 二、如何做到开闭原则? 1、面向接口或抽象类编程 2、依赖注入 3、单一职责原则 三、是不是为了满足开闭原则就要一味的追求代码的扩展性? 一、什么是开闭原则? 相信很多人都听说过这个原则&#x…...
【stm32+K210项目】基于K210与STM32协同工作的智能垃圾分类系统设计与实现(完整工程资料源码)
视频效果演示: 基于K210与STM32协同工作的智能垃圾分类系统设计与实现 目录: 目录 视频效果演示: 目录: 项目简介: 一、设计目的: 1.1 项目背景 1.2 设计意义: 二、硬件部分: 2.1 st…...
Ps:创建数据驱动的图像
在设计实践中,常常需要处理大量内容变化但设计格式统一的任务,例如批量生成名片、工作证、学生证、胸牌、奖状或证书甚至图册。这些工作如果逐一手动制作,不仅耗时费力,还容易出错。 为解决这一问题,Photoshop 提供了强…...
git的全通路线介绍
一、关系 1.git是代码版本管理工具,即可将项目切换到任意版本。 2.github与gitee是基于git技术构建的远程仓库网站。github是国外建立的,资源更丰富;gitee是国内建立的,免费功能更多。 3.gitlab与github类似,只不过…...
R1-3学习打卡
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 RNN心脏病识别 导入数据数据预处理标准化模型训练模型评估个人总结 import tensorflow as tfgpus tf.config.list_physical_devices("GPU")…...
Vue.js组件开发-实现无感刷新Token
在Vue.js应用中,实现无感刷新Token涉及到在用户的会话Token即将过期或已经过期时自动获取新的Token,而不影响用户的操作体验。需要通过拦截器(interceptors)来处理API请求,并在检测到Token过期或无效时自动进行刷新。 …...
可编辑31页PPT | 大数据湖仓一体解决方案
荐言分享:在当今数字化时代,大数据已成为企业决策和业务优化的关键驱动力。然而,传统的数据处理架构,如数据仓库和数据湖,各自存在局限性,难以满足企业对数据高效存储、灵活处理及实时分析的综合需求。因此…...