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

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

  • 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资源的组织方式。

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

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

相关文章

IJ中配置TortoiseSVN插件:

文章目录 一、报错情况&#xff1a;二、配置TortoiseSVN插件&#xff1a; 一、报错情况&#xff1a; 由于公司电脑加密&#xff0c;TortoiseSVN菜单没有提交和更新按钮&#xff0c;所以需要使用IJ的SVN进行代码相关操作 二、配置TortoiseSVN插件&#xff1a; 需要设置一个svn.…

肖sir__linux讲解vim命令(3.1)

vim 命令 一、 vi/vim 编辑器共分为三种模式&#xff1a; 格式 &#xff1a;vim 文件名 命令模式&#xff08;Command mode&#xff09;&#xff0c;“ESC”或ctrlc键 输入模式&#xff08;Insert mode&#xff09; 底线命令模式&#xff08;Last line mode&#xff09; …

【uniapp】使用扫码插件,解决uni.scanCode扫码效率低的问题

1. 背景 uniapp 中自带的二维码扫描的 API 是 uni.scanCode&#xff0c;但有如下问题&#xff1a; 二维码扫描的效率不高&#xff0c;有些需要扫2秒左右 较小或模糊的一些二维码无法识别出来&#xff0c;多次扫同样的一个码可能出现扫码失败的情况 受环境影响大&#xff0c…

传输层——UDP协议

文章目录 一.传输层1.再谈端口号2.端口号范围划分3.认识知名端口号4.两个问题5.netstat与iostat6.pidof 二.UDP协议1.UDP协议格式2.UDP协议的特点3.面向数据报4.UDP的缓冲区5.UDP使用注意事项6.基于UDP的应用层协议 一.传输层 在学习HTTP等应用层协议时&#xff0c;为了便于理…

从0开始学习JavaScript--JavaScript DOM操作与事件处理

在前端开发中&#xff0c;DOM&#xff08;文档对象模型&#xff09;是一个至关重要的概念&#xff0c;它为JavaScript提供了一种与HTML和XML文档交互的方法。本文将深入探讨DOM的概念与作用&#xff0c;以及JavaScript与DOM之间的密切关系。 DOM的概念与作用 DOM是什么&#…

Vite 启动默认只能访问localhost解决方法

事情的经过是因为我需要测试本地项目的接口,然后因为burp默认不抓取localhost,127.0.0.1 .而且我也不想去修改burp. 所以我通过本地IP地址访问项目, 发现项目无法访问。 默认启动 所以特此记录一下。 在本地项目的package.json 中需要运行的脚本后 添加 --host即可。 具体如下…

IIs部署发布vue项目测试环境

打开【控制面板 > 程序>启用或关闭Windows功能 】 1、安装IIS: 把这些勾选上&#xff0c;点击确定下载。 2、安装.net: 把这些勾选上&#xff0c;点击确定下载。 3、搜索IIs打开&#xff1a; 4、右击【网站>添加网站 】进行配置&#xff0c;点击确定。 4、右击[项目le…

DRF纯净版项目搭建和配置

一、安装模块和项目 1.安装模块 pip install django pip install djangorestframework pip install django-redis # 按需安装 2.开启项目和api (venv) PS D:\pythonProject\env_api> django-admin startproject drf . (venv) PS D:\pythonProject\env_api> python ma…

YOLOv8改进 | DAttention (DAT)注意力机制实现极限涨点

论文地址&#xff1a; DAT论文地址 官方地址&#xff1a;官方代码的地址 代码地址&#xff1a;文末有修改了官方代码BUG的代码块复制粘贴即可 一、本文介绍 本文给大家带来的是YOLOv8改进DAT(Vision Transformer with Deformable Attention)的教程&#xff0c;其发布于2022…

使用 Python进行量化交易:前向验证分析

运行环境&#xff1a;Google Colab 1. 利用 yfinance 下载数据 import yfinance as yfticker AAPL df yf.download(ticker) df下载苹果的股票数据 df df.loc[2018-01-01:].copy()dfdf[change_tomorrow] df[Adj Close].pct_change(-1) df.change_tomorrow df.change_tom…

YOLOv8中训练参数中文解释

预测函数&#xff1a; from ultralytics import YOLO# Load a model model YOLO(yolov8n.pt) # Train the model model.train(datarD:\yolov8\ultralytics-main\data1.yaml, workers0, epochs100, batch16) 可选参数&#xff1a;

el-table树形数据隐藏子选择框

0 效果 1 代码 type是table数据中用来区分一级和二级的标识 // 隐藏子合同选择框 cellNone(row) {if (row.row.type 3 || row.row.type 4) {return "checkNone";} }, <style lang"scss" scoped>::v-deep {.checkNone .el-checkbox__input {displa…

数据结构与算法设计分析——常用搜索算法

目录 一、穷举搜索二、图的遍历算法&#xff08;一&#xff09;深度优先搜索&#xff08;DFS&#xff09;&#xff08;二&#xff09;广度优先搜索&#xff08;BFS&#xff09; 三、回溯法&#xff08;一&#xff09;回溯法的定义&#xff08;二&#xff09;回溯法的应用 四、分…

轻量级 Java 日志组件

日志记录功能在开发中很常用&#xff0c;不仅可以记录程序运行的细节&#xff0c;方便调试&#xff0c;也可以记录用户的行为&#xff0c;是框架中不可或缺的组件。为最大程度复用现有的组件&#xff0c;我们就地取材使用了 JDK 自带的 JUL&#xff08;java.util.logging&#…

学习模拟简明教程【Learning to simulate】

深度神经网络是一项令人惊叹的技术。 有了足够的标记数据&#xff0c;他们可以学习为图像和声音等高维输入生成非常准确的分类器。 近年来&#xff0c;机器学习社区已经能够成功解决诸如对象分类、图像中对象检测和图像分割等问题。 上述声明中的加黑字体警告是有足够的标记数…

手把手教你用C语言写出“走迷宫”小游戏(能看懂文字就会自己敲系列)

目录 设计迷宫地图 设计主角——小球 完整代码 这次教大家编写一个简单的“走迷宫”小游戏&#xff0c;我们可以通过键盘上的‘W’、‘S’、‘A’、‘D’四个键来控制一个“小球”向上&#xff0c;下&#xff0c;左&#xff0c;右移动&#xff0c;目的就是让这个“小球”从起…

Python3语法总结-数据转换②

Python3语法总结-数据转换② Python3语法总结二.Python数据类型转换隐式类型转换显示类型转换 Python3语法总结 二.Python数据类型转换 有时候我们&#xff0c;需要对数据内置的类型进行转换&#xff0c;数据类型的转换。 Python 数据类型转换可以分为两种&#xff1a; 隐式类…

原型网络Prototypical Network的python代码逐行解释,新手小白也可学会!!-----系列4

文章目录 原型网络进行分类的基本流程一、原始代码---计算欧氏距离&#xff0c;设计原型网络&#xff08;计算原型开始训练&#xff09;二、每一行代码的详细解释总结 原型网络进行分类的基本流程 利用原型网络进行分类&#xff0c;基本流程如下&#xff1a; 1.对于每一个样本…

信号完整性分析基础知识之有损传输线、上升时间衰减和材料特性(十):有损传输线在时域中的表现

如果高频衰减大于低频衰减&#xff0c;随着信号传输&#xff0c;上升时间将会增加。上升时间通常定义为边沿在最终值的 10% 到 90% 之间过渡的时间。这假设信号的边缘轮廓看起来有点高斯分布&#xff0c;中间是最快的斜率区域。对于该波形&#xff0c;10%−90% 的上升时间是有意…

MIB 6.1810实验Xv6 and Unix utilities(4)primes

难度: hard/moderate Write a concurrent prime sieve program for xv6 using pipes and the design illustrated in the picture halfway down this page and the surrounding text. This idea is due to Doug McIlroy, inventor of Unix pipes. Your solution should be in …