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

时间:2022-11-17 15:18:45

一组服务器与客户端之间通信方式有很多,下面介绍一种共享内存+信号量+消息队列的形式来实现进程间的通信。

信号量:信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

特点:

信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
支持信号量组。

原型:

#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);

共享内存:共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

特点:

共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

因为多个进程可以同时操作,所以需要进行同步。

信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

原型:

#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr);
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

消息队列:消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
特点:
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

原型:

#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

综合示例:

server.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h> // shared memory
#include<sys/sem.h> // semaphore
#include<sys/msg.h> // message queue
#include<string.h> // memcpy

// 消息队列结构
struct msg_form {
long mtype;
char mtext;
};

// 联合体,用于semctl初始化
union semun
{
int val; /*for SETVAL*/
struct semid_ds *buf;
unsigned short *array;
};

// 初始化信号量
int init_sem(int sem_id, int value)
{
union semun tmp;
tmp.val = value;
if(semctl(sem_id, 0, SETVAL, tmp) == -1)
{
perror("Init Semaphore Error");
return -1;
}
return 0;
}

// P操作:
// 若信号量值为1,获取资源并将信号量值-1
// 若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = -1; /*P操作*/
sbuf.sem_flg = SEM_UNDO;

if(semop(sem_id, &sbuf, 1) == -1)
{
perror("P operation Error");
return -1;
}
return 0;
}

// V操作:
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = 1; /*V操作*/
sbuf.sem_flg = SEM_UNDO;

if(semop(sem_id, &sbuf, 1) == -1)
{
perror("V operation Error");
return -1;
}
return 0;
}

// 删除信号量集
int del_sem(int sem_id)
{
union semun tmp;
if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
{
perror("Delete Semaphore Error");
return -1;
}
return 0;
}

// 创建一个信号量集
int creat_sem(key_t key)
{
int sem_id;
if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
{
perror("semget error");
exit(-1);
}
init_sem(sem_id, 1); /*初值设为1资源未占用*/
return sem_id;
}


int main()
{
key_t key;
int shmid, semid, msqid;
char *shm;
char data[] = "this is server";
struct shmid_ds buf1; /*用于删除共享内存*/
struct msqid_ds buf2; /*用于删除消息队列*/
struct msg_form msg; /*消息队列用于通知对方更新了共享内存*/

// 获取key值
if((key = ftok(".", 'z')) < 0)
{
perror("ftok error");
exit(1);
}

// 创建共享内存
if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)
{
perror("Create Shared Memory Error");
exit(1);
}

// 连接共享内存
shm = (char*)shmat(shmid, 0, 0);
if((int)shm == -1)
{
perror("Attach Shared Memory Error");
exit(1);
}


// 创建消息队列
if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
{
perror("msgget error");
exit(1);
}

// 创建信号量
semid = creat_sem(key);

// 读数据
while(1)
{
msgrcv(msqid, &msg, 1, 888, 0); /*读取类型为888的消息*/
if(msg.mtext == 'q') /*quit - 跳出循环*/
break;
if(msg.mtext == 'r') /*read - 读共享内存*/
{
sem_p(semid);
printf("%s\n",shm);
sem_v(semid);
}
}

// 断开连接
shmdt(shm);

/*删除共享内存、消息队列、信号量*/
shmctl(shmid, IPC_RMID, &buf1);
msgctl(msqid, IPC_RMID, &buf2);
del_sem(semid);
return 0;
}
client.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h> // shared memory
#include<sys/sem.h> // semaphore
#include<sys/msg.h> // message queue
#include<string.h> // memcpy

// 消息队列结构
struct msg_form {
long mtype;
char mtext;
};

// 联合体,用于semctl初始化
union semun
{
int val; /*for SETVAL*/
struct semid_ds *buf;
unsigned short *array;
};

// P操作:
// 若信号量值为1,获取资源并将信号量值-1
// 若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = -1; /*P操作*/
sbuf.sem_flg = SEM_UNDO;

if(semop(sem_id, &sbuf, 1) == -1)
{
perror("P operation Error");
return -1;
}
return 0;
}

// V操作:
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
struct sembuf sbuf;
sbuf.sem_num = 0; /*序号*/
sbuf.sem_op = 1; /*V操作*/
sbuf.sem_flg = SEM_UNDO;

if(semop(sem_id, &sbuf, 1) == -1)
{
perror("V operation Error");
return -1;
}
return 0;
}


int main()
{
key_t key;
int shmid, semid, msqid;
char *shm;
struct msg_form msg;
int flag = 1; /*while循环条件*/

// 获取key值
if((key = ftok(".", 'z')) < 0)
{
perror("ftok error");
exit(1);
}

// 获取共享内存
if((shmid = shmget(key, 1024, 0)) == -1)
{
perror("shmget error");
exit(1);
}

// 连接共享内存
shm = (char*)shmat(shmid, 0, 0);
if((int)shm == -1)
{
perror("Attach Shared Memory Error");
exit(1);
}

// 创建消息队列
if ((msqid = msgget(key, 0)) == -1)
{
perror("msgget error");
exit(1);
}

// 获取信号量
if((semid = semget(key, 0, 0)) == -1)
{
perror("semget error");
exit(1);
}

// 写数据
printf("***************************************\n");
printf("* IPC *\n");
printf("* Input r to send data to server. *\n");
printf("* Input q to quit. *\n");
printf("***************************************\n");

while(flag)
{
char c;
printf("Please input command: ");
scanf("%c", &c);
switch(c)
{
case 'r':
printf("Data to send: ");
sem_p(semid); /*访问资源*/
scanf("%s", shm);
sem_v(semid); /*释放资源*/
/*清空标准输入缓冲区*/
while((c=getchar())!='\n' && c!=EOF);
msg.mtype = 888;
msg.mtext = 'r'; /*发送消息通知服务器读数据*/
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
break;
case 'q':
msg.mtype = 888;
msg.mtext = 'q';
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
flag = 0;
break;
default:
printf("Wrong input!\n");
/*清空标准输入缓冲区*/
while((c=getchar())!='\n' && c!=EOF);
}
}

// 断开连接
shmdt(shm);

return 0;
}