一、IPC对象

除了最原始的进程间通信方式信号、无名管道和有名管道外,还有三种进程间通信方式,这三种方式称之为IPC对象

IPC对象分类:消息队列、共享内存、信号灯集

IPC对象也是在内核空间开辟区域,每一种IPC对象创建好之后都会将其设置为全局,并且会给其分配一个编号,只要找到唯一的这个编号就可以进行通信,所以不相关的进程可以通过IPC对象通信。IPC对象创建好之后,会在当前系统中可见,只要不删除或者不关闭系统,就会一直存在。

查看已经创建的IPC对象∶

ipcs 查看当前系统中所有创建的IPC对象

如图所示:




聊天使用消息队列 vx消息队列_c语言


ipcs -q 查看创建的消息队列


聊天使用消息队列 vx消息队列_c语言_02


ipcs -m查看创建的共享内存


聊天使用消息队列 vx消息队列_iot_03


ipcs -s查看信号量


聊天使用消息队列 vx消息队列_c语言_04


ipcrm删除IPC对象
例如: ipcrm -q msqid 删除标号为msqid的消息队列

二、消息队列概述

消息队列是消息的链表,存放在内存中,由内核维护

消息队列的特点

1、消息队列中的消息是有类型的。
2、消息队列中的消息是有格式的。
3、消息队列可以实现消息的随机查询。消息不一定要以先进先出的次序读取,编程时可以按消息的类型读取。
4、消息队列允许一个或多个进程向它写入或者读取消息。
5、与无名管道、命名管道一样,从消息队列中读出消息,消息队列中对应的数据都会被删除。
6、每个消息队列都有消息队列标识符,消息队列的标识符在整个系统中是唯一的。
7、只有内核重启或人工删除消息队列时,该消息队列才会被删除。若不人工删除消息队列,消息队列会一直存在于系统中。

在ubuntu 12.04中消息队列限制值如下:

每个消息内容最多为8K字节
每个消息队列容量最多为16K字节
系统中消息队列个数最多为1609个
系统中消息个数最多为16384个

System V提供的IPC通信机制需要一个key值,通过key值就可在系统内获得一个唯一的消息队列标识符。

key值可以是人为指定的,也可以通过ftok函数获得。

如果多个进程想通过IPC对象通信,则必须找到唯一的标识,而唯一的标识是由key决定的,所以只要key知道,则就可以实现多个进程通信

2.1 ftok函数

#include <sys/types.h>
#include <sys /ipc.h>
key_t ftok( const char *pathname, int proj_id);
功能:通过文件名和目标值共同创造一个键值并返回值
参数:
    pathname:任意一个文件名(文件名或者目录名)
    proj_id:目标值,范围一般是0~127
返回值:
    成功:键值
    失败:-1

如果使用ftok函数获取键值,得到的键值是由ftok的第一个参数对应文件的信息

和第二个参数一起决定的

2.1.1 案例:使用ftok函数获取键值
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
int main(int argc, char const *argv[])
{

//只要保证ftok的第一个参数对应的文件和第二个参数值相同,则不管程序运行多少遍,
//或者多少个进程获取键值,键值一定都是唯一的
    key_t mykey;
if((mykey = ftok(".",100)) == -1){   //"."表示当前目录,随便取值100,在0~127即可
    perror( "fail to ftok");
    exit(1);
}
    printf( "key =%#x\n", mykey); //以十六进制打印出来
    return 0;
}

运行结果:


聊天使用消息队列 vx消息队列_iot_05


2.2 创建消息队列-- msgget

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:
    创建一个消息队列,得到消息队列的id
参数:
     key:键值,唯一的键值确定唯一的消息队列
     方法1:任意指定一个数
    方法2:使用ftok函数获取键值
    
    msgflg:消息队列的访问权限,
        一般设置为IPC_CREAT | IPC_ExCL | 0777或者IPC_CREAT |0777
返回值:
        成功:消息队列的id    失败:-1
2.2.1 测试:通过msgget函数创建一个消息队列
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main(int argc,char const *argv[])
{
    //通过ftok函数获取ipc键值
    key_t mykey;
    if((mykey = ftok(".",100)) == -1)
    {
        perror( "fail to ftok" );
        exit(1);
    }
    //通过msgget函数创建一个消息队列
    int msgid;
    if(msgget(mykey,IPC_CREAT | 8666) == -1)
    {
        perror("fail to msgget");
        exit(1);
    }
return 0;
}

运行结果:执行后什么也出现,说明创建成功


聊天使用消息队列 vx消息队列_c语言_06


使用ipcs -q查看


聊天使用消息队列 vx消息队列_单片机_07


打印出key值

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main(int argc,char const *argv[])
{
    //通过ftok函数获取ipc键值
    key_t mykey;
    if((mykey = ftok(".",100)) == -1)
    {
        perror( "fail to ftok" );
        exit(1);
    }
    printf( "mykey = %#x\n",mykey);

    //通过msgget函数创建一个消息队列
    int msgid;
    if((msgid=msgget(mykey,IPC_CREAT | 8666)) == -1)
    {
        perror("fail to msgget");
        exit(1);
    }
    printf( "msqid = %d\n",msgid);
    system("ipcs -q");
return 0;
}

如图:


聊天使用消息队列 vx消息队列_聊天使用消息队列_08


2.3 发送消息--msgsnd

#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:要写入的数据,需要自己定义结构体
    struct struct_name{
            long mtype;  //消息的编号,必须大于0
            char mtext[128]; //消息正文,可以定义多个成员
            ...
          }
    msgsz:消息正文的大小,不包括消息的编号长度
    msgflg:标志位
      0    阻塞
      IPC_NOWAIT   非阻塞
返回值:
    成功:0失败:-1
2.3.1 测试:使用msgsnd函数向消息队列中发送数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define N 128
typedef struct{
    long msg_type;  //消息类型,必须在结构体的第一个位置并且类型必须是long
    char msg_text[N];       //消息正文,也可以有多个成员并且类型也可以是任意
}MSG;

#define MSGTEXT_SIZE (sizeof(MSG) - sizeof(long))

int main(int argc,char const *argv[])
{
    //通过ftok函数获取ipc键值
    key_t mykey;
    if((mykey = ftok(".",100)) == -1)
    {
        perror( "fail to ftok" );
        exit(1);
    }
    printf( "mykey = %#x\n",mykey);

    //通过msgget函数创建一个消息队列
    int msgid;
    if((msgid=msgget(mykey,IPC_CREAT | 0777)) == -1)
    {
        perror("fail to msgget");
        exit(1);
    }
    printf( "msqid = %d\n",msgid);
    system("ipcs -q");
//使用msgsnd函数向消息队列中发送数据(写操作)
MSG msg1 = {1,"hello world"};
MSG msg2 = {2,"nihao beijing"};
MSG msg3 = {3,"hello kitty"};
MSG msg4 = {4,"welcome to 100ephone"};
if(msgsnd(msgid, &msg1,MSGTEXT_SIZE,0)== -1)
{
    perror( "fail to msgsnd");
    exit(1);
}
if(msgsnd(msgid, &msg2,MSGTEXT_SIZE,0)== -1)
{
    perror( "fail to msgsnd");
    exit(1);
}
if(msgsnd(msgid, &msg3,MSGTEXT_SIZE,0)== -1)
{
    perror( "fail to msgsnd");
    exit(1);
}
if(msgsnd(msgid, &msg4,MSGTEXT_SIZE,0)== -1)
{
    perror( "fail to msgsnd");
    exit(1);
}
    system("ipcs -q");
return 0;
}

运行结果:


聊天使用消息队列 vx消息队列_物联网_09


当再次执行程序发送数据时,数据并不会消失


聊天使用消息队列 vx消息队列_物联网_10


2.4 接收消息队列-- 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:保存接收到的数据的结构体
    struct struct_name{
        long mtype;  	//消息的编号,必须大于0
        char mtext[128];	//消息正文,可以定义多个成员
    }
    msgsz:消息正文的大小
    msgtvp:设置要接收哪个消息
    0	按照写入消息队列的顺序依次读取
    >0	只读取消息队列中消息编号为当前参数的第一个消息
    <0	只读取消息队列中小于等于当前参数的绝对中内最小的第一个消息
    msgflg:标志位
    0	阻塞
    IPC_NOWAIT	非阻塞
返回值:
    成功:接收到的消息正文的长度	失败:-1
2.4.1 测试1:如果第四个参数为,则按照先进先出的方式读取数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define N 128
typedef struct{
    long msg_type;  //消息类型,必须在结构体的第一个位置并且类型必须是long
    char msg_text[N];       //消息正文,也可以有多个成员并且类型也可以是任意
}MSG;

#define MSGTEXT_SIZE (sizeof(MSG) - sizeof(long))

int main(int argc,char const *argv[])
{
    //通过ftok函数获取ipc键值
    key_t mykey;
    if((mykey = ftok(".",100)) == -1)
    {
        perror( "fail to ftok" );
        exit(1);
    }
    printf( "mykey = %#x\n",mykey);

    //通过msgget函数创建一个消息队列
    int msgid;
    if((msgid=msgget(mykey,IPC_CREAT | 0777)) == -1)
    {
        perror("fail to msgget");
        exit(1);
    }
    printf( "msqid = %d\n",msgid);
    system("ipcs -q");
MSG msg;
if(msgrcv(msgid,&msg,MSGTEXT_SIZE,0,0)==-1)
{
        perror("fail to msgrcv");
        exit(1);
}
printf("recv_msg = %s\n",msg.msg_text);
system( "ipcs -q");
return 0;
}

测试结果: 因为我们在创建打开消息队列的时侯,用的是同一个键值,所以在接收数据前我们已经在前面发送了四次数据,当我们接收数据的时候,在同一键值内便可以获取到刚才所发送的数据


聊天使用消息队列 vx消息队列_物联网_11


聊天使用消息队列 vx消息队列_聊天使用消息队列_12


注意:必须是同一键值内


聊天使用消息队列 vx消息队列_物联网_13


2.4.2 测试2:如果第四个参数为>0,则获取当前值得消息类型的数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define N 128
typedef struct{
    long msg_type;  //消息类型,必须在结构体的第一个位置并且类型必须是long
    char msg_text[N];       //消息正文,也可以有多个成员并且类型也可以是任意
}MSG;

#define MSGTEXT_SIZE (sizeof(MSG) - sizeof(long))

int main(int argc,char const *argv[])
{
    //通过ftok函数获取ipc键值
    key_t mykey;
    if((mykey = ftok(".",100)) == -1)
    {
        perror( "fail to ftok" );
        exit(1);
    }
    printf( "mykey = %#x\n",mykey);

    //通过msgget函数创建一个消息队列
    int msgid;
    if((msgid=msgget(mykey,IPC_CREAT | 0777)) == -1)
    {
        perror("fail to msgget");
        exit(1);
    }
    printf( "msqid = %d\n",msgid);
    system("ipcs -q");
//通过msgrcv函数接收消息队列中的信息(读操作)
//注意:如果没有第四个参数指定的消息时,msgrcV函数会阻塞等待
MSG msg;
//如果第四个参数为,则按照先进先出的方式读取数据
//if(msgrcv(msgid,&msg,MSGTEXT_SIZE,0,0)==-1)
//如果第四个参数为>0,则获取当前值得消息类型的数据
if(msgrcv(msgid,&msg,MSGTEXT_SIZE,2,0)==-1)
{
        perror("fail to msgrcv");
        exit(1);
}
printf("recv_msg = %s\n",msg.msg_text);
system( "ipcs -q");
return 0;
}

测试结果:

发送四次数据


聊天使用消息队列 vx消息队列_物联网_14


指定读取哪条消息:


聊天使用消息队列 vx消息队列_iot_15


没有第四个参数指定的消息,msgrcV函数会阻塞等待


聊天使用消息队列 vx消息队列_物联网_16


打开新的终端,再次发送四次数据,消息队列中出现第四个参数指定的消息,接收端继续接收


聊天使用消息队列 vx消息队列_物联网_17


2.4.3测试 3:如果第四个参数为<0,则获取当前值得绝对值内最小的数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define N 128
typedef struct{
    long msg_type;  //消息类型,必须在结构体的第一个位置并且类型必须是long
    char msg_text[N];       //消息正文,也可以有多个成员并且类型也可以是任意
}MSG;

#define MSGTEXT_SIZE (sizeof(MSG) - sizeof(long))

int main(int argc,char const *argv[])
{
    //通过ftok函数获取ipc键值
    key_t mykey;
    if((mykey = ftok(".",100)) == -1)
    {
        perror( "fail to ftok" );
        exit(1);
    }
    printf( "mykey = %#x\n",mykey);

    //通过msgget函数创建一个消息队列
    int msgid;
    if((msgid=msgget(mykey,IPC_CREAT | 0777)) == -1)
    {
        perror("fail to msgget");
        exit(1);
    }
    printf( "msqid = %d\n",msgid);
    system("ipcs -q");
//通过msgrcv函数接收消息队列中的信息(读操作)
//注意:如果没有第四个参数指定的消息时,msgrcV函数会阻塞等待
MSG msg;
//如果第四个参数为,则按照先进先出的方式读取数据
//if(msgrcv(msgid,&msg,MSGTEXT_SIZE,0,0)==-1)
//如果第四个参数为>0,则获取当前值得消息类型的数据
//if(msgrcv(msgid,&msg,MSGTEXT_SIZE,2,0)==-1)
//如果第四个参数为<0,则获取当前值得绝对值内最小的数据
if(msgrcv(msgid,&msg,MSGTEXT_SIZE,-1,0)==-1)
{
        perror("fail to msgrcv");
        exit(1);
}
printf("recv_msg = %s\n",msg.msg_text);
system( "ipcs -q");
return 0;
}

运行结果:


聊天使用消息队列 vx消息队列_单片机_18


2.5 消息队列的控制

#include <sys/msg. h>
int msgctl(int msqid, int cmd,struct msqid ds, *buf);
功能:
    对消息队列进行各种控制,如修改消息队列的属性,或删除消息消息队列
参数:
    msqid:消息队列的标识符。
    cmd:函数功能的控制。
    buf: msgid_ds数据类型的地址,用来存放或更改消息队列的属性。
    cmd:函数功能的控制
        IPC_RMID:删除由msqid,指示的消息队列,将它从系统中删除并破坏相关数据结构。
        IPC_STAT:将msqid相关的数据结构中各个元素的当前值存入到由buf指向的结构中。
        IPC_SET:将msqid相关的数据结构中的元素设置为由buf指向的结构中的对应值。
返回值:
    成功返回 0 	失败返回-1
2.5.1 测试1:通过msgctl函数删除消息队列
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main(int argc,char const *argv[])
{
    //通过ftok函数获取ipc键值
    key_t mykey;
    if((mykey = ftok(".",100)) == -1)
    {
        perror( "fail to ftok" );
        exit(1);
    }
    printf( "mykey = %#x\n",mykey);

    //通过msgget函数创建一个消息队列
    int msgid;
    if((msgid=msgget(mykey,IPC_CREAT | 0777)) == -1)
    {
        perror("fail to msgget");
        exit(1);
    }
    printf( "msqid = %d\n",msgid);
    system("ipcs -q");
//通过msgctl函数删除消息队列
if (msgctl(msgid,IPC_RMID,NULL)==-1){
perror("fail to msgctl");
exit(1);
}
system( "ipcs -q");
return 0;
}

测试将结果:


聊天使用消息队列 vx消息队列_聊天使用消息队列_19