系统编程四:消息队列+共享内存+信号量

一、linuxIPC对象

1、什么是IPC对象?

在linux下,IPC对象指的是 消息队列、共享内存、信号量。如果用户需要使用IPC对象来进行进程之间的通信,首先必须为IPC对象申请对应的资源
比如,如果想要使用消息队列来通信,那么就必须先申请消息队列对应的key值 和 ID号。

想要操作文件:

需要获得的资源:

1、文件的路径名
2、文件的文件描述符

想要操作消息队列:

需要获得的资源:

1、需要获得key值------文件的路径名
2、ID号  -----文件的文件描述符

2、查看系统中所有的IPC对象?

(1)查看IPC对象: ipcs -a

(2)删除IPC对象

想删除消息队列: ipcrm  -q   消息队列的key值  /  ipcrm  -q   消息队列的ID值
想删除共享内存: ipcrm  -m   共享内存的key值  /  ipcrm  -m   共享内存的ID值
想删除信号量: ipcrm      -s   信号量的key值  /  ipcrm      -s   信号量的ID值

3、使用IPC对象之前,要申请key值,那么这个key值是怎么来的??? ---》ftok  --> man 3 ftok

NAME
       ftok  -  convert  a pathname and a project identifier to a System V IPC key
        给定一个 路径 和 项目标识符 可以转换成 一个 key值 
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>

       key_t ftok(const char *pathname, int proj_id);

函数作用: 获得一个key值
参数:
        pathname: 一个合法的路径。  常用 "."
        proj_id: 非0整数。   常用 10
        
返回值:
        成功 key值 
        失败 -1
        
 The resulting value is the same for all pathnames that  name  the  same file,  when  the  same  value  of  proj_id is used.  
 当文件路径pathname 和 proj_id 是一样的时候,两个ftok函数的返回值---key是一样的。
 
 The value returned should be different when the (simultaneously  existing)  files  or  the project IDs differ.
  只要文件路径pathname 或者 proj_id 有一个不一样,返回的key值 就是不一样的。
        

key_t key = ftok(".",10);
key_t key = ftok(".",10);
 

例子:验证key值。

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>

int main()
{
	key_t key;
	
	//获取key值
	key = ftok(".", 10);
	printf("key:%x\n",key);
	
	//获取key值
	key = ftok("..", 10);
	printf("key:%x\n",key);
	
	
	//获取key值
	key = ftok(".", 20);
	printf("key:%x\n",key);
	
	
	//获取key值
	key = ftok(".", 10);
	printf("key:%x\n",key);
	
	return 0;
}

结论:如果想要使用IPC对象 实现两个进程 之间的通信,那么两个进程的IPC对象的key值 必须 是一样的。

进程1: key_t key = ftok(".",10);
进程1: key_t key = ftok(".",10);

二、进程之间通信方式之一----消息队列 

1、消息队列 是 属于 IPC对象,所以使用之前一定要先申请 key 值。
2、管道通信  跟 消息队列 非常相似,它们之间的区别???

管道通信: 不能读取指定的数据,只要管道中有数据,就一定要读取出来,操作的时候使用 
            open /  write  read 
消息队列:消息队列是一种带有数据标识的特殊管道,消息队列可以读取指定的数据,如果里面有多个数据,但是不符合我的类型,我可以不读取,操作时候使用消息队列中独有的函数接口。

3、消息队列机制:

进程1往消息队列中写入数据时,  “类型”+ “数据正文”          //类型 ---数据的编号    
进程2从消息队列中读取数据时,只需要提供 数据的编号就可以 读取到指定的数据了。

4、消息队列作用的范围: linux下任意两个进程。

三、使用消息队列来通信的步骤

1、申请消息队列的key值 
    key_t key = ftok(".",10);

2、根据消息队列的key值 获取 ID号 ,如果该key值对应的消息不存在,会创建 -->man 2 msgget


NAME
       msgget - get a message queue identifier
        得到一个消息队列的ID号
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/msg.h>

       int msgget(key_t key, int msgflg); // 类似 之前的open("./1.txt",O_CREAT,0666)
参数: 
        key:消息队列的key值
        msgflg: IPC_CREAT|0666  如果不存在则创建。并且给权限 

返回值:
        成功返回 消息队列的ID号 
        失败返回 -1

例子:创建一条消息队列,并且把key值 和 id打印出来

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
 #include <string.h>


//写入结构体 是由 用户 自己定义的 ,也就是该结构体的数据类型是我们自己设计
struct msgbuf {
	long mtype;       /* 消息类型/数据的编号  而且是>0 */
	char mtext[1024];    /* 数据的正文 */
};

int main()
{
	key_t key;
	
	//1、获取消息队列的key值
	key = ftok(".", 10);
	printf("key:%x\n",key);
	
	//2、根据消息队列的key值 获取 ID号 ,如果该key值对应的消息不存在,会创建
	int msgid = msgget(key,IPC_CREAT|0666);
	printf("msgid:%x\n",msgid);
	
	//3、往消息队列中写入数据 /发送数据
	
	//初始化数据
	struct msgbuf data;//定义一个写入结构体

	scanf("%s",data.mtext);
	data.mtype = 100;//数据的编号	
	
	msgsnd(msgid,&data, strlen(data.mtext),0);
	

	return 0;
}

3、发送 /写入数据

#include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>

    int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

函数作用: 往消息队列中写入数据

参数: 
    msqid:消息队列的ID号 
    msgp:你要写入的数据,注意 传递的是 写入的数据结构体的地址
    msgsz:数据正文的大小,也就是 char mtext[1024] 的大小,注意不是整个结构体的大小
    msgflg:一般属性,默认为0 
    
返回值: 
        成功返回 0
        失败返回 -1
    
电子相册: 

struct node{
    char bmpName[256]; //数据的正文
    int index;//数据的编号
}    

消息队列:    
10 + "数据"

//写入结构体 是由 用户 自己定义的 ,也就是该结构体的数据类型是我们自己设计
struct msgbuf {
    long mtype;       /* 消息类型/数据的编号  而且是>0 */
    char mtext[1024];    /* 数据的正文 */
};

4、从消息队列中 读取数据(接收数据) ---》man 2 msgrcv

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>


ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

参数: 
    msqid:消息队列的ID号 
    msgp:读取的数据存储到这里,注意 传递的是 读取的数据结构体的地址
    msgsz:数据正文的大小,也就是 char mtext[1024] 的大小,注意不是整个结构体的大小
    msgtyp:读取的数据的类型 或者说 数据的编号 
    msgflg:一般属性,默认为0 


返回值: 
    成功返回  读取的字节数 
    失败 返回 -1

5、删除消息队列

#include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/msg.h>

    int msgctl(int msqid, int cmd, struct msqid_ds *buf); //cmd --->command

参数: 
        msqid:你要删除哪条消息队列,将消息队列的ID号传递过来 
        cmd: 操作的命令 
                IPC_STAT -->获取消息队列的状态  ---》最后一个参数要填
                IPC_RMID---》删除消息队列 ---》删除不需要第三个参数,但是要填NULL
        buf: 获取的那些数据 存储到这个结构体里面
        
返回值: 
        成功返回 0
        失败返回 -1

比如删除消息队列:
        msgctl(msgid,IPC_RMID,NULL);

6、练习1:进程1  可以不断地发送数据给 进程2 ,进程2接收到数据 然后打印出来

#include<stdio.h>

#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>




#define PROJ_PATH "."  //用于产生一个键值
//#define PROJ_ID 10    //数据的编号



//写一个结构体 是由 用户自己定义的 , 也就是该结构体的数据类型是我们自己设计
struct msgbuf{
	long mtype; //消息类型的编号 >0
	char mtext[1024]; //数据的正文
};

int main()
{
	//1、获取消息队列的key值
	key_t key = ftok(PROJ_PATH,10);
	
	//2、根据消息队列的key值 获取 ID号 , 如果该key值对应的消息不存在,则创建
	int msgid = msgget(key,IPC_CREAT|0777);
	
	pid_t id = fork();//创建一个子进程  id1为子进程id
	
	if(id>0)//父进程
	{
		struct msgbuf data1;//定义一个写入结构体
		//往消息队列中写入数据/发送数据
		//初始化数据
		while(1)
		{
			scanf("%s",data1.mtext);
			data1.mtype = 10;//数据的编号
			//发送数据
			msgsnd(msgid,&data1,strlen(data1.mtext),0);
			
			if(!strcmp(data1.mtext,"byebye"))
				break;
		}
		kill(id,SIGINT);//杀死子进程
	}
		
	else if(id == 0)//子进程
	{
		struct msgbuf data2; //定义一个写入结构体
		//从消息队列中 读取(接受数据)
		while(1)
		{
			
			bzero(&data2,sizeof(struct msgbuf));
			
			msgrcv(msgid,&data2,sizeof(data2.mtext),100,0);
			
			printf("\t\trecv:%s\n",data2.mtext);
			
			//退出条件
			
			if(!strcmp(data2.mtext,"byebye"))
				break;
		}
		kill(getppid(),SIGINT);  //杀死父进程
	}

	//,退出之后,删除消息队列
	//删除消息队列 
	msgctl(msgid,IPC_RMID,NULL);
	
	return 0;		
}
#include<stdio.h>

#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>


#define PROJ_PATH "."  //用于产生一个键值
//#define PROJ_ID 10    //数据的编号



//写一个结构体 是由 用户自己定义的 , 也就是该结构体的数据类型是我们自己设计
struct msgbuf{
	long mtype; //消息类型的编号 >0
	char mtext[1024]; //数据的正文
};

int main()
{
	//1、获取消息队列的key值
	key_t key = ftok(PROJ_PATH,10);

	printf("key:%x\n",key);

	//2、根据消息队列的key值 获取 ID号 , 如果该key值对应的消息不存在,则创建
	int msgid = msgget(key,IPC_CREAT|0777);

	sleep(3);
	pid_t id = fork();//创建一个子进程  id2为子进程id
	
	if(id>0)//父进程
	{
		struct msgbuf data1; //定义一个写入结构体
		//从消息队列中 读取(接受数据)
		while(1)
		{
			
			bzero(&data1,sizeof(struct msgbuf));
			
			msgrcv(msgid,&data1,sizeof(data1.mtext),10,0);
			
			printf("\t\trecv:%s\n",data1.mtext);
			if(!strcmp(data1.mtext,"byebye"))
				break;
		}
		kill(id,SIGINT);//杀死子进程
	}
		
	else if(id == 0)//子进程
	{
		struct msgbuf data2;//定义一个写入结构体
		//3、往消息队列中写入数据/发送数据
		while(1)
		{
			
			
			scanf("%s",data2.mtext);
			data2.mtype = 100;//数据的编号
			//发送数据
			msgsnd(msgid,&data2,strlen(data2.mtext),0);
			
			//退出条件
			
			if(!strcmp(data2.mtext,"byebye"))
				break;
		}
		kill(getppid(),SIGINT);  //杀死父进程
	}

	//,退出之后,删除消息队列
	//删除消息队列 
	msgctl(msgid,IPC_RMID,NULL);
	
	
	return 0;		
}

四、进程之间的通信方式之一-----共享内存

1、共享内存

        共享内存 也是属于IPC对象,所以使用之前必须先申请key值。

2、共享内存作用范围以及机制 是如何的?

作用范围:在linux下任意两个进程。
机制: 任意两个进程通过申请key值 和 ID号 ,共享内存得到一片内存空间,那么这两个进程就可以将数据
写入到共享内存 / 从共享内存中读取数据。

3、使用共享内存实现两个进程的通信 步骤

1)先获取 key值。
    key_t key = ftok(".",10);

2)根据key值 获取 共享内存的ID号,如果说key值对应的共享内存不存在,则创建
NAME
       shmget - allocates a shared memory segment

SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmget(key_t key, size_t size, int shmflg);

参数: 
        key:共享内存的key值 
        size:共享内存的大小(总字节数),要求共享内存的大小必须是1024的整数倍 
        shmflg:IPC_CREAT|0666 --如果不存在则创建,权限是0666
返回值: 
        成功返回 共享内存的ID号
        失败返回 -1

3)根据ID号将共享内存 映射 到 本进程虚拟内存空间的某个区域 

    #include <sys/types.h>
    #include <sys/shm.h>

    void *shmat(int shmid, const void *shmaddr, int shmflg);
    
参数: 
        shmid:共享内存的ID号
        shmaddr:你要将共享内存 映射到 进程的虚拟内存空间哪一块区域,将那一块区域的起始地址传递过来
                    如果为NULL,系统会自动分配给你 
        shmflg:普通属性,为0 
返回值: 
        成功返回 映射成功的虚拟内存空间的起始地址 
        失败返回 (void*)-1
        
4)最后不用的时候,解除映射 
        int shmdt(const void *shmaddr);

参数:
    shmaddr: 你要解除映射内存空间的起始地址 
    
    
5)当没有进程再需要使用这一块共享内存时,删除释放它 ----》shmctl   man 2 shmctl     
    
    #include <sys/ipc.h>
    #include <sys/shm.h>

    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    
参数: 
        shmid:共享内存的ID号
        cmd: 操作的命令 
                IPC_STAT -->获取共享内存的状态  ---》最后一个参数要填
                IPC_RMID---》删除共享内存 ---》删除不需要第三个参数,但是要填NULL
        buf: 获取的那些数据 存储到这个结构体里面
            
返回值: 
        
        成功返回 0
        失败返回 -1

例子:通过共享内存 实现两个 进程的通信

写端:

#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int main()
{
	//1)先获取 key值。
	key_t key = ftok(".",10);

	//2)根据key值 获取 共享内存的ID号,如果说key值对应的共享内存不存在,则创建
    int shmid = shmget(key,1024,IPC_CREAT|0666);


	//3)根据ID号将共享内存 映射 到 本进程虚拟内存空间的某个区域 
	char* shm_p = shmat(shmid, NULL, 0);
	if(shm_p == (void*)-1)
	{
		perror("shmat error");
		return -1;
	}
	
	//信号量 
	//4)先申请key值 
	key_t key1 = ftok(".",20);
	//5)根据key值获取信号量ID号。	 ---》semget
	int semid = semget(key1,2,IPC_CREAT|0666);
	//6)初始化信号量起始值
	//想要设置 空间的起始值为 1(有一个车位)
	semctl(semid, 0, SETVAL, 1);
	//想要设置 数据的起始值为 0(没有车)
	semctl(semid, 1, SETVAL, 0);	

	
/* 	struct sembuf{
	unsigned short sem_num;   需要操作的成员的下标
	short          sem_op;    P操作 / V操作   p: -1  v: 1
	short          sem_flg;  普通属性,为0
}*/
	
	struct sembuf space;
	space.sem_num = 0;//空间下标 0
	space.sem_op = -1;//P操作
	space.sem_flg = 0;
	
	struct sembuf data;
	data.sem_num = 1;//数据下标 1
	data.sem_op = 1;//V操作
	data.sem_flg = 0;
	
	//shm_p 就是共享内存的地址
	//数据交互
	while(1)
	{
		//开车进去之前,询问空间能不能进行P操作?? 空间 -1
		semop(semid, &space, 1);//自动减1
		
		//能 ---有车位 ---函数返回
		//不能 ---没有车位 ---函数阻塞
		
		//开车进去
		//直接从键盘上获取数据 保存 到共享内存上
		scanf("%s",shm_p);
		
		//开车进去之后 ,数据+1 ---V操作 
		semop(semid, &data, 1);//自动 数据+1
	}
	
	//4)最后不用的时候,解除映射 
	shmdt(shm_p);

	return 0;
}

读端:

#include<stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int main()
{
	//1)先获取 key值。
	key_t key = ftok(".",10);

	//2)根据key值 获取 共享内存的ID号,如果说key值对应的共享内存不存在,则创建
    int shmid = shmget(key,1024,IPC_CREAT|0666);


	//3)根据ID号将共享内存 映射 到 本进程虚拟内存空间的某个区域 
	char* shm_p = shmat(shmid, NULL, 0);
	if(shm_p == (void*)-1)
	{
		perror("shmat error");
		return -1;
	}
	//信号量 
	//4)先申请key值 
	key_t key1 = ftok(".",20);
	//5)根据key值获取信号量ID号。	 ---》semget
	int semid = semget(key1,2,IPC_CREAT|0666);
	//6)初始化信号量起始值
	//想要设置 空间的起始值为 1(有一个车位)
	semctl(semid, 0, SETVAL, 1);
	//想要设置 数据的起始值为 0(没有车)
	semctl(semid, 1, SETVAL, 0);	

	
/* 	struct sembuf{
	unsigned short sem_num;   需要操作的成员的下标
	short          sem_op;    P操作 / V操作   p: -1  v: 1
	short          sem_flg;  普通属性,为0
}*/
	
	struct sembuf space;
	space.sem_num = 0;//空间下标 0
	space.sem_op = 1;//V操作
	space.sem_flg = 0;
	
	struct sembuf data;
	data.sem_num = 1;//数据下标 1
	data.sem_op = -1;//p操作
	data.sem_flg = 0;
	
	//shm_p 就是共享内存的地址
	//数据交互
	while(1)
	{
		//把车开出来之前,先询问数据能不能进行p操作  -1
		semop(semid, &data, 1);//自动减1
		
		//能 ---表示当前有车---函数返回
		//不能 ---没有车---》函数阻塞
		
		//把车开出来
		//直接从共享内存中 读取数据
		printf("recv:%s\n",shm_p);
		
		//把车开出来之后,有车位了 空间 + 1
		semop(semid, &space, 1);
	}
	
	//4)最后不用的时候,解除映射 
	shmdt(shm_p);
	//5)当没有进程再需要使用这一块共享内存时,删除释放它 ----》shmctl   man 2 shmctl 	
	shmctl(shmid,IPC_RMID, NULL);

	return 0;
}

现象:上面出现了数据践踏,我们写了一次数据进去,打印的时候出现了无限打印。
我们想要实现的效果是:来一个数据,我们就打印一次就可以了

五、信号量

更像一个信号灯,用于协调两个进程的共享内存数据资源。

1、什么是信号量?

信号量也是属于 IPC对象,所以使用信号量之前必须先申请key值。    
信号量 不是用于 进程之间的通信,主要用来协调两个进程之间的资源分配。

2、信号量的函数接口

1)先申请key值 
    key_t key = ftok(".",20);
    
2)根据key值获取信号量ID号。     ---》semget
    
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>

    int semget(key_t key, int nsems, int semflg);

参数:     
    key:信号量的key值 
    nsems:信号量的元素个数。 比如 空间 + 数据  ----》2
    semflg: IPC_CREAT|0666 --如果不存在则创建,权限是0666
            
返回值: 
        成功 返回 信号量 ID号 
        失败 返回 -1
        
3)控制信号量参数   semctl
            
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>

    int semctl(int semid, int semnum, int cmd, ...);
        
参数: 
        semid:信号量 ID号 
        semnum:需要操作的成员的下标   空间 代表 0   数据 代表  1
        cmd:操作命令 
                IPC_RMID --》删除信号量的命令 
                SETVAL -->用于设置 信号量的初始值 的命令
        ....:空间/数据的起始值 

比如:
想要设置 空间的起始值为 1(有一个车位)
semctl(semid, 0, SETVAL, 1);
想要设置 数据的起始值为 0(没有车)
semctl(semid, 1, SETVAL, 0);

4)如何实现信号量的P/V操作  --- semop

信号量的P/V操作
 -----P操作  1->0  减法 说白了就是减1
 -----V操作  0->1  加法 说白了就是加1

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>

    int semop(int semid, struct sembuf *sops, unsigned nsops);

参数: 
    semid:信号量ID号
    sops:进行 P / V 操作结构体 
    
struct sembuf{
    unsigned short sem_num;  /* 需要操作的成员的下标 */
    short          sem_op;   /* P操作 / V操作 */  p: -1  v: 1
    short          sem_flg;  /* 普通属性,为0*/
}

    nsops:信号量操作结构体的个数----》1


返回值: 
        成功返回 0
        失败返回 -1

例子1: 将信号量 加入到 共享内存的代码里面 ,协调两个进程的共享内存数据资源。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值