进程通信(三)SYSTEM V IPC

一、为什么要使用 IPC

       管道和信号都有一些不足之处,管道无法实现多对多,而信号必须获取进程 pid

IPC就类似于文件(以下说的文件都是 IPC,说是这么说,但IPC 不是文件),通过特

有的函数可以访问该文件,这样进程间的通信会更加灵活文件内容如果没有被删除则

一直存在,而且没有执行相关函数或者命令,文件不会被删除。但又不同于物理内存

上的文件,当设备掉电时,这些文件会被自动删除。

二、相关函数说明

2.1、信号量集合相关函数(这个比较麻烦,不过是祖宗)

1、ftok函数
函数功能:获取一个唯一的 key 值
函数原型: key_t ftok(const char *pathname, int proj_id)
函数名:ftok
参数:pathname------>路径名
      proj_id------>自定义整数
返回值:非 -1------->成功(只有当 pathname 和 proj_id 完全一致返回值就会一样)
          -1------->失败
注意 ftok 主要为产生信号量,共享内存,消息队列提供参数

2、semget函数
函数功能:参数信号量并获取信号量id
函数原型:int semget(key_t key, int nsems, int semflg)
函数名:semget
参数: key------->ftok函数获取或者自定义正整数
      nsems----->产生多少个信号量
      semflg---->O_CREAT|O_EXCL|mode 具体需要哪些请参考 open 函数
返回值:非 -1------->型号量 id (key 参数一样得到的 id 就一样)
          -1------->失败

3、semctl函数
函数功能:删除信号量, 设置信号量的值(这个可不是信号的id),获取当前信号量的属性
函数原型:int semctl(int semid, int semnum, int cmd, ...)
函数名:semctl
参数:semid------->信号量id
     semnum------>信号量标号(从 0 开始)
     cmd--------->记住三个宏
            IPC_STAT------>获取信号量属性信息放入一个结构体中(linux 下输入 man semctl 查询)
            SETVAL-------->设置信号量的值
            IPC_RMID------>删除信号量
返回值:非 -1----------->成功
          -1----------->失败

4、semop函数
函数功能:设置 PV 操作(不知道什么是 PV 操作请百度)
函数原型:int semop(int semid, struct sembuf *sops, size_t nsops)
函数名:semop
参数:semid------>型号量id
      sops------->PV操作相关设置
      nsops------>几个信号量(一般写 1 )
返回值:0------->成功
       -1------>失败
sopsx详解:
struct sembuf
{
    unsigned short sem_num;  //需要进行设置的信号量标号
    short          sem_op;   //大于0--->V操作     小于0---->P操作
    short          sem_flg;  // SEM_UNDO--->详解请看
         
https://blog.csdn.net/Quinn0918/article/details/72596828
                             // IPC_NOWAIT--->消息队列时使用,下面会说
};

注意:还有 无名 POSIX 信号量和有名 POSIX 信号量集

          有名信号量相关函数还有一对(一般用于进程间)------->(相互配合,成对出现)

          sem_open函数-------------------->创建或者打开一个信号量(这样会有名字,类比管道吧)

          sem_wait函数--------------------->对信号量进行 P 操作

         sem_post函数---------------------->对信号量进行 V 操作

(信号量的值被改变退出后信号量值不会被还原)那就再加一个函数 sem_getvalue 函数可以获取信号量的值

          无名信号量相关函数(一般用于线程间)

          sem_init函数------------->给信号量赋值。就这么简单,没了

一些不怎么重要的:还有 SYSTEM V 信号量集合(但是并不常用)

2.1.1、有名 POSIX 信号量事例

//sent.c
/**********发送端************/
/*******假装有头文件*********/

int main(void)
{
    // 弄一个共享内存
    key_t key = ftok(".", 111);
    int shmid = shmget(key, 1024, O_CREAT|0777);
    // 映射内存入口
    char *p = shmat(shmid, NULL, 0);

    // 创建或打开有名信号量
    sem_t *data_s  = sem_open("data", O_CREAT, 0777, 1);
    sem_t *space_s = sem_open("space", O_CREAT, 0777, 0);
    
    while (1)
    {
        // P操作
        sem_wait(data_s);
        fgets(p, 100, stdin);
        // V操作
        sem_post(space_s);
    }

    return 0;
}


// 接收端
/************头文件**********/


int main(void)
{
    // 弄一个共享内存
    key_t key = ftok(".", 111);
    int shmid = shmget(key, 1024, O_CREAT|0777);
    // 映射内存入口
    char *p = shmat(shmid, NULL, 0);

    // 创建或打开有名信号量
    sem_t *data_s  = sem_open("data", O_CREAT, 0777, 1);
    sem_t *space_s = sem_open("space", O_CREAT, 0777, 0);
    
    while (1)
    {
        // P操作
        sem_wait(space_s);
        printf("收到消息:%s\n", p);
        // V操作
        sem_post(data_s);
    }

    return 0;
}

2.1.1、无名 POSIX 信号量事例

/**************头文件不写**********/

// 无名信号量
sem_t data_s;
sem_t space_s;

// 线程操作函数
void *routine(void *arg)       //函数接口必须这么写
{
    cahr *buf = (char *)arg;
    while (1)
    {
        sem_wait(&space_s);
        fgets(buf, 100, stdin);
        sem_post(&data_s);
    }

}

int main(void)
{
     // 创建无名信号量并且初始化
     sem_init(&data_s, 0, 0);   // 第一个 0 表示线程间使用
     sem_init(&space_s, 0, 1);  // 1表示信号量被初始化的值

    char buf[100];
    bzero(&buf, sizeof (buf));

    while (1)
    {
        sem_wait(&data_s);
        printf("大小:%lu\n", strlen (buf));
        sem_post(&space_s);
    }   
    return 0
}

2.2、共享内存相关函数

1、ftok函数:已讲

2、shmget函数
函数功能:产生共享内存并返回共享内存 id
函数原型:int shmget(key_t key, size_t size, int shmflg)
参数:参考 semget 函数
     size----------->所需共享内存大小,不能为奇数
返回值:非 -1---------->成功
          -1---------->失败

3、shmat/shmdt
函数功能:获取共享内存入口地址/解除共享内存(可不是删除)
函数原型:   void *shmat(int shmid, const void *shmaddr, int shmflg)
            int shmdt(const void *shmaddr)(这个函数类似于 free 不说了)
参数:shmid------->shmget获得的共享内存id
     shmaddr----->共享内存入口地址,一般为 NULL 由系统自动获取
     shmflg------>权限设置,一般写 0--->可读可写,了解更多自行 man
返回值:(void *)-1   ------->失败   其他返回共享内存的入口地址

2.3、共享内存与信号量联合使用的事例

功能实现:通过共享内存进行数据通信,通过信号量进行通信控制(PV操作)

不足之处:函数没有封装,有点缺陷(功能函数都要封装,方便阅读,使代码具有模块性)

2.3.1、这次我把头文件写了

#ifndef _MYHEAD_H_
#define _MYHEAD_H_
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <strings.h>
#include <sys/wait.h>
#include <errno.h>

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


#endif

2.3.2、发送端

#include "myhead.h"

int semid;
int shmid;
char *p;

void func(int sig)
{
	// 解除共享内存 
	shmdt(p);
	// 删除共享内存 
	shmctl(shmid, IPC_RMID, NULL);
	// 删除信号量 
	semctl(semid, 0, IPC_RMID);
	exit(0);
}

int main(void)
{ 
    // 改变信号的响应方式 
	signal(SIGINT, func);
	
	// 为 pv 操作做准备 
	struct sembuf mysembuf1;
	bzero(&mysembuf1, sizeof (mysembuf1)); 
	mysembuf1.sem_num = 0;
	mysembuf1.sem_op = -1;
	mysembuf1.sem_flg=SEM_UNDO;
	
	struct sembuf mysembuf2;
	bzero(&mysembuf2, sizeof (mysembuf2)); 
	mysembuf2.sem_num = 1;
	mysembuf2.sem_op = 1;
	mysembuf2.sem_flg=SEM_UNDO;
	
	// 获取信号量 id 
	key_t keysem = ftok(".", 100);
	semid = semget(keysem, 1024, IPC_CREAT | IPC_EXCL | 0777);
	if (semid == -1)
	{
		if (errno != EEXIST)
		{
			perror("setget() filed");
			exit(0);
		}
	} 
	
	// 获取共享内存 id 
		key_t keyshm = ftok(".", 200);
		shmid = shmget(keyshm, 1024, IPC_CREAT | IPC_EXCL | 0777);
		if (shmid == -1)
		{
			if (errno != EEXIST)
			{
				perror("shmget() filed");
				exit(0);
			}
		}  

    // 获取共享内存入口地址 
	char *p= shmat(shmid, NULL, 0);
	
	// 设置信号量初始值 
	semctl(semid, 0, SETVAL, 1);
	semctl(semid, 1, SETVAL, 0);
	while(1)
	{ 
	    // 对标号为 0 的信号进行P操作 
		semop(semid, &mysembuf1, 1);
		printf("scanf sent msg : ");
		fflush(stdout);
		// 写入共享内存 
		scanf("%s",p);
		// 对标号为 1 的信号进行V操作 
		semop(semid, &mysembuf2, 1);
	}
	return 0;
}

2.3.3、接收端

#include "myhead.h"

int main(void)
{ 

	//为 pv 操作做准备 
	struct sembuf mysembuf1;
	bzero(&mysembuf1, sizeof (mysembuf1)); 
	mysembuf1.sem_num = 1;
	mysembuf1.sem_op = -1;
	mysembuf1.sem_flg=SEM_UNDO;
	
	struct sembuf mysembuf2;
	bzero(&mysembuf2, sizeof (mysembuf2)); 
	mysembuf2.sem_num = 0;
	mysembuf2.sem_op = 1;
	mysembuf2.sem_flg=SEM_UNDO;


	// 获取信号量 id 
	key_t keysem = ftok(".", 100);
	// 打开已有信号量 
	int semid = semget(keysem, 1024, 0777);
	if (semid == -1)
	{
		if (errno != EEXIST)
		{
			perror("setget() filed");
			exit(0);
		}
	} 
	 
        // 取共享内存 id 
		key_t keyshm = ftok(".", 200);
		int shmid = shmget(keyshm, 1024, 0777);
		if (shmid == -1)
		{
			if (errno != EEXIST)
			{
				perror("shmget() filed");
				exit(0);
			}
		}  
	

	// 获取共享内存入口地址 
	char *p = shmat(shmid, NULL, 0);
	
	int ret = 0;
	while(1)
	{
		// 对标号为 1 的信号量进行 P 操作 
		semop(semid, &mysembuf1, 1);
		printf("save msg : %s\n", p);
		// 检查共享内存是否被删除 
		ret = shmget(keyshm, 1024, 0777);
		if (ret == -1)
		{
			perror("shmdt() sucsses");
			exit(0);
		}
		// 对标号为 0 的信号量进行 V 操作 
		semop(semid, &mysembuf2, 1);	
	}
	return 0;
	
}

敲重点:关于权限的几种组合

                          O_CREAT | 0666                        (不存在则创建,存在则打开,存在不会报错)

                         O_CREAT | O_EXCL | 0666        (不存在则创建,存在则报错)

                         0666                                            (以可读可写打开)

                        O_EXCL | 0666                           (不合理的组合)

2.3.4、算了,还是封装吧

请链接:https://blog.csdn.net/qq_41985711/article/details/82454470

三、消息队列

3.1、相关函数说明

1、msgget函数
函数功能:获得消息队列 id
函数原型:int msgget(key_t key, int msgflg)
函数名:msgget
参数:不想说了,见太多了
返回值:也不说了

2、msgsnd函数
函数功能:发送消息
函数原型: int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg)
函数名:msgsnd
参数:nsqid------>msgget函数获得消息队列id
      msgp------>struct msgbuf
      msgsz----->msgp中除了第一个参数的大小
      msgflg---->0:标准模式
                 IPC_NOWAIT:不阻塞 
返回值:-1-------->失败
        0-------->成功
参数详解:struct msgbuf
struct msgbuf 
{
     long mtype;       //通信密码
     char mtext[1];    //通信内容
};

3、msgrcv
函数功能:接收消息
函数原型:ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
                                                              int msgflg);
函数名:msgrcv
参数:msqid------->msgget函数获得的消息队列id
     msgp-------->struct msgbuf 收到消息存放的地方
     msgst------->msgbuf 中除了密码外的大小
     msgtyp------>通信密码
     msgflg---->0:标准模式
                 IPC_NOWAIT:不阻塞 
返回值:-1-------->失败
        0-------->成功



3.2、事例代码

3.2.1、发送端

/***********写了头文件,你看不到*******/
//装通信密码和内容
struct msgbuf
{
    long password;
    char text[100];
};

int main(void)
{
    key_t key = ftok(".", 100);
    int msgid = msgget(key, IPC_CREAT | 0777);
 
    // 准备发送消息
    struct msgbuf msg;
    bzero(&msg, sizeof(msg));
    msg.password = 123456;
    fgets(msg.text, 100, stdin);
    
    // 发送消息
    msgsnd(msgid, &msg, sizeof (msg.text), 0);
    return 0;
}

3.2.2、接收端

/***********写了头文件,你看不到*******/
//装通信密码和内容
struct msgbuf
{
    long password;
    char text[100];
};

int main(void)
{
    key_t key = ftok(".", 100);
    int msgid = msgget(key, IPC_CREAT | 0777);
 
    // 准备接收消息
    struct msgbuf msg;
    bzero(&msg, sizeof(msg));

    // 阻塞等待
    msgrcv(msgid, &msg, sizeof (msg.text), 123456, 0);
    printf("收到:%s", msg.text);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41985711/article/details/82426058
今日推荐