进程间通信(IPC)

        IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。

       消息队列、信号灯、共享内存常用在Linux服务端编程的进程间通信环境中。而此三类编程函数在实际项目中都是用System V IPC函数实现的。System V IPC函数名称和说明如下表1-1所示。

                                                                         表1-1 System V IPC函数

 

消息队列

信号灯

共享内存区

头文件

<sys/msg.h>

<sys/sem.h>

<sys/shm.h>

创建或打开IPC函数

msgget

semget

shmget

控制IPC操作的函数

msgctl

semctl

shmctl

IPC操作函数

msgsnd

msgrcv

semop

shmat

shmdt

一、消息队列

     消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

1、特点

  1. 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

  2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

  3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

2、原型

 

#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);

 

在以下两种情况下,msgget将创建一个新的消息队列:

  • 如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。
  • key参数为IPC_PRIVATE

函数msgrcv在读取消息队列时,type参数有下面几种情况:

  • type == 0,返回队列中的第一个消息;
  • type > 0,返回队列中消息类型为 type 的第一个消息;
  • type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。

可以看出,type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。

二、信号量

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

1、特点

  1. 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。

  2. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

  3. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

  4. 支持信号量组。

2、原型

       最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。

      Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

 

 

#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, ...);

       当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。

三、共享内存

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

1、特点

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

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

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

2、原型

 


 #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);

        当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。

 

       当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。

  shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。

  shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。

3、例子

下面这个例子,使用了【共享内存+信号量+消息队列】的组合来实现服务器进程与客户进程间的通信。

  • 共享内存用来传递数据;
  • 信号量用来同步;
  • 消息队列用来 在客户端修改了共享内存后 通知服务器读取。

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;
 }

注意:当scanf()输入字符或字符串时,缓冲区中遗留下了\n,所以每次输入操作后都需要清空标准输入的缓冲区。但是由于 gcc 编译器不支持fflush(stdin)(它只是标准C的扩展),所以我们使用了替代方案:

1 while((c=getchar())!='\n' && c!=EOF);
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值