进程间通信2

news2024/12/26 20:56:25

3. system  V-IPC 

3.1 知识点

ipcs -a查看所有的ipc对象

在系统中他们都使用一种叫做 key 的键值来唯一标识,而且他们都是“持续性”资源——即他 们被创建之后,不会因为进程的退出而消失,而会持续地存在,除非调用特殊的函数或者命 令删除他们。

跟文件类型,进程每次“打开”一个 IPC 对象,就会获得一个表征这个对象的 ID,进而再使用这个 ID 来操作这个对象。IPC 对象的 key 是唯一的,但是 ID 是可变的。key 类似于文件的路径名,ID 类似于文件的描述符

系统中的多个进程,如果他们需要使用IPC 对象来通信,那么他们必须持有这个对象的 键值 key:

3.1.1 创建key

path:这是一个字符串,通常是指向一个存在的文件的路径。ftok() 将使用这个路径名来生成键。这个路径名应该是一个可访问的文件,因为它的存在与访问权限会影响键的生成。通常情况下,不同的路径名会生成不同的键。

proj:这是一个整数,通常取值在0到255之间。它用于在指定路径名的情况下生成唯一的键。如果不同的进程使用相同的路径名,可以通过不同的 proj 值生成不同的键。通常,每个IPC对象(消息队列、信号量、共享内存)都会有一个唯一的 proj 值。

这个函数需要注意的几点:

1,如果两个参数相同,那么产生的 key 值也相同。

2 ,第一个参数一般取进程所在的目录,因为在一个项目中需要通信的几个进程通常会 出现在同一个目录当中。

3 ,如果同一个目录中的进程需要超过 1 个 IPC 对象,可以通过第二个参数来标识。

4 ,系统中只有一套 key 标识,也就是说,不同类型的 IPC 对象也不能重复。 

3.1.2 查看和删除ipc

可以使用以下命令来查看或删除当前系统中的IPC 对象:

查看消息队列:ipcs -q

查看共享内存:ipcs -m

查看信号量:ipcs -s

查看所有的 IPC 对象:ipcs -a

删除指定的消息队列:ipcrm -q MSG_ID 或者 ipcrm -Q msg_key

删除指定的共享内存:ipcrm -m SHM_ID 或者 ipcrm -M shm_key

删除指定的信号量:ipcrm -s SEM_ID 或者 ipcrm -S sem_key

3.2  消息队列

3.2.1 创建消息队列,获取操作id

权限只有读和写,执行权限是无效的,例如 0777 跟 0666 是等价的。

删除消息队列

可以用

ipcrm -q 消息队列id

或者

ipcrm -Q 消息队列的key

3.2.2 发送和接收消息

使用这两个收、发消息函数需要注意以下几点:

1,发送消息时,消息必须被组织成以下形式:

struct msgbuf

{

long mtype;  // 消息的标识

char mtext[1];    // 消息的正文

};

也就是说:发送出去的消息必须以一个 long 型数据打头,作为该消息的标识,后 面的数据则没有要求。

2,消息的标识可以是任意长整型数值,但不能是 0L。

3,参数 msgsz 是消息中正文的大小,不包含消息的标识。

3.2.3 代码

ipc_msg_que_send.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//消息队列的使用

struct msgbuf
{
    long mtype;//消息的标识(整数)
    char mtext[128];//消息的正文
};


int main()
{
    //创建key
    key_t key = ftok("/",0);

    //创建消息队列,获取操作消息队列的id
    int msg_id = msgget(key,IPC_CREAT | 0777);
    if(msg_id == -1)
	{
		perror("msg_id error");
	}
    struct msgbuf buf;
    buf.mtype = 1;
    while(1){
        memset(buf.mtext,0,sizeof(buf.mtext));//清零
        fgets(buf.mtext,sizeof(buf.mtext),stdin);//获取输入
        //发送
        msgsnd(msg_id,&buf,128,0);
    }
    return 0;
}
ipc_msg_que_receive.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//消息队列的使用

struct msgbuf
{
    long mtype;//消息的标识(整数)
    char mtext[128];//消息的正文
};


int main()
{
    //创建key
    key_t key = ftok("/",0);

    //创建消息队列,获取操作消息队列的id
    int msg_id = msgget(key,IPC_CREAT | 0777);
    if(msg_id == -1)
	{
		perror("msg_id error");
	}
    struct msgbuf msg;
    while(1){
        memset(msg.mtext,0,sizeof(msg.mtext));//清零
        //接收
        msgrcv(msg_id,&msg,128,1,0);

        printf("receive msg: %s\n",msg.mtext);
    }
    return 0;
}

3.2.4 练习

1.设计一个消息队列程序,发送时,通过键盘来指定消息标识接收时,通过键盘来指定要接收的消息

ipc_que_test_w.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//设计一个消息队列程序,发送时,通过键盘来指定消息标识接收时,通过键盘来指定要接收的消息
struct msgbuf{
    long mtype;//消息的标识(整数)
    char mtext[128];//消息的正文
};

int main()
{
    //创建key
    key_t key = ftok("/",4);
    //创建消息队列
    int msg_id = msgget(key,IPC_CREAT | 0777);
    if(msg_id==-1){
        perror("msg_id error");
    }

    struct msgbuf buf;

    
    int type;

    while(1){
        memset(buf.mtext,0,sizeof(buf.mtext));

        //键盘输入消息标识符
        printf("请输入发送消息标识符/(99退出):");
        scanf("%d",&type);
        if(type==99){
            break;
        }

        buf.mtype = type;

        getchar();//清空缓冲区

        //键盘接收消息正文
        printf("请输入消息正文:");

        fgets(buf.mtext,sizeof(buf.mtext),stdin);

        //发送
        msgsnd(msg_id,&buf,128,0);
    }

    return 0;
}
ipc_que_test_r.c
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/msg.h>

//设计一个消息队列程序,发送时,通过键盘来指定消息标识接收时,通过键盘来指定要接收的消息
struct msgbuf{
    long mtype;//消息的标识(整数)
    char mtext[128];//消息的正文
};

int main()
{
    //创建key
    key_t key = ftok("/",4);
    //创建消息队列
    int msg_id = msgget(key,IPC_CREAT | 0777);
    if(msg_id==-1){
        perror("msg_id error");
    }

    struct msgbuf buf;
    
    int type;

    while(1){

        memset(buf.mtext,0,sizeof(buf.mtext));

        //键盘输入消息标识符
        printf("请输入接收消息标识符/(99退出):");
        scanf("%d",&type);

        if(type==99){
            break;
        }

        buf.mtype = type;

        printf("\n");

        //发送
        int ret = msgrcv(msg_id,&buf,128,type,0);
    
        printf("接收到标识符尾%d的消息为: %s\n",type,buf.mtext);
    }

    return 0;
}

3.3 共享内存

3.3.1 使用步骤

使用共享内存的一般步骤是:

1,获取共享内存对象的 ID

2,将共享内存映射至本进程虚拟内存空间的某个区域

3,当不再使用时,解除映射关系

4,当没有进程再需要这块共享内存时,删除它。

3.3.2 获取共享内存的 ID

共享内存的大小必须是偶数

3.3.3 共享内存映射

3.3.4 代码

ipc_share_w.c
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/shm.h>


//共享内存
int main()
{
    //创建key  和  获取共享内存id
    int share_id = shmget(ftok("/",1),1024,IPC_CREAT | 0777);

    //对共享内存进行映射  将这个共享内存映射至本进程的虚拟空间
    char* p = shmat(share_id,NULL,0);//p就是共享内存的首地址

    fgets(p,1024,stdin);//获取键盘输入
    
    shmdt(p);//解除映射


    return 0;
}
ipc_share_r.c
#include<sys/types.h>
#include<sys/stat.h>
#include<stdio.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<sys/ipc.h>
#include<sys/shm.h>


//共享内存
int main()
{
    //创建key  和  获取共享内存id
    int share_id = shmget(ftok("/",1),1024,IPC_CREAT | 0777);

    //对共享内存进行映射  将这个共享内存映射至本进程的虚拟空间
    char* p = shmat(share_id,NULL,0);//p就是共享内存的首地址

    printf("%s\n",p);//打印

    shmdt(p);//解除映射


    return 0;
}

3.4 信号量

一些基本概念如下:

1,多个进程或线程有可能同时访问的资源 (变量、链表、文件等等) 称为共享资源, 也叫临界资源 (critical resources) 。

2,访问这些资源的代码称为临界代码,这些代码区域称为临界区 (critical zone) 。

3,程序进入临界区之前必须要对资源进行申请,这个动作被称为 P 操作,这就像你要 把车开进停车场之前,先要向保安申请一张停车卡一样,P 操作就是申请资源,如果申请成 功,资源数将会减少。如果申请失败,要不在门口等,要不走人。

4,程序离开临界区之后必须要释放相应的资源,这个动作被称为 V 操作,这就像你把 车开出停车场之后,要将停车卡归还给保安一样,V 操作就是释放资源,释放资源就是让资 源数增加。

system-V 的信号量并不是单个的值,而是一组 (事实上是一个数 组) 信号量元素构成的

信号量跟前面的 MSG 和 SHM 有极大的不同,SEM 不是用来传输数据的,而 是作为“旗语”,用来协调各进程或者线程工作的。

信号量的 P、V 操作最核心的特征是:他们是原子性的,也就是说对信号量元素的值的 增加和减少,系统保证在 CPU 的电气特性级别上不可分割,这跟整型数据的加减法有本质 的区别。

3.4.1 创建信号量获取操作id

创建信号量时,还受到以下系统信息的影响:

1,SEMMNI:系统中信号量的总数最大值。

2,SEMMSL:每个信号量中信号量元素的个数最大值。

3,SEMMNS:系统中所有信号量中的信号量元素的总数最大值。

Linux 中,以上信息在/proc/sys/kernel/sem 中可查看。

3.4.2 获取或者设置信号量的相关属性

使用以上函数接口,需要注意以下几点:

1,这是一个变参函数,根据cmd 的不同,可能需要第四个参数,第四个参数是一个如 下所示的联合体,用户必须自己定义:

union semun

{

int   val;        /* 当 cmd 为 SETVAL 时使用 */

struct semid_ds *buf;       /* 当 cmd 为 IPC_STAT 或 IPC_SET 时使用 */

unsigned short   *array;   /* 当 cmd 为 GETALL 或 SETALL 时使用 */

struct seminfo   *__buf;   /* 当 cmd 为 IPC_INFO 时使用 */

};

3.4.3 对信号量进行 P/V 操作,或者等零操作

使用以上函数接口需要注意以下几点:

1,信号量操作结构体的定义如下:

struct sembuf

{

unsigned short short  sem_num;    /* 信号量元素序号 (数组下标)  */

short   sem_op;       /* 操作参数 */

short sem_flg;                  /* 操作选项 */

};

请注意:信号量元素的序号从 0 开始,实际上就是数组下标。

1. 当 sem_op 大于 0 时:进行 V 操作,即信号量元素的值 (semval) 将会被加上 sem_op 的值。

2. 当 sem_op 等于 0 时:进行等零操作

3. 当 sem_op 小于 0 时:进行 P 操作,即信号量元素的值 (semval) 将会被减去 sem_op 的绝对值。

重点

一个信号量(Semaphore)通常对应一个 struct sembuf 数组,但这个 struct sembuf 数组可以包含多个操作,而不仅限于一个操作。让我更详细地解释:

一个信号量对应一个 struct sembuf 数组:每个信号量在信号量集合中都有一个唯一的标识符,你可以使用这个标识符来指定要操作的信号量。通常情况下,你会创建一个 struct sembuf 数组,其中的每个元素描述了对某个特定信号量的操作。

一个 struct sembuf 数组可以包含多个操作:在 struct sembuf 数组中,你可以定义多个操作,这些操作都是针对同一个信号量的。每个 struct sembuf 结构体包括三主要字段:sem_num(信号量集合中的索引,用于指定操作的目标信号量)、sem_op(要执行的操作,通常是 -1 表示 P 操作或 1 表示 V 操作),以及 sem_flg(标志位,通常设置为 0)。

所以,你可以创建一个 struct sembuf 数组,其中的每个元素描述了对同一个信号量的不同操作。这种方式允许你在一个数组中组织多个操作,以便在一次函数调用中执行多个操作,这些操作都是基于同一个信号量的。

总结,一个信号量对应一个 struct sembuf 数组,而这个数组可以包含多个操作,用于对该信号量执行不同的操作。这是一种有效的方式来管理并发访问共享资源或进行同步操作。

3.4.5 代码

sys_sem_w.c
#include <stdio.h>
#include <fcntl.h>              
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>           
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/sem.h>

#define DATA 0
#define SPACE 1

//共享内存的结合信号量,循环写入0-9
union semun
{
	int                          val;       /* 当 cmd 为 SETVAL 时使用 */
	struct semid_ds *buf;     /* 当 cmd 为 IPC_STAT 或 IPC_SET 时使用 */
	unsigned short         *array;  /* 当 cmd 为 GETALL 或 SETALL 时使用 */
	struct seminfo          *__buf; /* 当 cmd 为 IPC_INFO 时使用 */
};


void sem_init(int id, int semnum, int val)
{
	union semun a;
	a.val = val;
	
	semctl(id, semnum,SETVAL, a);
}

void sem_p(int id,int semnum)//资源-1
{
	struct sembuf sops[1];
	sops[0].sem_num = semnum;
	sops[0].sem_op = -1;
	sops[0].sem_flg = 0;
	
	semop(id, sops, 1);
}

void sem_v(int id, int semnum)//资源+1
{
	struct sembuf sops[1];
	sops[0].sem_num = semnum;
	sops[0].sem_op = 1;
	sops[0].sem_flg = 0;
	
	semop(id, sops, 1);
}
		   
int main()
{
	//创建共享内存的IPC对象
	int shmid = shmget(ftok("/",2), 2,IPC_CREAT | 0666);//共享内存的大小必须是偶数,实际上在本代码中只需要1个
	
	char *p = shmat(shmid, NULL, 0);//映射
	
	//创建信号量的IPC对象
	int sem_id = semget(ftok("/",3),2,IPC_CREAT | 0666);
	
	//数据当0
	//数据初始化为0个
	sem_init(sem_id, DATA, 0);
	
	//空间当1
	//空间初始化为1个
	sem_init(sem_id, SPACE, 1);
	
	char *msg = "0123456789";
	int i = 0;
	
	while(1)
	{
		//1个空间
		//0个数据
		//写入数据
		sem_p(sem_id,SPACE);//p -1 s 0
		memcpy(p, msg+i, 1);//0,1,2,3,4,5,6,7,8,9
		sem_v(sem_id,DATA);//v +1   d 1
		
		i = (i+1)%10;//10%10=0
		
	}
}
 sys_sem_r.c
#include <stdio.h>
#include <fcntl.h>              
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>           
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/msg.h>
#include <sys/sem.h>
//共享内存的结合信号量,循环写入0-9
//首先搞清楚一个概念 信号量集合中可以有多个信号量,一个信号量对应一个struct sembuf sops[]结构体数组
//, 一个信号量可以有多个资源也就是说一个struct sembuf sops[]结构体数组可以有多个元素

//这里两个宏定义 的0  1 可以理解为信号量的下标(因为有的情况会有很多个信号量
//我们的例子中只有两个信号量,如果有多个的话 就是 0  1 2 3 4.....这样排下去)
#define DATA 0
#define SPACE 1

//获取或设置信号量的相关属性涉及到的结构体
union semun
{
	int        val;       /* 当 cmd 为 SETVAL 时使用 */
	struct semid_ds *buf;     /* 当 cmd 为 IPC_STAT 或 IPC_SET 时使用 */
	unsigned short         *array;  /* 当 cmd 为 GETALL 或 SETALL 时使用 */
	struct seminfo          *__buf; /* 当 cmd 为 IPC_INFO 时使用 */
};


//信号量初始化
void sem_init(int id, int semnum, int val)
{
	union semun a;//联合体
	a.val = val;
	//获取或设置信号量的相关属性
	//第二个参数是指定哪个信号量
	//这是一个变参函数 第三个参数SETVAL(设置该信号量元素的值) 会影响第四个参数应该传递啥(参数是个联合体)
	//这里带入解释一下我们假如传来的的是我们定义的SPACE信号量,val参数为1
	//参数semnum是SPACE   所以semctl函数的意思是,给SPACE信号量 设置该信号量元素的值为 a联合体
	//因为我们第三个参数使用的是SETVAL 所以a联合体中主要用到属性val被赋值为该信号量元素的值为1
	semctl(id, semnum,SETVAL, a);
}

//p操作
void sem_p(int id,int semnum)//资源-1
{
	//这个结构体数组可以有多个元素,代表同一个信号量可以携带多个资源并在一个函数内对多个资源操作
	//这里我们的一个信号量只设置了一个资源,所以结构体数组的大小为1,千万别被迷惑了
	struct sembuf sops[1];
	sops[0].sem_num = semnum; //semnum指定信号量
	sops[0].sem_op = -1;
	sops[0].sem_flg = 0;
	
	//对信号量进行pv操作的函数,这个函数的第二参数需要一个struct sembuf类型的结构体数组
	//所以我们上面才会定义一个结构体数组
	//第三个参数 就是你的结构体数组中有几个元素
	semop(id, sops, 1);
}

//v操作
void sem_v(int id, int semnum)//资源+1
{
	struct sembuf sops[1];
	sops[0].sem_num = semnum;
	sops[0].sem_op = 1;
	sops[0].sem_flg = 0;
	
	semop(id, sops, 1);
}
		   
int main()
{
	//创建共享内存的IPC对象,获取操作id
	int shmid = shmget(ftok("/",2), 2,IPC_CREAT | 0666);//共享内存的大小必须是偶数,实际上在本代码中只需要1个
	
	//将共享内存 映射 到虚拟内存
	char *p = shmat(shmid, NULL, 0);
	
	//创建信号量的IPC对象 ,,2,代表这里我们创建2个信号量
	int sem_id = semget(ftok("/",3),2,IPC_CREAT | 0666);
	

	//初始化信号量
	//数据信号量 的下标为0
	//数据初始化为0个
	sem_init(sem_id, DATA, 0);//这里的DATA代表它在信号量集合中的下标(就是我们上面的宏定义),0代表这个信号量的大小
	//空间信号量我们规定下标为1
	//空间初始化为1个
	sem_init(sem_id, SPACE, 1);
	
	char *msg = "0123456789";
	int i = 0;
	
	while(1)
	{
		//1个空间
		//0个数据
		//写入数据
		//p操作
		sem_p(sem_id,DATA);//p -1 s 0
		fprintf(stderr,p);
		//v操作
		sem_v(sem_id,SPACE);//v +1   d 1
		
		i = (i+1)%10;//10%10=0
		
	}
}

编译运行

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

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

相关文章

带你手搓阻塞队列——自定义实现

&#x1f308;&#x1f308;&#x1f308;今天给大家分享的是——阻塞队列的自定义实现&#xff0c;通过自定义实现一个阻塞队列&#xff0c;可以帮助我们更清晰、更透彻的理解阻塞队列的底层原理。 清风的CSDN博客 &#x1f6e9;️&#x1f6e9;️&#x1f6e9;️希望我的文章…

C++stack

目录 1.什么是stack 2.容器适配器 3.stack的使用 top push pop 4.模拟实现stack 1.什么是stack 1. stack是一种容器适配器&#xff0c;专门用在具有后进先出操作的上下文环境中&#xff0c;其删除只能从容器的一端进行 元素的插入与提取操作。(后进先出) 2. stack是作为容…

进程间通信方式——管道

进程间通信方式——管道 1、管道2、匿名管道2.1 创建匿名管道2.2 进程间通信 3、有名管道3.1 创建有名管道3.2 进程间通信 4、管道的读写行为 原文链接 1、管道 管道的是进程间通信&#xff08;IPC - InterProcess Communication&#xff09;的一种方式&#xff0c;管道的本质…

深入 C 语言和程序运行原理 实战项目代码在CentOS 7上编译

cat /etc/redhat-release看到操作系统的版本是CentOS Linux release 7.6.1810 (Core)&#xff0c;uname -r可以看到内核版本是3.10.0-957.21.3.el7.x86_64。 安装gtest 参考博客《使用gtest和lcov测试代码覆盖率》 wget https://github.com/google/googletest/archive/refs/…

YOLOv5项目实战(5)— 算法模型优化和服务器部署

前言:Hello大家好,我是小哥谈。近期,作者所负责项目中的算法模型检测存在很多误报情况,为了减少这种误报情况,作者一直在不断优化算法模型。鉴于此,本节课就给大家详细介绍一下实际工作场景中如何去优化算法模型和进行部署,另外为了方便大家进行模型训练,作者在文章中提…

Promise的resolve和reject方法(手写题)

1.resolve 2.reject 3.手写 1.resolve //构造函数上添加 resolve 方法 Promise.resolve function (value) {return new Promise((resolve, reject) > {if (value instanceof Promise) {value.then((val) > {resolve(val)},(err) > {reject(err)})} else {resolve(v…

国内首所国际职业培训学院落户深圳盐田揭幕开业

11月26日&#xff0c;中科国药•中科大有大健康上市企业孵化平台迎来了国内首所国际职业学院——深圳市盐田区国际职业培训学院的正式落成与揭幕仪式。中科大有高新科技有限公司董事长、长江商学院MBA\FMBA金融导师、深圳市中科国药生物医药研究院理事长、深圳市盐田区国际职业…

HarmonyOS到底有哪些独特之处?你真正了解鸿蒙多少!

鸿蒙系统太炸裂了&#x1f4a5;我已经后悔了&#x1f62d;后悔没早点学习鸿蒙 HarmonyOS 概念&#xff0c;系统定位 1&#xff1a;鸿蒙系统是由华为公司自主研发的全球化开放源代码操作系统&#xff0c;它具有以下特别之处&#xff1a; 2&#xff1a;分布式架构&#xff1a;…

华为鸿蒙工程师成“热门”!最高年薪160万,只看技术不看年龄

引言&#xff1a; 今天&#xff0c;在互联网行业&#xff0c;超过30岁的工程师往往被认为是“码农”的生命周期终点。然而&#xff0c;华为鸿蒙系统的崛起&#xff0c;却再次给予了这些35岁以上的工程师们第二春的机会。国内一线互联网公司纷纷向鸿蒙系统靠拢&#xff0c;导致…

c语言练习13周(1~5)

输入任意整数n求以下公式和的平方根。 读取一系列的整数 X&#xff0c;对于每个 X&#xff0c;输出一个 1,2,…,X 的序列。 编写double fun(int a[M][M])函数&#xff0c;返回二维数组周边元素的平均值&#xff0c;M为定义好的符号常量。 编写double fun(int a[M])函…

设计模式-结构型模式之桥接设计模式

文章目录 三、桥接模式 三、桥接模式 桥接模式&#xff08;Bridge&#xff09;是用于把抽象化与实现化解耦&#xff0c;使得二者可以独立变化。它通过提供抽象化和实现化之间的桥接结构&#xff0c;来实现二者的解耦。 这种模式涉及到一个作为桥接的接口&#xff0c;使得实体类…

Unity 使用Horizontal Layout Group和Toggle制作多个水平开关按钮实现自动排列和单个点击放大后的自动排列。

Unity的布局组件Horizontal Layout Group是很好用的&#xff0c;当然也包括其它布局组件也一样好用。 比如要实现多按钮开关自动水平排列&#xff0c;那么就可以使用它了。 首先我们为按钮创建个父物体&#xff08;我这里使用了Scroll View中的Content作为父物体&#xff09;…

elementUI实现根据屏幕大小自适应换行,栅格化布局

需求&#xff1a; 默认一行展示4个卡片&#xff1b;当屏幕小于某个大小的时候&#xff0c;一行展示3个卡片&#xff1b;再小就展示2个&#xff1b;以此类推&#xff0c;最小就展示1个。 效果卡片样式如下图&#xff1a; 默认一行4个 屏幕缩小到某个阈值&#xff0c;一行展示…

11.26电梯控制器设计分析

项目三 电梯控制器设计&#xff08;*****&#xff09; 设计一个多楼层的电梯控制器系统&#xff0c;并能在开发板上模拟电梯运行状态。可以利用按键作为呼叫按键&#xff0c;数码管显示电梯运行时电梯所在楼层&#xff0c;led灯显示楼层叫梯状态。 就是初始默认在1楼&#xff0…

【LeetCode:1657. 确定两个字符串是否接近 | 计数 + 阅读理解】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

Kaggle-水果图像分类银奖项目 pytorch Densenet GoogleNet ResNet101 VGG19

一些原理文章 卷积神经网络基础&#xff08;卷积&#xff0c;池化&#xff0c;激活&#xff0c;全连接&#xff09; - 知乎 PyTorch 入门与实践&#xff08;六&#xff09;卷积神经网络进阶&#xff08;DenseNet&#xff09;_pytorch conv1x1_Skr.B的博客-CSDN博客GoogLeNet网…

Web 实时消息推送

Web 实时消息推送详解 什么是消息推送&#xff1f; 推送的场景比较多&#xff0c;比如有人关注我的公众号&#xff0c;这时我就会收到一条推送消息&#xff0c;以此来吸引我点击打开应用。 消息推送通常是指网站的运营工作等人员&#xff0c;通过某种工具对用户当前网页或移…

最小化安装 Neokylin7.0 用于搭建 Hadoop 集群

文章目录 环境搭建背景虚拟机创建和环境配置安装过程注意事项虚拟机设置软件选择KOUMP系统分区网络和主机名打开以太网&#xff0c;并记录信息配置 IPv4修改主机名 创建用户 hadoop完全分布式搭建-CSDN博客 环境搭建背景 为什么不从hadoop100或者hadoop101开始&#xff0c;而是…

引领数据趋势:2023年最值得关注的十大ETL数据集成工具

在这个数据至上的时代&#xff0c;对于以数据为驱动的组织来说&#xff0c;建立一个信息集中的强大源头是成功的关键。众多企业依靠ETL工具来管理和理解它们的数据。 ETL&#xff0c;即提取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;、加载&…

matlab基于线性二次调节器(LQR)法实现机器人路径规划可变轨迹跟踪

1、内容简介 略 可以交流、咨询、答疑 2、内容说明 基于线性二次调节器(LQR)法实现机器人路径规划可变轨迹跟踪 3、仿真分析 略 load path.mat %% 轨迹处理 % 定义参考轨迹 refPos_x path(:,1); refPos_y path(:,2); refPos [refPos_x, refPos_y];% 计算航向角和曲率 …