Linux 进程间通信 管道系列: 匿名和命名管道,自定义shell当中命令行管道的模拟实现

Linux 进程间通信1: 匿名和命名管道以及进程池的实现

  • 一.进程间通信的介绍
    • 1.为什么要进程进程间通信?
    • 2.什么是进程间通信
    • 3.进程间通信的具体做法
  • 二.管道
    • 1.从文件的角度理解什么是管道?
  • 三.匿名管道
    • 1.验证代码
    • 2.四种情况
      • 1.写端不写,且不退
      • 2.读端不读,且不退
      • 3.写端不写,退了
      • 4.读端不读,退了
      • 5.小小总结
    • 3.五种特性
    • 4.理解命令行管道
  • 四.命名管道
    • 1.理论
      • 1.回顾匿名管道的原理
      • 2.命名管道理论
    • 2.系统调用接口的介绍与使用
      • 1.介绍
      • 2.使用
    • 3.代码编写
      • 1.Common.hpp
      • 2.pipeServer.cpp
      • 3.pipeClient.cpp
      • 4.makefile
    • 4.动图演示
  • 五.自定义shell当中添加命令行管道的功能
    • 1.前言
    • 2.思路
      • 1.如何创建管道与进程
      • 2.预处理
    • 3.实现
    • 4.演示
    • 5.代码

一.进程间通信的介绍

1.为什么要进程进程间通信?

因为有些情况下需要进程完成以下任务:
在这里插入图片描述
而我们知道进程之间是不能进行"数据"的直接传递的,因为进程具有独立性
因此才有了我们今天要谈论的进程间通信

2.什么是进程间通信

通俗点讲,进程间通信就是一个进程能够把自己的数据交给另一个进程

进程间进程通信就是进行数据的交互,那么数据存放在哪里呢?
下面我们来分析一下
在这里插入图片描述
因此,关于进程间通信的一个非非非非非非常重要的结论就得出了:

进程间通信的本质是让不同的进程,看到同一份资源(一般都是要由OS提供)

3.进程间通信的具体做法

因为OS提供的"数据存放的空间"有着你不同的样式,就决定了进程间通信有着不同的通信方式
在这里插入图片描述

二.管道

管道的本质就是一个内存级文件,不存储在磁盘上,只存储在内存当中

1.从文件的角度理解什么是管道?

在这里插入图片描述
创建子进程之后log.txt的struct file对象的引用计数++

此时父子进程都对log.txt这个文件同时具有读写权限
也就是说父子进程看到了同一份资源(log.txt),这就是进程间通信

此时如果子进程对log.txt进行写入,父进程对log.txt进行读取
这不就完成了进程间通信了吗?

因此,这种

基于文件,让不同进程看到同一份资源的方式,就叫做管道!

如果让父子进程同时对log.txt既有读权限,又有写权限,那不就容易发生混乱吗?
因此设计管道的工程师规定:

管道只能被设计为单向通信!

那么我们如何把刚才的情况改为只能进行单向通信呢?
比方说我们要求父进程作为读端,子进程作为写端
只需要关闭父进程的’w’权限和子进程的’r’权限即可
log.txt的struct file对象的引用计数–
只有当引用计数减为0时,才会释放该struct file对象和其内核缓冲区

此时,父子进程的通信方式就叫做管道!!

回想一下,刚才我们是如何让不同的进程看到同一份资源的呢?

通过创建子进程,子进程会继承父进程的相关属性信息

子进程继承了父进程的相关信息,子进程的子进程也会继承子进程的相关信息
那么子进程的子进程不就也能跟父进程进行进程间通信了吗?
是的,因此进程之间只要具有血缘关系,那么就可以利用管道来进行进程间通信
而如果没有血缘关系,那么就无法利用管道来进行进程间通信了

三.匿名管道

如果此时我们想要让两个进程之间进行通信,但是不想在磁盘当中建立单独的文件,怎么办呢?

此时就可以使用匿名管道了
匿名管道:只能让具有血缘关系的进程之间进行进程间通信(常用于父子进程)
(祖先跟后代可以,兄弟之间也可以哦)

如何利用呢?
在这里插入图片描述
pipe这个函数就是用来创建匿名管道的,传入的参数是一个数组,是一个输出型参数
执行该函数后,会得到2个fd,分别存储在pipefd[0]和pipefd[1]的位置
pipefd[0]存储的是负责r(读)的fd,pipefd[1]存储的是负责w(写)的fd

pipe所创建的匿名管道是一个内存级的文件,并不存在于磁盘当中!!

创建成功返回值为0,创建失败返回值小于0

1.验证代码

下面我们来写一个验证代码
在这里插入图片描述
这份代码的含义是:
1.创建管道
2.创建子进程
3.子进程死循环向管道当中写入数据
4.父进程死循环从管道当中读取数据
然后我们编译生成可执行程序,开始运行
在这里插入图片描述
至此我们验证完毕

2.四种情况

经过刚才的演示,我们知道父子进程是如何通过管道来进行通信的了
但是还是不够细节,因为有些情况没有涉及到,下面我们来一一看看这4种情况吧
在这里插入图片描述
为了方便演示,我们改一下代码,让子进程发送消息时每次少发一些

不着急,我们一一分析

1.写端不写,且不退

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

2.读端不读,且不退

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
补充一点:
在这里插入图片描述
PIPE_BUF:管道的缓存大小是4096字节

当要写入的数据量不大于PIPE_BUF时,Linux将保证写入的原子性
当要写入的数据量大于PIPE_BUF时,Linux将不再保证写入的原子性

关于原子性我们以后会还会见到的
在这里插入图片描述

3.写端不写,退了

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

4.读端不读,退了

在这里插入图片描述
其实发送的是13号信号:
SIGPIPE
验证:
在这里插入图片描述
在这里插入图片描述

5.小小总结

在这里插入图片描述

3.五种特性

在这里插入图片描述

4.理解命令行管道

我们之前在Linux常见指令2当中介绍过命令行管道的使用
在这里插入图片描述
而今天我们学习了管道之后,我们再回过头来重新认识一下命令行管道

ps ajx | head -1 && ps ajx | grep 可执行程序名字 | grep -v grep

还记得我们的监控脚本吗?
其实它就是一个命令行管道的典型应用
在这里插入图片描述

四.命名管道

匿名管道挺好的,只不过只能由具有血缘关系的进程才能够使用,还是有些局限性的
能不能让没有血缘关系的进程之间也能使用管道来通信呢?
是可以的,不过需要使用我们接下来要介绍的命名管道

1.理论

1.回顾匿名管道的原理

在这里插入图片描述

2.命名管道理论

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

2.系统调用接口的介绍与使用

1.介绍

跟匿名管道一样,这件事情OS不放心让用户完成,也是为了给用户一个良好的使用体验,因此OS提供了系统调用接口
mkfifo
在这里插入图片描述

2.使用

我们知道|是命令行当中的匿名管道,说明Linux支持在命令行当中创建匿名管道,那么命名管道呢?
如果不允许的话,Linux是不是就有点偏心了啊
Linux也允许在命令行当中使用mkfifo来创建命名文件
曾记否:我们之前在介绍Linux下的文件类型的时候见过这个管道文件哦
在这里插入图片描述
今天我们想说的是:创建了一个命名管道之后,就不能再创建同名的命名管道了
在这里插入图片描述
因此我们在创建了命名管道之后,当本次客户端和服务端通信结束之后,为了让下一次对应的代码还能正常运行(也就是创建命名管道成功)
而且肯定是客户端先退出,所以我们要在服务端退出时将这个命名管道删掉

如何删呢?总不能进程程序替换执行个rm -f xxx命名管道吧,那也太挫了吧
OS肯定要提供系统调用接口
在这里插入图片描述
在这里插入图片描述
同样的unlink也是一个指令哦
介绍完我们需要使用的新增的系统调用接口之后,下面我们来搞代码啦

3.代码编写

在这里插入图片描述
这里unlink,read,write,open等等都要判断是否成功,这里为了让代码更加简洁,就没怎么判断,大家可以加上,返回错误码是真的难受…
还是异常香

1.Common.hpp

命名管道是不是只有一份呢?
不是,可以同时有很多份,OS要不要管理,要
如何管理?先描述,在组织
走起
在这里插入图片描述

2.pipeServer.cpp

在这里插入图片描述
注意:
命名管道创建之后
如果只有读端被打开了,写端还没有被打开,那么读端会阻塞在open函数当中
如果只有写端被打开了,读端还没有被打开,那么写端会阻塞在open函数当中

3.pipeClient.cpp

在这里插入图片描述

4.makefile

因为要生成2个可执行程序,因此需要用一个伪目标
(当然你make xxx两次也可以,主要是不优雅)
在这里插入图片描述

4.动图演示

此时就能够随便玩了
在这里插入图片描述
当然你要是想换一下读写端的话,两个人约定一些暗号等等的信息,输入之后就先暂时退出,然后各自换一种权限打开该文件等等,
这一点上面比匿名管道好玩总之,你想怎么玩就怎么写
随意~

五.自定义shell当中添加命令行管道的功能

1.前言

前言 : 有些内建命令的确可以跟命令行管道一起使用,例如echo,不过某些内建命令无法跟命令行管道一起使用:比如cd
命令行管道可以跟重定向>>,>,<一起使用

拿出我们实现完重定向的shell,(我今天是由自己写了一遍,所以跟上一次的有些不同,但功能是一样的,大家知道能这么玩就行,有空的时候可以自己玩一下)
(还有就是那个echo $?返回最近一次错误码,因为最近刚学了异常,所以看到这种错误码就浑身难受,所以没有搞这个,不过不妨碍我们今天要实现的重定向)

这是一个cpp文件(因为如果用C添加命令行管道[太麻烦了,连个顺序表都没有]…写C++写惯了,不想用C…)
为了方便实现,我们把main函数中的代码拿出去了两部分
代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <fcntl.h>
#include <vector>
#include <iostream>
#define NoneRedir 0
#define OutputRedir 1
#define InputRedir 2
#define AppendRedir 3
using namespace std;
int lastcode=0;//上一次进程退出时的退出码

int redir=NoneRedir;
char* filename=NULL;

char cwd[1024]={'\0'};
char env[1024][1024]={'\0'};
int my_index=0;

//将字符串s按照空格作为分割符拆分后添加到vs当中(不过: 空格可能会连续出现)
void CommandLineSplit(char* command,char* commandV[])
{
    char* s=strtok(command," ");
    commandV[0]=s;
    int i=1;
    while(commandV[i++]=strtok(NULL," ")){}
}

#define SkipSpace(pos) do{while(isspace(*pos)){pos++;}}while(0)

void CheckRedir(char* command)
{
    int len=strlen(command);
    char* pos;
    for(int i=len-1;i>=0;)
    {
        if(command[i]=='>')
        {
            if(i>0 && command[i-1]=='>')//追加
            {
                redir=AppendRedir;
                command[i-1]='\0';
            }
            else//输出
            {
                redir=OutputRedir;
                command[i]='\0';
            }
            pos=command+i+1;
            SkipSpace(pos);
            filename=pos;
            break;
        }
        else if(command[i]=='<')//输入
        {
            redir=InputRedir;
            command[i]='\0';
            pos=command+i+1;
            SkipSpace(pos);
            filename=pos;
            break;
        }
        else
        {
            i--;
        }
    }
}

void Exec(char* commandV[])
{
    pid_t id=fork();
    if(id==0)
    {
        if(redir==InputRedir)
        {
            int fd=open(filename,O_RDONLY);
            dup2(fd,0);
        }
        else if(redir==OutputRedir)
        {
            int fd=open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);
            dup2(fd,1);
        }
        else if(redir==AppendRedir)
        {
            int fd=open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);
            dup2(fd,1);
        }
        execvp(commandV[0],commandV);
    }
    wait(NULL);
}

void cd(char* path)
{
    chdir(path);
    char tmp[1024]={'\0'};
    getcwd(tmp,sizeof(tmp));
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
}

void Export(char* s)
{
    strcpy(env[my_index],s);
    putenv(env[my_index++]);
}

int echo(char* commandV[])
{
        //1.echo后面什么都没有,相当于'\n'
        if(commandV[1]==NULL)
        {
            printf("\n");
            lastcode=0;
            return 1;
        }
        //2.echo $?  echo $PWD echo $
        char* cmd=commandV[1];
        int len=strlen(cmd);
        if(cmd[0]=='$' && len>1)
        {
            //echo $?
            if(cmd[1]=='?')
            {
                printf("%d\n",lastcode);
                lastcode=0;
            }
            //echo $PWD
            else
            {
                char* tmp=cmd+1;
                const char* env=getenv(tmp);
                //找不到该环境变量,打印'\n',退出码依旧为0
                if(env==NULL)
                {
                    printf("\n");
                }
                else
                {
                    printf("%s\n",env);
                }
                lastcode=0;
            }
        }
        else
        {
            if(cmd[0]=='"' && cmd[len-1]=='"')
            {
                cmd[len-1]='\0';
                printf("%s\n",cmd+1);
            }
            else
                printf("%s\n",cmd);
        }
        return 1;
}


int doBulidIn(char* commandV[])
{
    int ret=0;
    if(strcmp(commandV[0],"cd")==0)
    {
        cd(commandV[1]);
        ret=1;
    }
    else if(strcmp(commandV[0],"export")==0)
    {
        Export(commandV[1]);
        ret=1;
    }
    else if(strcmp(commandV[0],"echo")==0)
    {
        echo(commandV);
        ret=1;
    }
    return ret;
}

//打印提示符和修正redir,filename的函数
void step1()
{
    redir=NoneRedir;
    filename=NULL;
    //1.打印提示符wzs@VM-16-10-ubuntu:~/ubuntucode/shell$ 
    printf("%s@VM-16-10-ubuntu:%s$ ",getenv("USER"),getenv("PWD"));
}

//具体执行指令的函数
void step3(char* command)
{
    //3.检查重定向
    CheckRedir(command);
    //4.解析字符串
    char* commandV[1024]={NULL};
    CommandLineSplit(command,commandV);
    //5.分析内建命令
    int ret=doBulidIn(commandV);
    if(ret==0)
    {
        //6.进程程序替换
        Exec(commandV);
    }
}

int main()
{
    while(1)
    {
        step1();
        char command[1024]={'\0'};
        fgets(command,sizeof(command),stdin);
        int len=strlen(command);
        command[len-1]='\0';
        step3(command);
    }
    return 0;
}

2.思路

1.如何创建管道与进程

在这里插入图片描述
因此,我们只需要边创建管道,边创建进程,(最后一个进程单独创建)
并且用vector<pair<int,int>>存储所有的管道的读写端方便后续管道对其进行关闭
(因为管道的特点是读/写端没有全都退出,另一端就会一直阻塞等待,如果不关闭读写端的话父进程回收时就必须要逆序回收)

又因为我们要让父进程回收子进程,所以在用一个vector<int>存储所有的子进程pid,后续还要waitpid回收他们呢

2.预处理

在检查是否需要重定向之前,我们需要先检查整个字符串有多少个’|‘命令行管道,并且
[1]用一个vector<int>保存它们的位置,并且将对应位置改为’\0’,方便后续把每一块指令传递给子进程去处理
[2]因为第一个指令左侧没有’|',所以我们在初始化vector<int>的时候,可以先存上一个-1,然后分配任务时就好分配了

3.实现

只有刚才的这两步,搞清楚这两点之后代码就能够很好的写出来了
在这里插入图片描述

4.演示

在这里插入图片描述

5.代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <ctype.h>
#include <fcntl.h>
#include <vector>
#include <iostream>
#define NoneRedir 0
#define OutputRedir 1
#define InputRedir 2
#define AppendRedir 3
using namespace std;
int lastcode=0;//上一次进程退出时的退出码

int redir=NoneRedir;
char* filename=NULL;

char cwd[1024]={'\0'};
char env[1024][1024]={'\0'};
int my_index=0;

//将字符串s按照空格作为分割符拆分后添加到vs当中(不过: 空格可能会连续出现)
void CommandLineSplit(char* command,char* commandV[])
{
    char* s=strtok(command," ");
    commandV[0]=s;
    int i=1;
    while(commandV[i++]=strtok(NULL," ")){}
}

#define SkipSpace(pos) do{while(isspace(*pos)){pos++;}}while(0)

void CheckRedir(char* command)
{
    int len=strlen(command);
    char* pos;
    for(int i=len-1;i>=0;)
    {
        if(command[i]=='>')
        {
            if(i>0 && command[i-1]=='>')//追加
            {
                redir=AppendRedir;
                command[i-1]='\0';
            }
            else//输出
            {
                redir=OutputRedir;
                command[i]='\0';
            }
            pos=command+i+1;
            SkipSpace(pos);
            filename=pos;
            break;
        }
        else if(command[i]=='<')//输入
        {
            redir=InputRedir;
            command[i]='\0';
            pos=command+i+1;
            SkipSpace(pos);
            filename=pos;
            break;
        }
        else
        {
            i--;
        }
    }
}

void Exec(char* commandV[])
{
    pid_t id=fork();
    if(id==0)
    {
        if(redir==InputRedir)
        {
            int fd=open(filename,O_RDONLY);
            dup2(fd,0);
        }
        else if(redir==OutputRedir)
        {
            int fd=open(filename,O_WRONLY | O_CREAT | O_TRUNC,0666);
            dup2(fd,1);
        }
        else if(redir==AppendRedir)
        {
            int fd=open(filename,O_WRONLY | O_CREAT | O_APPEND,0666);
            dup2(fd,1);
        }
        execvp(commandV[0],commandV);
    }
    wait(NULL);
}

void cd(char* path)
{
    chdir(path);
    char tmp[1024]={'\0'};
    getcwd(tmp,sizeof(tmp));
    sprintf(cwd,"PWD=%s",tmp);
    putenv(cwd);
}

void Export(char* s)
{
    strcpy(env[my_index],s);
    putenv(env[my_index++]);
}

int echo(char* commandV[])
{
        //1.echo后面什么都没有,相当于'\n'
        if(commandV[1]==NULL)
        {
            printf("\n");
            lastcode=0;
            return 1;
        }
        //2.echo $?  echo $PWD echo $
        char* cmd=commandV[1];
        int len=strlen(cmd);
        if(cmd[0]=='$' && len>1)
        {
            //echo $?
            if(cmd[1]=='?')
            {
                printf("%d\n",lastcode);
                lastcode=0;
            }
            //echo $PWD
            else
            {
                char* tmp=cmd+1;
                const char* env=getenv(tmp);
                //找不到该环境变量,打印'\n',退出码依旧为0
                if(env==NULL)
                {
                    printf("\n");
                }
                else
                {
                    printf("%s\n",env);
                }
                lastcode=0;
            }
        }
        else
        {
            if(cmd[0]=='"' && cmd[len-1]=='"')
            {
                cmd[len-1]='\0';
                printf("%s\n",cmd+1);
            }
            else
                printf("%s\n",cmd);
        }
        return 1;
}

int doBulidIn(char* commandV[])
{
    int ret=0;
    if(strcmp(commandV[0],"cd")==0)
    {
        cd(commandV[1]);
        ret=1;
    }
    else if(strcmp(commandV[0],"export")==0)
    {
        Export(commandV[1]);
        ret=1;
    }
    else if(strcmp(commandV[0],"echo")==0)
    {
        echo(commandV);
        ret=1;
    }
    return ret;
}

void step1()
{
    redir=NoneRedir;
    filename=NULL;
    //1.打印提示符wzs@VM-16-10-ubuntu:~/ubuntucode/shell$ 
    printf("%s@VM-16-10-ubuntu:%s$ ",getenv("USER"),getenv("PWD"));
}

void step3(char* command)
{
    //3.检查重定向
    CheckRedir(command);
    //4.解析字符串
    char* commandV[1024]={NULL};
    CommandLineSplit(command,commandV);
    //5.分析内建命令
    int ret=doBulidIn(commandV);
    if(ret==0)
    {
        //6.进程程序替换
        Exec(commandV);
    }
}

int CheckPipe(char* command,vector<int>& v)//返回管道个数,并且把|的位置填到数组当中
{
    int len=strlen(command);
    for(int i=0;i<len;i++)
    {
        if(command[i]=='|')
        {
            v.push_back(i);
            command[i]='\0';//直接截断
        }
    }
    return v.size();
}

void Execpipe(char* command)
{
    vector<int> pos(1,-1);//为了后续分割方便
    int sz=CheckPipe(command,pos);
    if(sz==1)
    {
        step3(command);
        return;
    }
    vector<int> child_id;
    vector<pair<int,int>> v;//v[i].first:i号管道的读 second:写
    for(int i=0;i<sz-1;i++)//创建sz个进程,sz-1个管道
    {
        int pipefd[2];
        int n=pipe(pipefd);
        pid_t id=fork();
        if(id==0)
        {
            close(pipefd[0]);//关闭当前管道的读
            int prev_fd_sz=v.size();
            for(int j=0;j<prev_fd_sz;j++)
            {
                if(j<prev_fd_sz-1)
                {
                    //把前面打开的读都关上(除了最近的那一个管道)
                    close(v[j].first);
                }
                //把前面打开的管道的写都关上
                close(v[j].second);
            }
            dup2(pipefd[1],1);//当前管道的写重定向到我的1
            if(!v.empty()) dup2(v.back().first,0);//上一个管道的读重定向到我的0
            step3(command+pos[i]+1);
            exit(0);
        }
        else
        {
            v.push_back({pipefd[0],pipefd[1]});
            child_id.push_back(id);
        }
    }
    //最后一个进程:
    pid_t id=fork();
    if(id==0)
    {
        int prev_fd_sz=v.size();
        for(int j=0;j<prev_fd_sz;j++)
        {
            if(j<prev_fd_sz-1)
            {
                //把前面打开的读都关上(除了最近的那一个管道)
                close(v[j].first);
            }
            //把前面打开的管道的写都关上
            close(v[j].second);
        }
        dup2(v.back().first,0);
        step3(command+pos[sz-1]+1);
        exit(0);
    }
    child_id.push_back(id);
    for(auto& e:v)
    {
        //最后的时候父亲关闭所有的读写端
        close(e.first);close(e.second);
    }
    for(auto& e:child_id)
    {
        pid_t rid=waitpid(e,nullptr,0);
        if(rid>0) cout<<"wait success, pid: "<<rid<<endl;
    }
}

int main()
{
    while(1)
    {
        step1();
        char command[1024]={'\0'};
        fgets(command,sizeof(command),stdin);
        int len=strlen(command);
        command[len-1]='\0';
        Execpipe(command);
    }
    return 0;
}

以上就是Linux 进程间通信 管道系列: 匿名和命名管道,自定义shell当中命令行管道的模拟实现的全部内容,希望能对大家有所帮助!!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

xhs图片获取并且转换成PDF,实现了我考研期间一直想实现的想法

对于一些xhs图文&#xff0c;很多人其实想把它的图片保存到本地&#xff0c;尤其是下图所示的考研英语从文章中背单词&#xff0c;不说别人&#xff0c;我就是这样的。 我在考研期间就想实现把图片批量爬取下来&#xff0c;转成PDF&#xff0c;方便一篇一片阅读进行观看&#…

mysql报错-mysql服务启动停止后,某些服务在未由其他服务或程序使用时将自动停止和数据恢复

启动mysql服务时出现该错误: 本地计算机上的mysql服务启动停止后,某些服务在未由其他服务或程序使用时将自动停止。 我的mysql版本是8.0.18 系统&#xff1a;win10 如何安装mysql&#xff0c;可以看我这一篇文章&#xff1a;mysql的安装 ---必会 - bigbigbrid - 博客园 (cn…

腾讯面试准备-2024.3.25

腾讯面试准备-2024.3.25 腾讯面试准备-2024.3.25自我介绍C11/14/17新特性C11新特性C14新特性C17新特性 struct和class的区别进程状态现代的流媒体通信协议栈流媒体协议详解extern "C"程序从编译到执行的过程进程、线程、协程进程线程协程 如何实现一个信号与槽系统&a…

Binary Heap 二叉堆 (二)

一、二叉堆 二叉堆本质上是一种完全二叉树。 它分为两类&#xff1a;最大堆和最小堆。最大堆的任何一个父节点的值都大于或等于它左右孩子节点的值&#xff1b;最小堆的任何一个父节点的值&#xff0c;都小于或等一它左右孩子节点的值。 二叉堆虽然是一个完全二叉树&#xff0c…

国产数据库实践:亚信安慧AntDB在DTC 2024展示创新实力

4月12至13日&#xff0c;我国数据库行业最具影响力的活动之一——第十三届『数据技术嘉年华』(DTC 2024) 在京成功举办&#xff0c;业内众多专家学者、技术领袖、各行业客户和实力厂商均到场参会。亚信安慧AntDB数据库总架构师洪建辉受邀参与“数据库一体化”专题论坛&#xff…

XDEFIANT不羁联盟怎么申请测试 不羁联盟参与测试教程

《不羁联盟》有五个独具特色的阵营可供选择&#xff1a;自由武装、暗影小队、梯队、净化者、DedSec&#xff0c;全部出自育碧知名的角色与世界。无论是拥有“声纳护目镜”超能的梯队探员&#xff0c;还是拥有黑入对手设备能力的 DedSec&#xff0c;每个阵营都有自己的一套独特技…

MySQL 8.0 新特性之 Clone Plugin

个人感觉&#xff0c;主要还是为 Group Replication 服务。在 Group Replication 中&#xff0c;如果要添加一个新的节点&#xff0c;这个节点差异数据的补齐是通过分布式恢复&#xff08; Distributed Recovery &#xff09;来实现的。 在 MySQL 8.0.17 之前&#xff0c;只支…

第二证券|突发!美联储释放重磅信号,中国资产大涨

美联储稀有开释“加息”信号。 北京时刻4月18日晚间&#xff0c;有“美联储三把手”之称的享有FOMC&#xff08;美国联邦公开商场委员会&#xff09;永久投票权的美国纽约联储主席威廉姆斯宣布说话。他正告称&#xff0c;假如数据显现&#xff0c;美联储需求加息&#xff0c;以…

Python Flask Web框架快速入门

Flask 入门Demo Flask 开发环境搭建&#xff0c;执行如下指令&#xff1a; pip install flask # 第一节: Flask 快速入门from flask import Flask app Flask(__name__)app.route(/flask) def hello_flask():return Hello Flaskapp.run() 核心代码剖析&#xff1a; 从 fla…

【机器学习】小波变换在特征提取中的实践与应用

小波变换在特征提取中的实践与应用 一、小波变换的基本原理与数学表达二、基于小波变换的特征提取方法与实例三、小波变换在特征提取中的优势与展望 在信号处理与数据分析领域&#xff0c;小波变换作为一种强大的数学工具&#xff0c;其多尺度分析特性使得它在特征提取中扮演着…

2024最新面试跳槽,软件测试面试题的整理与解析

今天接着来说说测试工程师面试比较高频的面试题&#xff0c;大家可以通过面试题内的一些解析再结合自己的真实工作经验来进行答题思路的提取、整理。 硬背答案虽可&#xff0c;但容易翻车哦。能够举一反三才是重点&#xff01; 1&#xff1a;请介绍一下UI自动化测试中三种时间等…

解线性方程组——上三角、下三角,回代算法 | 北太天元

解上三角(回代) a i i ≠ 0 a_{ii\neq0} aii0​ , i 1 , 2 , … , n i1,2,\ldots,n i1,2,…,n a 11 x 1 a 12 x 2 ⋯ a 1 n x n b 1 a 22 x 2 ⋯ a 2 n x n b 2 ⋯ a n n x n b n \begin{aligned} a_{11}x_1a_{12}x_2\cdotsa_{1n}x_n&b_1 \\ a_{22}x_2\cdotsa_…

从零开始搭建社交圈子系统:充实人脉的最佳路径

线上交友圈&#xff1a;拓展社交网络的新时代 线上交友圈是社交网络的新引擎&#xff0c;提供了更广泛的社交机会&#xff0c;注重共同兴趣的连接&#xff0c;强调多样性的社交形式&#xff0c;更真实地展示自己&#xff0c;让朋友更全面地了解我们的生活状态。虽然虚拟交往存在…

【智能算法】饥饿游戏搜索算法(HGS)原理及实现

目录 1.背景2.算法原理2.1算法思想2.2算法过程 3.结果展示4.参考文献 1.背景 2021年&#xff0c;Yang等人受到自然界饥饿驱动的活动和动物的行为选择启发&#xff0c;提出了饥饿游戏搜索算法&#xff08;Hunger Games Search, HGS&#xff09;。 2.算法原理 2.1算法思想 HGS…

SPN的相关利用(下)

Kerberoasting kerberos通信过程&#xff1a; 在TGS-REQ中会发出ST服务票据获取servicePrincipalName(SPN)&#xff0c;该SPN是用户或者机器用户注册的。TGS-REP中TGS会返回给user一个ST&#xff0c;而ST是由user请求的server的密码进行加密的&#xff0c;我们可以从TGS-REP中…

RT-Thread时钟管理

操作系统需要通过时间来规范其任务,主要介绍时钟节拍和基于时钟节拍的定时器。 时钟节拍 任何操作系统都需要提供一个时钟节拍,以供系统处理所有和时间有关的事件,如线程的延时、线程的时间片轮转调度以及定时器超时等。 RT-Thread 中,时钟节拍的长度可以根据 RT_TICK_P…

Module外贸主题开心版下载-v5.7.0版本WordPress企业模板

主题下载地址&#xff1a;Module外贸主题开心版下载-v5.7.0版本 Module主题介绍&#xff1a;采用全新模块化开发&#xff0c;首页模块可视化拖拽自由组合&#xff0c;可自定义搭建出不同行业适用的企业网站。同时主题全面支持WPML多语言切换&#xff0c;可轻松搭建外贸网站。W…

JetBrains Rider 2024.1.1 .NET集成开发环境 mac/win

JetBrains Rider是一个新的跨平台的基于Inte lliJ平台和ReSharper的. NET集成技术开发工作环境。 Rider提供了大量人工智能系统代码进行编辑管理功能&#xff0c;如不同类型的代码可以完成、自动设备名称发展空间设计导入、自动通过插入大括号和突出研究显示信息匹配作为分隔符…

torchEEG工具箱

文章信息: 题目&#xff1a;TorchEEGEMO&#xff1a;基于脑电图的情绪识别深度学习工具箱 期刊&#xff1a;Expert Systems with Applications 环境&#xff1a;pytorch 1.11.0 CUDA 11.3 摘要&#xff1a; ​ 一个python工具箱TorchEEG&#xff0c;将工作流程分为五个模块…

软考 - 系统架构设计师 - 架构风格例题

问题一&#xff1a; 什么是软件架构风格&#xff1f; 软件架构风格指特定软件系统组织方式的惯用模式。组织方式描述了系统的组成构件和这些构件的组织方式。惯用模式反映了众多系统所共有的结构和语义。 集成开发环境与用户的交互方式 &#xff08;实际上询问在交互方面&am…
最新文章