Linux应用开发:进程间通信 System V

news2024/12/23 22:34:47

目录

1、查看删除IPC对象

1.1 IPC对象

1.2 ipcs 命令查看系统中的 IPC 对象

1.3 ipcrm 命令删除系统中的 IPC 对象

2、共享内存

2.1 共享内存简介

2.2 共享内存相关API

2.2.1 shmget:创建共享内存

2.2.2 shmat:映射共享内存

2.2.3 shmdt:断开共享内存连接

2.2.4 shmctl:共享内存管理(删除)

2.2.5 API使用小技巧 

2.3 应用程序:共享内存 server.c

2.4 应用程序:共享内存 client.c

3、消息队列

3.1 消息队列简介

3.2 消息队列相关API

3.2.1 msgget:创建消息队列

3.2.2 msgsnd:消息队列发送

3.2.3 msgrcv:消息队列接收

3.2.4 msgctl:消息队列管理(删除)

3.3 应用程序:消息队列 server.c

3.4 应用程序:消息队列 client.c

4、信号量(信号灯)

4.1 信号量简介

4.2 信号量相关API

4.2.1 semget:创建信号量

4.2.2 semctl:控制信号量

4.2.3 semop:完成对信号量的P操作或V操作

4.2.4 init_sem:封装信号量初始化

4.2.5 sem_p:封装p操作,即获取信号量,信号量-1

4.2.6 sem_v:封装v操作,即释放信号量,信号量+1

4.3 应用程序:信号量 server.c :在程序中轮流获取与释放信号量

4.4 应用程序:信号量 client.c :在程序中查询还有多少信号量


1、查看删除IPC对象

1.1 IPC对象

        IPC(Inter-Process Communication)进程间通信,提供了各种进程间通信的方法,使用IPC通信的步骤为:

1.2 ipcs 命令查看系统中的 IPC 对象

1.ipcs -m 共享内存
2.ipcs -s 信号量
3.ipcs -q 消息队列

1.3 ipcrm 命令删除系统中的 IPC 对象

ipcrm -m id

创建的 IPC 对象如果不删除的话会一直保留在系统中 

2、共享内存

2.1 共享内存简介

1、共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝
2、为了在多个进程间交换信息, 内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间,进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
3、由于多个进程共享一段内存,因此也需要依靠某种同步机制,如 互斥锁 和 信号量 等。

2.2 共享内存相关API

2.2.1 shmget:创建共享内存

int shmget(key_t key, size_t size, int shmflg)
/*
功能:得到一个共享内存标识符或创建一个共享内存对象并返回共享内存标识符
参数:
    @key:IPC_PRIVATE 或 ftok 的返回值
    @size:共享内存区大小
    @shmflg:IPC_CREAT:当shmflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的共享内存,
                        则新建一个共享内存;如果存在这样的共享内存,返回此共享内存的标识符
             IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的共享内存,则新建一个消息队列;
                                 如果存在这样的共享内存则报错
返回值:成功返回共享内存的标识符吗,失败返回-1
*/

2.2.2 shmat:映射共享内存

void *shmat(int shmid, const void *shmaddr, int shmflg)
/*
功能:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,
     随后可像本地空间一样访问
参数:
    @shmid:共享内存标识符
    @shmaddr::将共享内存映射到指定地址,若为 NULL ,则表示由系统自动完成映射
    @shmflg:SHM_RDONLY:为只读模式,其他为读写模式
返回值:成功返回映射后的地址,失败返回-1
*/

2.2.3 shmdt:断开共享内存连接

int shmdt(const void *shmaddr)
/*
功能:与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
参数:
    @shmid:共享内存标识符
返回值:成功返回0,失败返回-1
*/

2.2.4 shmctl:共享内存管理(删除)

int shmctl(int shmid, int cmd, struct shmid_ds *buf)
/*
功能:连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,
     随后可像本地空间一样访问
参数:
    @shmid:共享内存标识符
    @cmd:IPC_STAT:获取对象属性
          IPC_SET:设置对象属性
          IPC_RMID:删除这片共享内存
    @buf:指定 IPC_STAT/IPC_SET 时用以保存/设置属性
返回值:成功返回映射后的地址,失败返回-1
*/

2.2.5 API使用小技巧 

1、key_t ftok(const char *pathname, int proj_ id);

参数pathname为路径,必须是真实存在且可以访问的路径。

参数proj_id是int类型数字,且必须传入非零值。

ftok函数内部会根据路径和proj_id通过算法生成一个独一无二的key_t返回值。多进程通信时,需要通信双方使用同一个key值,因此双方使用的ftok参数应该一致。

2、shmget 的 size

该参数用于确定共享内存大小。

一般而言是4096的整数倍,因为内存的块的大小就是4KB即4096B。因此即便我们需要的空间大小不是块大小的整数倍,操作系统实际上也还是分配块的倍数个。但在使用时,那些超过size大小的多余分配空间不能访问。  

3、shmget 的 shmflg

常用的方式:

shmget(..., IPC_CREAT | 权限)                             创建失败不报错返回已有shmid

shmget(..., IPC_CREAT | IPC_EXCL | 权限)         创建失败报错返回-1

其中 IPC_EXCL 无法单独使用

通常情况下在多进程通信时,创建方使用IPC_CREAT | IPC_EXCL,接收方使用0即可。

4、shmat 的 shmaddr 和 shmflg

shmaddr一般填nullptr即可代表让内核自己确定位置。

shmflg用于确定挂接方式,一般填0

2.3 应用程序:共享内存 server.c

#define	SEG_SIZE	((size_t)100)	

int main()
{
	long now;
	int n;
	key_t key_info;
	
	if ((key_info = ftok ("/app", 'i')) < 0)
	{
		perror ("ftok info");
		exit (-1);
	}

	int seg_id = shmget(key_info, SEG_SIZE, IPC_CREAT | 0777);

	char *mem_ptr = shmat(seg_id, NULL, 0);

	for (n = 0; n < 60; n++) 
	{
		time(&now);		
		strcpy(mem_ptr, ctime(&now));	
		sleep(1);		
	}
	shmctl(seg_id, IPC_RMID, NULL);
}

2.4 应用程序:共享内存 client.c

#define	SEG_SIZE	((size_t)100)
int main()
{
	key_t key_info;
	
	if ((key_info = ftok ("/app", 'i')) < 0)
	{
		perror ("ftok info");
		exit (-1);
	}
	int seg_id = shmget(key_info, SEG_SIZE, 0777);
	
	char *mem_ptr = shmat(seg_id, NULL, 0);
	
	printf("The time, direct from memory: ..%s", mem_ptr);
	shmdt(mem_ptr);
}

3、消息队列

3.1 消息队列简介

1、消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。
2、消息队列可以按照类型来发送接收消息。

3.2 消息队列相关API

3.2.1 msgget:创建消息队列

int msgget(key_t key, int flag);
/*
功能:用来创建和访问一个消息队列
参数:
    @key:和消息队列关联的 key 值
    @flag:消息队列的访问权限
返回值:成功返回消息队列ID,失败返回-1
*/

3.2.2 msgsnd:消息队列发送

int msgsnd(int msqid, const void *msgp, size_t size, int flag);
/*
功能:消息的发送
参数:
    @msqid:消息队列的 ID
    @msgp:指向消息的指针。常用消息结构 msgbuf 如下:
            struct {
                long mytype;    //消息类型
                char mtext[N];  //消息正文
            }
    @size:消息正文的字节数
    @flag: IPC_NOWAIT 消息没有发送完成函数也会立即返回。
           0:直到发送完成函数才返回
返回值:成功返回0,失败返回-1
*/

3.2.3 msgrcv:消息队列接收

int msgrcv(int msgid, void* msgp, size_t size, long msgtype, int flag);
/*
功能:消息的接收
参数:
    @msqid:消息队列的 ID
    @msgp:接收消息的缓冲区
    @size:消息正文的字节数
    @msgtype:=0:接收消息队列中第一个消息。
              >0:接收消息队列中第一个类型为 msgtyp 的消息
              <0:接收消息队列中类型值不小于 msgtyp 的绝对值且类型值又最小的消息。
    @flag: IPC_NOWAIT:若没有消息,进程会立即返回 ENOMSG 。
           0:若无消息函数会一直阻塞
返回值:成功接收到的消息的长度,失败返回-1
*/

3.2.4 msgctl:消息队列管理(删除)

int msgctl(int msgqid, int cmd, struct msqid_ds *buf)
/*
功能:消息队列控制
参数:
    @msgqid:和消息队列关联的 key 值
    @cmd:IPC_STAT:读取消息队列的属性,并将其保存在 buf 指向的缓冲区中。
          IPC_SET:设置消息队列的属性。这个值取自 buf 参数。
          IPC_RMID:从系统中删除消息队列。
    @buf:消息队列缓冲区
返回值:成功返回0,失败返回-1
*/

3.3 应用程序:消息队列 server.c

#define QUEUE_MSG_LEN	256
#define PROJ_ID		'g'
#define PATH_NAME	"/app"
#define SERVER_MSG	1
#define CLIENT_MSG	2
struct msg 
{
	long type;
	long msgtype;
	unsigned char text[QUEUE_MSG_LEN];
};

int main(void)
{
	struct msg msg_buf;
	int qid;
	int msglen;
	int i=0;

	key_t msgkey;
	msgkey = ftok(PATH_NAME, PROJ_ID);
	qid = msgget(msgkey, IPC_CREAT | 0666);
	while (1) 
	{
		printf("server send: ");
		fgets(msg_buf.text, QUEUE_MSG_LEN, stdin);
		if (strncmp("exit", msg_buf.text, 4) == 0) 
		{
			msgctl(qid, IPC_RMID, NULL);
			break;
		}
		msg_buf.text[strlen(msg_buf.text) - 1] = '\0';
		msg_buf.type = SERVER_MSG;
		msg_buf.msgtype = i++;

		msgsnd(qid, &msg_buf, sizeof(struct msg) - sizeof(long), 0);

		msgrcv(qid, &msg_buf, sizeof(struct msg) - sizeof(long), CLIENT_MSG, 0);
		printf("server rcv: %d: %s\n",msg_buf.msgtype,msg_buf.text);		
	}
	exit(0);
}

3.4 应用程序:消息队列 client.c

#define QUEUE_MSG_LEN	256
#define PROJ_ID		'g'
#define PATH_NAME	"/app"
#define SERVER_MSG	1
#define CLIENT_MSG	2
struct msg 
{
	long type;
	long msgtype;
	unsigned char text[QUEUE_MSG_LEN];
};

int main(void)
{
	int qid;
	int msglen;
	int i=0;
	struct msg msg_buf;

	key_t msgkey;
	msgkey = ftok(PATH_NAME, PROJ_ID));
	qid = msgget(msgkey, IPC_CREAT | 0666);
	while (1) 
	{
        msgrcv(qid, &msg_buf, sizeof(struct msg) - sizeof(long), SERVER_MSG, 0);
		printf("server rcv : %ld: %s\n",msg_buf.msgtype,msg_buf.text);	

		printf("client send: ");
		fgets(msg_buf.text, QUEUE_MSG_LEN, stdin);
		if (strncmp("exit", msg_buf.text, 4) == 0) 
		{
			break;
		}
		msg_buf.text[strlen(msg_buf.text) - 1] = '\0';
		msg_buf.type = CLIENT_MSG;
		msg_buf.msgtype = i++;

		msgsnd(qid, &msg_buf, sizeof(struct msg) - sizeof(long), 0);
	}
	exit(0);
}

4、信号量(信号灯)

4.1 信号量简介

System V 的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。

4.2 信号量相关API

4.2.1 semget:创建信号量

int semget(key_t key, int nsems, int semflg);
/*
功能:创建信号量
参数:
    @key:进程间通信键值,通过调用 ftok() 函数得到的键值
    @nsems:信号量的个数,一旦设置无法改变
    @semflg:IPC_CREAT:创建信号量。
             IPC_EXCL:检测信号量是否存在。
             位或权限位:信号量位或权限位后可以设置信号量的访问权限
返回值:成功返回信号量集标识符,失败返回-1
*/

4.2.2 semctl:控制信号量

int semctl(int semid, int semnum, int cmd, ...);
/*
功能:创建信号量
参数:
    @semid:信号量集标识符
    @semnum:要修改的信号灯编号
    @cmd:见下
    @...:当有4个参数时,第4个参数为联合体
            union semun {
               short val;             //SETVAL用的值
               struct semid_ds* buf;  //IPC_STAT、IPC_SET用的semid_ds结构
               unsigned short* array; //SETALL、GETALL用的数组值
               struct seminfo *buf;   //为控制IPC_INFO提供的缓存
            } arg;
返回值:成功返回0,失败返回-1

GETVAL:获取信号量的值。此时函数有3个参数。semctl() 函数的返回值即为信号量的值。
SETVAL:设置信号量的值。此时函数有4个参数。第4个参数为联合体中的val,其值为信号量的值。 
IPC_STAT:获取信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf。
IPC_SET:设置信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf。
IPC_RMID:删除信号量集。此时函数有3个参数,第2个参数semnum不起作用。
GETALL:获取所有信号量的值。此时函数有4个参数,第2个参数semnum不起作用。
		第4个参数为联合体中的array,其值为用来存放所有信号量值的数组的首地址。
SETALL:设置所有信号量的值 。参数说明同上。
IPC_INFO:获取信号量集合的限制信息。此时函数有4个参数,第2个参数semnum不起作用。
		 第4个参数为联合体中的__buf。
GETPID:获取信号的进程号,即最后操作信号量的进程。此时函数有3个参数。
		semctl() 函数的返回值即为信号的进程号。
GETNCNT:获取等待信号的值递增的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。
GETZCNT:获取等待信号的值递减的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。
*/

4.2.3 semop:完成对信号量的P操作或V操作

int semop(int semid, struct sembuf *sops, unsigned nsops)
/*
功能:完成对信号量的P操作或V操作
参数:
    @semid:信号量集标识符
    @sops:操作信号量的结构体(struct sembuf)数组的首地址        
    @nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。
            最常见设置此值等于1,只完成对一个信号量的操作
返回值:成功返回信号量集标识符,失败返回-1

struct sembuf{
    unsigned short  sem_num;  //信号量的序号
    short       sem_op;       //信号量的操作值
    short       sem_flg;      //信号量的操作标识
};
sem_num:信号量集中信号量的序号
sem_op :
    sem_op > 0:信号量的值在原来的基础上加上此值。
    sem_op < 0:如果信号量的值小于 semop 的绝对值,则挂起操作进程。
                如果信号量的值大于等于 semop 的绝对值,
                则信号量的值在原来的基础上减去 semop 的绝对值。
    sem_op = 0:阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;
                若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误
sem_flag: 
    IPC_NOWAIT:在对信号量的操作不能执行的情况下使函数立即返回。
    SEM_UNDO  :为SEM_UNDO 时,它将使操作系统跟踪当前进程对这个信号量的修改情况,
                如果这个进程在没有释放该信号量的情况下终止,
                操作系统将自动释放该进程持有的信号量。
*/

4.2.4 init_sem:封装信号量初始化

//此处初始化用的是 semctl,实际上初始化一般还是用 semget 函数
int init_sem(int semid, int num, int val)
{
	union semun myun;
	myun.val = val;
	if(semctl(semid, num, SETVAL, myun) < 0)
	{
		perror("semctl");
		exit(1);
	}
	return 0;
}

4.2.5 sem_p:封装p操作,即获取信号量,信号量-1

int sem_p(int semid, int num)
{
	struct sembuf mybuf;
	mybuf.sem_num = num;
	mybuf.sem_op = -1;
	mybuf.sem_flg = SEM_UNDO;
	if(semop(semid, &mybuf, 1) < 0)
	{
		perror("semop");
		exit(1);
	}
	return 0;
}

4.2.6 sem_v:封装v操作,即释放信号量,信号量+1

int sem_v(int semid, int num)
{
	struct sembuf mybuf;
	mybuf.sem_num = num;
	mybuf.sem_op = 1;
	mybuf.sem_flg = SEM_UNDO;
	if(semop(semid, &mybuf, 1) < 0)
	{
		perror("semop");
		exit(1);
	}
	return 0;
}

4.3 应用程序:信号量 server.c :在程序中轮流获取与释放信号量

int main(void)
{
	key_t key_info;
	int semid;

	if ((key_info = ftok ("/app", 'i')) < 0) {
		perror ("ftok info");
		exit (-1);
	}

	if ((semid = semget(key_info, 1, IPC_CREAT | IPC_EXCL | 0666)) < 0) {
		if (errno == EEXIST) {
			semid = semget(key_info, 1, 0666);
		} else {
			perror ("semget");
			exit (-1);
		}
	} else {
		init_sem(semid, 0, 1);
	}

	while (1) 
	{
		printf("p\n");
		sem_p(semid, 0);
		sleep(4);
		printf("v\n");
		sem_v(semid, 0);
		sleep(3);
	}
	exit(0);
}

4.4 应用程序:信号量 client.c :在程序中查询还有多少信号量

int main(void)
{
	key_t key;  
	int semid, semval;

	if((key = ftok("/app",'i')) <0) {
		perror("ftok");
		exit(1);
	}

	if((semid  = semget(key, 1, IPC_CREAT | 0666)) < 0) {
		perror("semget");
		exit(1);
	}

	while (1) 
	{
		if ((semval = semctl(semid, 0, GETVAL, 0)) == -1)  {
			perror("semctl error!\n");
			exit(1);
		}

		if (semval > 0)  {
			printf("Still %d resources can be used\n", semval);
		} else {
			printf("No more resources can be used!\n");
		}
		sleep(1);
	}
	exit(0);
}

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

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

相关文章

A40i使用笔记:安装python3.7(素装)

一、前言 项目需求&#xff0c;要用到python3以上&#xff0c;就研究了一下如何安装python&#xff0c;这里也是分享出来安装方法&#xff0c;为各位技术研发人员减少不必要的时间损耗 本文没有安装python其他依赖库&#xff0c;因为我也是在摸索中&#xff0c;所以只限指导到…

「程序员的浪漫」使用 Html、Css、JavaScript 实现 DIY 生日祝福页面 快发给你的朋友吧

前言 从网上搜集整理修改的好用网页生日祝福版本 特点 将三剑客放进一个 Html 文件 点开即用封装好 修改几个参数就可以 DIYDIY 的地方有注释 预览 …省略几张图 源码 有用的话点个 star 不迷路谢谢&#xff01;https://github.com/yangzi0210/Happy-birthday-page

领先的项目协作管理软件OpenProject

本文软件由网友 不长到一百四誓不改名 推荐&#xff1b; 什么是 OpenProject &#xff1f; OpenProject 是一个开源、基于 Web 的项目管理系统&#xff0c;提供了免费的社区版和收费的企业版。OpenProject 拥有完善的文档&#xff0c;API&#xff0c;及丰富的功能&#xff0c;可…

SlickEdit for Windows and Linux crack

SlickEdit for Windows and Linux crack 现在可以在“新建注释”对话框中对颜色进行排序&#xff0c;使调色板中的颜色阵列看起来更符合逻辑。 在拆分或扩展行注释时添加了撤消步骤&#xff0c;这样您只需点击“撤消”一次即可撤消行注释扩展。 已更新VHDL颜色编码&#xff0c;…

【网络】- 计算机网络体系结构 - OSI七层模型、TCP/IP四层(五层)协议

目录 一、概述 二、计算机网络体系结构的形成  &#x1f449;2.1 分层的网络体系结构  &#x1f449;2.2 OSI 参考模型  &#x1f449;2.3 TCP/IP - 事实的国际标准 三、OSI 参考模型 四、TCP/IP 协议 一、概述 但凡学习计算机网络知识&#xff0c;肯定绕不过网络协议的&…

Java 基础进阶篇(八)—— 常用 API

文章目录 一、Object 类二、Objects 工具类三、Math 类四、System 类五、BigDecimal 类 一、Object 类 一个类要么默认继承了 Object 类&#xff0c;要么间接继承了 Object 类&#xff0c;Object 类是 java 中的祖宗类。Object 类的方法是一切子类都可以直接使用的。 因此&…

7.4 电子信息系统预处理中所用放大电路

在电子信息系统中&#xff0c;通过传感器或其它途径所采集的信号往往很小&#xff0c;不能直接进行运算、滤波等处理&#xff0c;必须进行放大。 一、仪表放大器 集成仪表放大器&#xff0c;也称为精密放大器&#xff0c;用于弱信号放大。 1、仪表放大器的特点 在测量系统中…

Binder“一次拷贝“你真懂吗?

前言 谈到到Binder相对于其他传统进程间通信方式的优点的时候&#xff0c;我们总会说Binder只需要做“一次拷贝”就行了&#xff0c;而其他传统方式需要“两次拷贝”。这确实是Binder的优点&#xff0c;但再进一步思考就会碰到两个问题&#xff1a; 这所谓的“一次拷贝”到底…

回溯算法简单介绍

目录 1.回溯算法简单介绍 2.回溯算法框架&#xff1a; 我们用一道题来详细讲解回溯算法的过程 3.全排列问题 1.回溯算法简单介绍 解决一个回溯问题&#xff0c;其实就是一个决策树的遍历过程&#xff0c;我们只需要思考三个问题&#xff1a; 1.路径&#xff1a;就是已经做出…

一起Talk Android吧(第五百四十二回:无进度值ProgressBar)

文章目录 概念介绍使用资源文件实现使用默认设置修改风格使用动画资源 使用代码实现经验总结 各位看官们大家好&#xff0c;上一回中咱们说的例子是"ProgressBar总结",本章回中介绍的例子是" 无进度值ProgressBar"。闲话休提&#xff0c;言归正转&#xf…

linux相关操作

1 系统调用 通过strace直接看程序运行过程中的系统调用情况 其中每一行为一个systemcall &#xff0c;调用write系统调用将内容最终输出。 无论什么编程语言都必须通过系统调用向内核发起请求。 sar查看进程分别在用户模式和内核模式下的运行时间占比情况&#xff0c; ALL显…

MySQL的where表达式中的各种运算符的用法和细节

MySQL的where表达式中的各种运算符的用法和细节 小故事mysql运算符分类where表达式中的运算符between and 和 and运算符的区别in运算符需要注意的点 小故事 今天在研究mysql的where表达式中的运算符的时候&#xff0c;遇到一个有意思的问题。 问题是&#xff1a;以id为主键&am…

玩机搞机----修改手机各项参数工具解析 过验证 变新机

任何的芯片原则上都可以修改当前机型的任何参数。包括但不限于高通 MTk 展讯..... 等其他芯片。众所周知&#xff0c;有的机型 有的版本可以直接修改&#xff0c;有的不行。至于具体原因可以参考我以往的帖子 安卓玩机搞机技巧综合资源-----修复基带 改串码 基带qcn 改相关参数…

三、c++学习(指针引用详解)

上一次直播好像过去很久了&#xff0c;中间有加班&#xff0c;有5 1假期等&#xff0c;现在5 1放假完了&#xff0c;所以继续卷。 C学习&#xff0c;b站直播视频 3.1 指针 这个指针&#xff0c;考虑了很久&#xff0c;一直不知道在哪个地方介绍&#xff0c;为啥纠结&#xf…

Solr(2):Solr的安装

1 安装前的概述 &#xff08;1&#xff09;solr是基于lucene而lucene是java写的&#xff0c;所以solr需要jdk----当前安装的solr-7.5需要jdk-1.8及以上版本&#xff0c;下载安装jdk并设置JAVA_HOME即可。 &#xff08;2&#xff09;下载solr&#xff0c;然后解压即可&#xf…

Map对象的用法(JS)

&#x1f4dd;个人主页&#xff1a;爱吃炫迈 &#x1f48c;系列专栏&#xff1a;数据结构与算法 &#x1f9d1;‍&#x1f4bb;座右铭&#xff1a;道阻且长&#xff0c;行则将至&#x1f497; 文章目录 MapkeyMap常用语法Map其他语法创建map的其他方式 Map Map是一个键值对形式…

FPGA设计之控制集优化详解

目录 一、前言 二、Control Set控制集 三、Control Set控制集优化 四、优劣 一、前言 在工程设计优化中&#xff0c;综合阶段优化中有一项常见的优化&#xff0c;控制集&#xff08;control set&#xff09;优化&#xff0c;在vivado的Synthesis中有对该配置项的解释&#x…

Android Jetpack组件化之ORM 数据库访问框架详解

一、对象关系映射 - ORM / Object Relational Mapping Android 系统中使用的数据库是 SQLite 关系型数据库 , 使用 Android 提供的 api 访问 SQLite 数据库非常繁琐 , 由此出现了很多 ORM 框架 ; ORM 英文全称 Object Relational Mapping , 对象关系映射 ; 对象关系映射 ORM …

“智能指针:C++中优雅的内存管理解决方案“

前言 欢迎来到&#x1f496;小K&#x1f496;的&#x1f49e;C专栏&#x1f49e;&#xff0c;内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况&#xff0c;这是C和C程序员的噩梦之一。本节将为大家带来解决办法—>智能指针 文章目录 前言1、简介2、为什么…

【移动端网页布局】flex 弹性布局 ⑤ ( 设置侧轴单行子元素排列方式 | align-items 样式说明 | 代码示例 )

文章目录 一、设置子元素是否换行 : align-items 样式说明1、 align-items 样式引入2、 align-items 样式属性值 二、代码示例1、 代码示例 - 默认样式2、 代码示例 - 设置主轴水平居中3、 代码示例 - 设置侧轴垂直居中4、 代码示例 - 设置侧轴从下到上排列5、 代码示例 - 设置…