【linux】进行间通信——共享内存+消息队列+信号量

news2024/11/20 9:33:04

共享内存+消息队列+信号量

  • 1.共享内存
    • 1.1共享内存的原理
    • 1.2共享内存的概念
    • 1.3接口的认识
    • 1.4实操
      • comm.hpp
      • service.cc (写)
      • clint.cc (读)
    • 1.5共享内存的总结
    • 1.6共享内存的内核结构
  • 2.消息队列
    • 2.1原理
    • 2.2接口
  • 3.信号量
    • 3.1信号量是什么
    • 3.2为什么要信号量
    • 3.3接口
  • 4.IPC资源的组织方式

进程间通信方式目前我们已经学了匿名管道,命名管道。让两个独立的进程通信,前提是看到同一份资源。匿名管道适用于血缘关系的进程,一个打开写端一个打开读端实现的。命名管道适用于完全独立的进程,打开同一份文件实现的。

接下来我们看看剩下的实现进程间通信的方式。

1.共享内存

1.1共享内存的原理

在这里插入图片描述
可执行程序加载到内存,OS会对进程进行管理。进程的内核结构是独立的,加载到物理地址的地址也独立,因此进程具有独立性,互不影响。

那共享内存是如何实现两个独立的进程进行通信的呢?

1.申请一块空间(用户给OS发信号,然后OS去申请)

在这里插入图片描述

2.将创建好的内存经过页表映射到进程地址空间中的一段区域(将这块区域的起始地址返回给用户,用户通过访问这里的起始地址方式来进行对这块区域的访问)

在这里插入图片描述

3.未来不想通信了
a.取消进程和内存的映射关系
b.释放内存

上面就是共享内存的原理。

这块申请的内存—>共享内存
进程和内存建立映射关系过程—>进程和共享内存挂接
取消进程和内存的映射关系—>去关联
释放内存—>释放共享内存

如何理解上面这个过程?

在内存上申请空间,然后把地址返回,是不是像C/C++空间的申请,如malloc函数,底层都是一样的先去物理内存申请空间,然后经过页表映射到进程地址空间,最后把这块空间的起始地址返回用户。虽然过程都是一样,但是malloc申请的空间没有办法让另一个进程看见,因为这块空间是在堆上申请的,OS并没有专门为malloc这样的机制和其他进程建立映射关系策略。

a.进程间通信,是专门设计的,用来IPC
b.共享内存是一种通信方式,所有想通信的进程,都可以用
c.OS中一定可能会同时存在很多的共享内存

1.2共享内存的概念

通过让不同的进程,看到同一个内存的方式:共享内存。

1.3接口的认识

1.创建共享内存。

在这里插入图片描述
size 要申请多大的内存空间

shmflg 常用参数。

在这里插入图片描述

看到这里这个参数是不是和open接口有点类似,大写的宏。

shmflg是一个标志位。

IPC_CREAT:如果想创建的共享内存不存在,就创建,如果存在就获取
IPC_EXCL:无法单独使用。
IPC_CREAT | IPC_EXCL:如果不存在就创建,如果存在就出错返回。用户创建共享内存如果成功,一定是一个新的共享内存。

创建共享内存非常容易,那如果保证进程看到的是同一块共享内存呢?
key用来保证。

在这里插入图片描述
key是什么不重要,能进行唯一性标识最重要。

在这里插入图片描述
将路径名和项目标识符转换为key。

随便写个路径和项目id。经过算法转换成key。
两个进程传一样参数,能保证是同一个key,因此可以在系统中找到同一个内存。

在这里插入图片描述
返回值。成功是返回共享内存的标识符,失败返回-1。

再来理解key_t key

OS中一定可能会同时存在很多的共享内存。
OS也要对共享内存进行管理—>先描述,在组织。

申请一块空间—>共享内存=物理内存块+共享内存的相关属性

key是什么不重要,能进行唯一标识最重要。
创建共享内存的时候,key能保证共享内存在系统中的唯一性。两个进程如何看到同一份资源,只要另一个进程也看到同一个key。

key在那?
key在共享内存的相关属性这个结构体里。

创建共享内存把key传到shmget,本质上是把key设置进创建好的共享内存的某个属性里,另一个进程获取时,查这么多的共享内存,不是查共享内的物理内存块,而是去遍历共享内存对于的相关属性去查找key。

key传到shmget,设置到共享内存属性中,用来表示共享内存在内核中的唯一性。

在这里插入图片描述

返回值返回的共享内存的标识符取名shmid。

shmid vs key的关系
shmid是为了用户去访问共享内存的。就像fd vs inode的关系。

2.共享内存和进程关联

在这里插入图片描述

shmid:和哪一个共享内存关联
shmaddr:把共享内存映射到地址空间的那一块区域
shmflg:与读写权限有关,默认设置为0

在这里插入图片描述
成功是返回的是对应进程地址空间的起始地址,失败返回-1。

3.删除共享内存之前要先去关联

将共享内存从当前调用这个函数的进程地址空间进行卸装

在这里插入图片描述
shmaddr:进程地址空间的首地址

4.删除共享内存

删除共享内存接口是shmctl,本质上控制共享内存,不过常用的是删除选项。
在这里插入图片描述

shmid:控制哪一个共享内存
cmd:做什么控制,常用选项IPC_RMID
buf:如果不想获得共享内存的属性可以设置nullptr,不然就传一个对象接收共享内存部分属性信息。

在这里插入图片描述

1.4实操

在这里插入图片描述

comm.hpp

将写端和读端用的代码封装起来。

#include<iostream>
#include<cerrno>
#include<cstring>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

#define PATHNAME "."
#define PROJ_JD  "0x11"

#define MAX_SIZE 4096

key_t GetKey()
{
    key_t k=ftok(PATHNAME,PROJ_JD);//获得唯一标识key
    if(k < 0)
    {
        //cin cout cerr->stdin stdout stderr(默认打开的三个文件)-(fd)>0,1,2->键盘,显示器,显示器
        cerr<<errno<<":"<<strerror(errno)<<endl;
        exit(1);
    }
    return k;
}

//获得共享内存表示符shmid
int getShmHelper(int key,int shmflg)
{
    int shmid=shemget(key,MAX_SIZE,shmflg);
    if(shmid < 0)
    {
        
        cerr<<errno<<":"<<strerror(errno)<<endl;
        exit(1);
    }
    return shmid;
}

//写端创建共享内存
int CreateShm(key_t key)
{
	//这里运行时会报错。下面再看运行结果有解决方法
    return getShmHelper(key,IPC_CREAT|IPC_EXCL);
}

//读端获取共享内存
int GetShm()
{
    return getShmHelper(key,IPC_CREAT);
}

在这里插入图片描述
看运行结果写端和读端的key是一样的,shmid也是一样的。

在这里插入图片描述
当我再次执行一样的操作,发现不能再创建共享内存了。显示已经存在了。可是我已经退出进程了啊。OS不会帮我自动关闭吗。

共享内存的生命周期是随操作系统的,不是随进程的

查看IPC资源

ipcs查看IPC资源

在这里插入图片描述

ipcs -m 查看共享内存

在这里插入图片描述

ipcs -q 查看队列

在这里插入图片描述

ipcs -s 查信号量

在这里插入图片描述

ipcrm -m shmid 删除共享内存

在这里插入图片描述

代码删除共享内存

//删除共享内存
void DelShm(int shmid)
{
//删除共享内存
void DelShm(int shmid)
{
    if(shmctl(shmid,IPC_RMID,nullptr) == -1)
    {
        cerr<<errno<<":"<<strerror(errno)<<endl;
    }

}
}

在这里插入图片描述
通信之前需要关联(挂接:将共享内存经页表映射到进程地址空间)

void* attachShm(int shimid)
{
    void* mem=shmat(shimid,nullptr,0);//linux 64位机器指针大小位9
    if((long long)men == -1L)//1L代表是长整型
    {
        cerr<<errno<<":"<<strerror(errno)<<endl;
        exit(1);
    }
    return men;
}

删除共享内存之前需要去关联

void detachShm(const void* adder)
{
    if(shmdt(adder) == -1)
    {
        cerr<<errno<<":"<<strerror(errno)<<endl;
        exit(1);
    }
}

完整代码如下

#include<iostream>
#include<cerrno>
#include<cstring>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<unistd.h>

using namespace std;

#define PATHNAME "."
#define PROJ_JD  0x11

#define MAX_SIZE 4096


key_t GetKey()
{
    key_t k=ftok(PATHNAME,PROJ_JD);
    if(k < 0)
    {
        //cin cout cerr->stdin stdout stderr(默认打开的三个文件)-(fd)>0,1,2->键盘,显示器,显示器
        cerr<<errno<<":"<<strerror(errno)<<endl;
        exit(1);
    }
    return k;
}

//获得共享内存表示符shmid
int getShmHelper(int key,int flage)
{
    int shmid=shmget(key,MAX_SIZE,flage);
    if(shmid < 0)
    {    
        cerr<<errno<<":"<<strerror(errno)<<endl;
        exit(1);
    }
    return shmid;
}

//写端创建共享内存
int CreateShm(key_t key)
{
	//这里运行时会报错。下面再看运行结果有解决方法
    return getShmHelper(key,IPC_CREAT|IPC_EXCL);
}

//读端获取共享内存
int GetShm(key_t key)
{
    return getShmHelper(key,IPC_CREAT);
}

//关联
void* attachShm(int shimid)
{
    void* mem=shmat(shimid,nullptr,0);//linux 64位机器指针大小位9
    if((long long)mem == -1L)//1L代表是长整型
    {
        cerr<<errno<<":"<<strerror(errno)<<endl;
        exit(1);
    }
    return mem;
}

//去关联
void detachShm(const void* adder)
{
    if(shmdt(adder) == -1)
    {
        cerr<<errno<<":"<<strerror(errno)<<endl;
        exit(1);
    }
}

//删除共享内存
void DelShm(int shmid)
{
    if(shmctl(shmid,IPC_RMID,nullptr) == -1)
    {
        cerr<<errno<<":"<<strerror(errno)<<endl;
    }

}

service.cc (写)

#include"comm.hpp"

int main()
{
    key_t key=GetKey();
    printf("key->%d\n",key);
    int shmid=CreateShm(key);
    printf("shimid->%d\n",shmid);

    //关联
    char*start=(char*)attachShm(shmid);//我想将这个地址认为是一字符串;
    printf("attach success, address start: %p\n", start);
    
    //写
    while(true)
    {

    }

    //删除
    DelShm(shmid);

    return 0;
}

共享内存没有read和write这样的接口。
写—>直接把内容写到这块内存
读—>直接打印这块内存

以往我们可能是这样的写法,对于往内存中写有点麻烦了。

    //写
    char buffer[1024];
    const char* messge="hello clint,我是另一个进程,我正在和你通信";
    int id=getpid();
    int cnt=0;
    while(true)
    {
        //以往我们的做法
        snprintf(buffer,sizeof buffer,"%s([%d]->[%d])",messge,cnt++,id);
        memcpy(start,buffer,strlen(buffer)+1);
    }

看这个函数,我们可以直接把内容写到内存。

在这里插入图片描述

完整代码如下

#include"comm.hpp"

int main()
{
    key_t key=GetKey();
    printf("key->%d\n",key);
    int shmid=CreateShm(key);
    printf("shimid->%d\n",shmid);

    //关联
    char* start=(char*)attachShm(shmid);//我想将这个地址认为是一字符串;
    printf("attach success, address start: %p\n", start);
    
    //写
    char buffer[1024];
    const char* messge="hello clint,我是另一个进程,我正在和你通信";
    int id=getpid();
    int cnt=0;
    while(true)
    {
        snprintf(start,MAX_SIZE,"%s([%d]->[%d])",messge,cnt++,id);
        //以往我们的做法
        // snprintf(buffer,sizeof buffer,"%s([%d]->[%d])",messge,cnt++,id));
        // memcpy(start,buffer,strlen(buffer)+1);
    }

    //去关联
    detachShm(start);

    //删除
    DelShm(shmid);

    return 0;
}

clint.cc (读)

#include"comm.hpp"

int main()
{
    key_t key=GetKey();
    printf("key->%d\n",key);
    int shmid=GetShm(key);
    printf("shimid->%d\n",shmid);

    char* start=(char*)attachShm(shmid);
    printf("attach success, address start: %p\n", start);

    while(true)
    {
         printf("service say : %s\n", start);
    }

    return 0;
}

在这里插入图片描述
运行时报了这个没有权限的错误。在这里插入图片描述
这是因为再创建共享内存的时候没有给权限。
在这里插入图片描述
补上权限即可
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
看运行结果,虽然两个进程进行了通信,但是这个通信有点问题。
共享内存不像管道那样,阻塞等待,而是一直在读。

1.5共享内存的总结

共享内存的特点:

1.共享内存的生命周期是随OS的,不是随进程的。
2.所有进程间通信速度最快的(能大大的减少数据的拷贝次数)—>优点。

同样的代码,如果用管道来实现,综合考虑管道和共享内存,考虑键盘输入和显示器输出,共享内存有几次数据拷贝?管道呢?
在这里插入图片描述
3.不给我进行同步和互斥的操作,没有对数据做任何保护---->缺点

1.6共享内存的内核结构

在这里插入图片描述
给这个接口传一个struct shmid_de 对象,就看到共享内存一些信息。
在这里插入图片描述
在这里插入图片描述

#include"comm.hpp"

int main()
{
    key_t key=GetKey();
    printf("key->%d\n",key);
    int shmid=GetShm(key);
    printf("shimid->%d\n",shmid);

    char* start=(char*)attachShm(shmid);
    printf("attach success, address start: %p\n", start);

    while(true)
    {
         printf("service say : %s\n", start);
         struct shmid_ds ds;
         shmctl(shmid,IPC_STAT,&ds);
         printf("获得属性: size :%d,link :%d\n",ds.shm_segsz,ds.shm_nattch);
         sleep(1);
    }

    return 0;
}

在这里插入图片描述
我们确实获得了共享内核的一些属性。

我们的key在struct shmid_ds结构体的第一个变量里。
在这里插入图片描述

#include"comm.hpp"

int main()
{
    key_t key=GetKey();
    printf("key->%d\n",key);
    int shmid=GetShm(key);
    printf("shimid->%d\n",shmid);

    char* start=(char*)attachShm(shmid);
    printf("attach success, address start: %p\n", start);

    while(true)
    {
         printf("service say : %s\n", start);
         struct shmid_ds ds;
         shmctl(shmid,IPC_STAT,&ds);
         printf("获得属性: size :%d,link :%d,key :%d\n",\
                ds.shm_segsz,ds.shm_nattch,ds.shm_perm.__key);
         sleep(1);
    }

    return 0;
}

在这里插入图片描述

共享内存大小一般建议4KB(4096)的整数倍
系统分配共享内存是以4KB为单倍的!------内存划分内存块的基本单位page

在这里插入图片描述
虽然申请大小是4097,但是内核给你的实际是40962,内核给你向上取整。
虽然内核给的是4096
2,但是注意内核给你的,和你能用的是两码事。

2.消息队列

在前面说过System V IPC—>聚焦在本地通信,目前已经陈旧了,共享内存还是值得我们好好学习一番,剩下的我们看一看原理和接口就可以了。

2.1原理

在这里插入图片描述
进程A,B可以相互为读写段,一端把数据放到队列里,一端去拿。
那如何保证两个进程不会拿到自己的,而去拿对方的呢。
在这里插入图片描述

这个队列内核数据结构中有一个type,用来标识,这个信息是谁发的,不是自己的不拿。
在这里插入图片描述

2.2接口

创建一个消息队列

在这里插入图片描述

看到没这些参数和共享内存的参数非常相似。

在这里插入图片描述

成功是返回消息队列标识符,失败返回-1。

向消息队列中放数据

在这里插入图片描述

msgp:发送的数据
msgsz:数据大小
msgflg:默认为0

接收数据

在这里插入图片描述
msgp:接收数据放到这里
msgsz:大小
msgtyp:类型

删除队列

在这里插入图片描述
这些接口都和共享内核相似。

3.信号量

关于信号量这里补充一些概念,也是为了后面的学习。

3.1信号量是什么

本质上是一个计数器,通常用来表示公共资源中,资源数量的多少问题。

信号量本质是一个计数器,那可以直接设置一个全局变量用来充当计数器吗?

不可以的,就如在匿名管道,设置一个全局变量,因为写时拷贝,父子进程看到的根本不是同一个全局变量。

进程通信之前要看到同一份资源,然后才能通信。
公共资源:被多个进程同时可以访问的资源。

以管道的方式通信的双方,注意到写端没写,读端一直在阻塞等待,读端没读,写端写完也在阻塞等待等等,而共享内存的方式呢,不管写端是否写完,读端一直在读。假设写一段数据,结果数据没写完就读。就造成了问题,这些数据就在共享内存中没有被保护。

访问没有被保护的公共资源导致数据不一致的问题。

我们来捋一捋这个过程。

为什么要让不同的进程看到同一份资源呢?
因为我想通信,让进程之间实现协同,但是进程具有独立性,因此需要先让进程看到同一份资源。 提出了方法,然后也引入了新的问题------>数据不一致问题。

我们未来将被保护起来的公共资源:临界资源
进程大部分的资源是独立的。

资源(内存,文件,网络)是要被使用的,如何被进程使用呢?
一定是该进程有对应的代码来访问这部分临界资源,这个代码临界区,其他代码叫非临界区。

在这里插入图片描述

那如何保护公共资源呢?
互斥和同步
互斥:当有两个进程想访问一个公共资源的时候,我们只能让一个进程进行完整的访问。
同步,在多线程哪里再说。

还有一组概念比较不好理解。
假如我想向缓冲区写一段数据,要求我必须要把这段数据写完你才能读,我没写完你就读不了。对于我来讲,我在写的时候,我要么不写,要写就写完才对你有意义。
这种要么不做,要做就做完,两态的这种情况:原子性。

假设张三要去银行给李四转账200
张三1000 李四1000
1000-200 1000+200
但是网络出现问题,转账失败,那银行不能就不管了,必须要把这200还给张三保持原样。
转账对于我们来说就只有两态,要么不转,要转就转完,虽然可能有中间过程,但是最终结果就是要么不转,要转就转完。不会说正在转账中。
这就是我们所有的原子性。

上面说这么多主要是为了说明,让多进程和多线程用来进行原子性的互斥和同步,信号量是其中一种解决方案。

3.2为什么要信号量

举个例子

去电影票看电影,我是不是只要坐到座位上,这个位置才是属于我的?
在这里插入图片描述
并不是,买票(票号,座位号)之后,这个位置在那个时间段就是属于我的。

看电影买票的本质:对放映厅中座位进行进行预定机制。

当我们想要某种资源的时候,我们可以进行预定
在这里插入图片描述
电影院就相当于共享资源。
在这里插入图片描述

在这里插入图片描述

每个进程想访问某些公共资源时,先申请信号量,申请成功就相当于预定了共享资源的某一部分资源,才能允许进入这个小资源里进行访问,申请失败,就不允许这个基础进入共享资源,进而达到保护共享资源以及其他基础的目的。

在这里插入图片描述
所有进程在访问公共资源之前,都必须先申请sem信号量---->必须申请sem信号量的前提,是所有进程必须先看到同一个信号量---->信号量本身就是公共资源---->
信号量是不是也要保护自己的安全呢?(++,- - 操作)---->信号量必须要保护这种操作的安全性,++,- -操作是原子的!!!

在这里插入图片描述

++,- -就是我们所说的PV操作

信号量本质是一把计数器,这个计数器可以在多进程环境中,可以被多进程先看到,必须给我们匹配上两种操作,P操作,V操作,让进程对我们的共享资源进行访问。

如果信号量初始值是1,代表共享资源当作整体来使用,一个进程申请成功了,其他进程不能申请------->互斥。

提供互斥功能的信号量---->二元信号量。

这里可能有这样一个问题,进程申请成功了信号量,是有资格去访问共享资源,但具体是去访问那个资源子部分不知道,可能存在多个进程访问同一个资源的问题,(也就是买票(票号有了,座位号还没有))该怎么办?

这部分其实是由我们在写代码时来确认不同进程去访问那个资源的。

3.3接口

获取信号量

在这里插入图片描述
nsems:想申请几个信号量

在这里插入图片描述
申请成功返回一个信号量集的标识符。失败返回-1。

删除信号量,或者获取信号量属性

在这里插入图片描述
semnum:可能你申请很多信号量,这个是信号量对应的下标,申请一个信号量,下标为0,申请10个信号量,假设我想对第10个信号量操作,下标就是9。

cmd:删除,或者获取信号量相关属性。

在这里插入图片描述

在这里插入图片描述

对信号量做PV操作

在这里插入图片描述
对指定信号量,做对应的操作

struct sembuf结构有三个变量
在这里插入图片描述
sem_num:对申请的多个信号量,哪一个进行操作。
sem_op:一般只有两种值,一种是1代表++,V操作。一种是-1代表- -,P操作。
sem_flg:默认为0

nspos:有几个这样的结构体。

允许对多个信号量同时进行PV操作。

4.IPC资源的组织方式

我们注意到共享内存,消息队列,和信号量的接口都非常相似。都有struct …id_ds这样的结构体,还有对应描述IPC属性资源的第一个字段都是一样的。
在这里插入图片描述
这三种共性可以看出来一个细节,这三种都叫做System V标准的进程间通信
所谓的标准就是,大家用的方式,接口设计,数据结构的设置都必须遵守某种标准。

那OS如何对管理这些(比如一会申请共享内存,一会消息队列,信号量)的呢?

OS并不是对它们本身进行管理,而是先描述在组织,对匹配的相关资源的内核结构进行管理。

因为当前三种方式的属性的第一个字段都是一样的。
在这里插入图片描述
在内核中操作系统可以维护一个指针数组。
在这里插入图片描述
结构体第一个成员的地址,在数字上,和结构体对象本身的地址数字是相等的!!
在这里插入图片描述
OS有对应的方式知道强转成什么结构体指针。

这就是OS对IPC资源的组织方式。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

goland 远程调试 remote debug

1、远程服务器装好go环境&#xff0c;并设置国内源 linux go安装 参考&#xff1a; 如何在 Debian / Ubuntu 上安装 Go 开发环境 - 知乎 设置国内源 go env -w GOPROXYhttps://goproxy.cn,direct 2、远程服务器安装dlv git clone https://github.com/derekparker/delve.gi…

移动端表格分页uni-app

使用uni-app提供的uni-table表格 网址&#xff1a;https://uniapp.dcloud.net.cn/component/uniui/uni-table.html#%E4%BB%8B%E7%BB%8D <uni-table ref"table" :loading"loading" border stripe type"selection" emptyText"暂无更多数据…

C++ STL之string初始

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

2023年【陕西省安全员B证】考试报名及陕西省安全员B证模拟试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年陕西省安全员B证考试报名为正在备考陕西省安全员B证操作证的学员准备的理论考试专题&#xff0c;每个月更新的陕西省安全员B证模拟试题祝您顺利通过陕西省安全员B证考试。 1、【多选题】《陕西省建设工程质量和…

【计算机组成原理】知识点巩固 - 存储器概述

目录 1、存储器分类 1.1、按存储介质分类 1.2、按存取方式分类 1.3、按信息的可改写性分类 1.4、按信息的可保存性分类 1.5、按功能和存取速度分类 2、存储器技术指标 2.1、存储容量 2.2、存取速度 3、存储系统层次结构 4、主存的基本结构 5、主存中数据的存放 5…

基于SSM+Vue的鲜花销售系统/网上花店系统

基于SSM的鲜花销售系统/网上花店系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringMyBatisSpringMVC工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 主页 管理员界面 摘要 鲜花销售系统是一个基于SSM&#xff08;Spring …

springboot项目中没有识别到yml文件解决办法

springboot项目中没有识别到yml文件解决办法 ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传] 1、这个意思就是没有配置数据库的数据源路径。所以需要配置数据源&#xff0c;比如mysql的驱动和路径。检查是否在properties或者yml文件中是否已经配置好。…

75基于matlab的模拟退火算法优化TSP(SA-TSP),最优路径动态寻优,输出最优路径值、路径曲线、迭代曲线。

基于matlab的模拟退火算法优化TSP(SA-TSP)&#xff0c;最优路径动态寻优&#xff0c;输出最优路径值、路径曲线、迭代曲线。数据可更换自己的&#xff0c;程序已调通&#xff0c;可直接运行。 75matlab模拟退火算法TSP问题 (xiaohongshu.com)

python django 小程序博客源码

开发工具&#xff1a; PyCharm&#xff0c;mysql5.7&#xff0c;微信开发者工具 技术说明&#xff1a; python django html 小程序 功能介绍&#xff1a; 用户端&#xff1a; 登录注册&#xff08;含授权登录&#xff09; 首页显示搜索文章&#xff0c;文章分类&#xf…

持续集成交付CICD:Jenkins通过API触发流水线

目录 一、理论 1.HTTP请求 2.调用接口的方法 3.HTTP常见错误码 二、实验 1.Jenkins通过API触发流水线 三、问题 1.如何拿到上一次jenkinsfile文件进行自动触发流水线 一、理论 1.HTTP请求 &#xff08;1&#xff09;概念 HTTP超文本传输协议&#xff0c;是确保服务器…

linux如何使用shell远程连接

简介&#xff1a;本文的一切条件基于redhat的linux操作系统。 1、创建虚拟机&#xff1a; 如有需要&#xff0c;请转至【linux基础】在VMware上安装RHEL9详细教程_融社的博客-CSDN博客 &#xff08;如若侵权&#xff0c;该篇立删&#xff09; 2、使用命令查看网段信息 打…

Android——Gradle插件项目根目录settings.gradle和build.gradle

一、settings.gradle结构分析 项目根目录下的settings.gradle配置文件示例&#xff1a; pluginManagement {/*** The pluginManagement.repositories block configures the* repositories Gradle uses to search or download the Gradle plugins and* their transitive depen…

gRPC 四模式之 双向流RPC模式

双向流RPC模式 在双向流 RPC 模式中&#xff0c;客户端以消息流的形式发送请求到服务器端&#xff0c;服务器端也以消息流的形式进行响应。调用必须由客户端发起&#xff0c;但在此之后&#xff0c;通信完全基于 gRPC 客户端和服务器端的应用程序逻辑。 为什么有了双向流模式…

Codeforces Round #909 (Div. 3)

A. Game with Integers 签到题&#xff0c;但是本蒟蒻11分钟才AC&#xff0c;主要还是英文题面不熟练&#xff0c;题目中加粗了after&#xff0c;只有下一步操作之后能被整除才胜利。 英文题面的加粗单词很重要&#xff0c;注意提高签到题速度。 B. 250 Thousand Tons of TNT…

leetcoe刷题日志-6N字形变换

将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 “PAYPALISHIRING” 行数为 3 时&#xff0c;排列如下&#xff1a; 之后&#xff0c;你的输出需要从左往右逐行读取&#xff0c;产生出一个新的字符串&#…

【数据结构】图的存储结构(邻接矩阵)

一.邻接矩阵 1.图的特点 任何两个顶点之间都可能存在边&#xff0c;无法通过存储位置表示这种任意的逻辑关系。 图无法采用顺序存储结构。 2.如何存储图&#xff1f; 将顶点与边分开存储。 3.邻接矩阵&#xff08;数组表示法&#xff09; 基本思想&#xff1a; 用一个一维数…

公共字段自动填充-Mybatis Plus实现

简历描述 使用ThreadLocal动态获取当前登录用户&#xff0c;从而解决MybatisPlus公共字段自动填充问题。达到简化编码的目的&#xff0c;使业务方法更加简洁。 问题分析 前面我们已经完成了后台系统的员工管理功能的开发&#xff0c;在新增员工时需要设置创建时间、创建人、…

Mol-Instructions:大模型赋能,药物研发新视野

论文标题&#xff1a;Mol-Instructions: A Large-Scale Biomolecular Instruction Dataset for Large Language Models 论文链接&#xff1a; https://arxiv.org/pdf/2306.08018.pdf Github链接&#xff1a; https://github.com/zjunlp/Mol-Instructions 模型下载&#xf…

Java 省考试院自学考试考籍管理系统

1) 项目简介 考籍管理系统是省考试院自学考试管理系统的一部分&#xff0c;包括考生考籍档案管理、考生免考管理、课程顶替、考籍转入转出管理、毕业管理和日志管理等功能模块。该项目的建设方便和加强了省考试院对自学考试考籍的一系列管理操作&#xff0c;社会效应明显。…

<MySQL> 如何合理的设计数据库中的表?数据表设计的三种关系

目录 一、表的设计 二、一对一关系 三、一对多关系 四、多对多关系 一、表的设计 数据库设计就是根据需要创建出符合需求的表。 首先根据需求找到体系中的关键实体对象&#xff0c;通常每个实体对象都会有一个表&#xff0c;表中包含了这个实体的相关属性。 再理清楚实体对…