Linux 进程间通讯(第二阶段 消息队列、共享内存)

6.消息队列的通信原理

消息队列的工作原理如下图解释,消息队列存在于linux内核,每个队列有Id号,是由链表组成的,但我们关心的是1、如何创建新的消息队列(链表) ;2、进程B(或A)怎么加消息到队列;3、进程A(或B)怎么从队列拿到消息 。
在这里插入图片描述
下面看看别人的博文如何描述消息队列的:
消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识
1、特点

1、消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
2、消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
3、消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

7.消息队列相关api

AB进程使用消息队列方式通讯有这几个步骤:
A1、获取队列; B1、获取/创建队列
A2、读数据; B2、写数据到队列
原型

		1 #include <sys/msg.h>
		2 // 创建或打开消息队列:成功返回队列ID,失败返回-1
		3 int msgget(key_t key, int flag);//key是索引值是无类型的整形数,通过其从内核中找到队列,flag是打开队列的方式。返回值是队列的ID。
		
		4 // 添加消息:成功返回0,失败返回-1
		5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);//msgid是队列的ID;第二个参数是消息内容,第三个是消息的大小,flag=0是非阻塞方式写到消息队列结构体里面
		
		6 // 读取消息:成功返回消息数据的长度,失败返回-1
		7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);//于上面的发送消息一样的参数,type是进程找相关类型的队列消息,flag如果是0的话就是以默认方式读数据,读不到type(就是结构体的类型)是888类型的数据会阻塞。
		
		8 // 控制消息队列:成功返回0,失败返回-1
		9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

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

1、如果没有与键值key相对应的消息队列,并且flag中包含了IPC_CREAT标志位。//一般用第一种情况。
2、key参数为IPC_PRIVATE。
队列里面的单个信息是个结构体是这样的(是读写队列函数的第二个参数模板)

struct msgbuf {
       long mtype;       /* message type, must be > 0 *//类型
       char mtext[1];    /* message data */					//内容
       };

8.消息队列编程收发数据

直接上代码(msgGet.c):接收端

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

       //int msgget(key_t key, int msgflg);
        //int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
       //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
       long mtype;       /* message type, must be > 0 */
       char mtext[128];    /* message data */
};

int main()
{
        //1.huoqu 
        struct msgbuf readBuf;

        int msgId = msgget(0x1234,IPC_CREAT|0777);//打开或创建队列
        if(msgId ==-1){
                printf("get que failuer\n");
        }

        msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);//读取消息
        printf("read from que:%s\n",readBuf.mtext);

        struct msgbuf sendBuf = {988,"thank you for reach"};
        msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//发送消息


        return 0;
}

直接上代码(msgSend.c):接收端

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

       //int msgget(key_t key, int msgflg);
        //int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
       //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
       long mtype;       /* message type, must be > 0 */
       char mtext[128];    /* message data */
};

int main()
{
        //1.send
        struct msgbuf sendBuf = {888,"this is message from quen"};//队列的类型和消息内容都在这个结构体里。
        struct msgbuf readBuf;

        int msgId = msgget(0x1234,IPC_CREAT|0777);//创建或打开队列
        if(msgId ==-1){
                printf("get que failuer\n");
        }

        msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//发送队列消息

        msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);//接收队列消息
        printf("return from get:%s\n",readBuf.mtext);


        return 0;
}

运行结果如下:(两个进程间可以通过消息队列发送和接收信息)
在这里插入图片描述

9.键值生成及消息队列移除

上面的带代是key(是索引,去内核找到相关队列的ID)被定死的,比较恼火,这里我们用更高大上的方式来做:
首先需要用到ftok函数
系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
ftok原型
头文件

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

函数原型:

key_t ftok( const char * fname, int id )

fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:
key_t key;
key = ftok(".", 1); 这样就是将fname设为当前目录。
id是子序号。虽然是int类型,但是只使用8bits(1-255)(可以是字符,因为字符是一个字节的,)。
在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。
如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
查询文件索引节点号的方法是: ls -i

下面是键值生消息队列接收端(msgGet.c)的代码:

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

       //int msgget(key_t key, int msgflg);
        //int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
       //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
       long mtype;       /* message type, must be > 0 */
       char mtext[128];    /* message data */
};

int main()
{
        //1.huoqu 
        struct msgbuf readBuf;

        key_t key;
        key = ftok(".",'z');
        printf("key=%x\n",key);

        int msgId = msgget(key,IPC_CREAT|0777);
        if(msgId ==-1){
                printf("get que failuer\n");
        }

        msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
        printf("read from que:%s\n",readBuf.mtext);

        struct msgbuf sendBuf = {988,"thank you for reach"};
        msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);

        msgctl(msgId,IPC_RMID,NULL);

        return 0;
}

下面是键值生消息队列接收端(msgSend.c)的代码:

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

//int msgget(key_t key, int msgflg);
//int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf {
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};

int main()
{
        //1.send
        struct msgbuf sendBuf = {888,"this is message from quen"};
        struct msgbuf readBuf;

        key_t key;					
        key = ftok(".",'z');		//键值生成队列,需要先生成key。
        printf("key=%x\n",key);

        int msgId = msgget(key,IPC_CREAT|0777);
        if(msgId ==-1){
                printf("get que failuer\n");
        }

        msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);

        msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);
        printf("return from get:%s\n",readBuf.mtext);

        msgctl(msgId,IPC_RMID,NULL);		//消除队列,第一个参数是队列的Id,第二个参数是命令,这里用消除的命令,第三个参数基本是写NULL。

        return 0;
}

运行结果图如下:
在这里插入图片描述

10.共享内存概述

下图是用场景来解释进程间通讯:1、管道的话就是男(或女)的写纸条塞进管道,女(或男)的只能读,不能写,只能确定一方是读还是写;2、消息队列男的放入箱子中纸条,女的去看,但是不拿走;女的也可以放纸条,男的看,不拿走;3、共享内存是纸条就放在桌上,男的直接写,女的直接就可以看到了,这是目前为止比较先进的一种通讯IPC方式。
在这里插入图片描述

11.共享内存编程实现

1、特点:

共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
因为多个进程可以同时操作,所以需要进行同步。
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

2、原型

1 #include <sys/shm.h>
2 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
3 int shmget(key_t key, size_t size, int flag);//第二个参数是共享内存大小,其必须于兆(M)对齐的。
4 // 连接(映射)共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
5 void *shmat(int shm_id, const void *addr, int flag);//第二个参数写0,让Linux内核自动帮我们安排共享内存。第三个参数一般我们也写0,代表我们映射进来的共享内存可读可写。
6 // 断开与共享内存的连接:成功返回0,失败返回-1
7 int shmdt(void *addr); 
8 // 控制共享内存的相关信息:成功返回0,失败返回-1
9 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);//一般用来干掉共享内存,第二个是命令,这里用消除的命令,消息队列有碰到;第三个参数一般写0.

下面是共享内存写端的代码(shmw.c):

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/*函数原型
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
*/
int main()
{
        int shmid;
        char *shmaddr;

        key_t key;
        key = ftok(".",1);
        printf("key=%d\n",key);//创建key

        shmid = shmget(key,1024*4,IPC_CREAT|0666);//shmid与key值不相等,这里是创建共享内存
        printf("shmid=%d\n",shmid);
        if(shmid == -1){
                printf("shmget noOk\n");
                exit(-1);
        }

        shmaddr = shmat(shmid,NULL,0);//映射到这个进程,用指针指向这个共享内存空间。

        printf("shmat ok\n");
        strcpy(shmaddr,"zhanglong");//对内存空间写数据

        sleep(5);
        shmdt(shmaddr);//进程断开与内存空间的连接
        shmctl(shmid,IPC_RMID,0);//消除内存空间

        printf("quit\n");

        return 0;
}

下面是共享内存读端的代码(shmw.c):

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/*函数原型
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
*/
int main()
{
        int shmid;
        char *shmaddr;

        key_t key;
        key = ftok(".",1);
        printf("key=%d\n",key);//创建key(索引)

        shmid = shmget(key,1024*4,0);//这里只对共享内存读,所以第三个参数写0就好了。
        printf("shmid=%d\n",shmid);
        if(shmid == -1){
                printf("shmget noOk\n");
                exit(-1);
        }

        shmaddr = shmat(shmid,NULL,0);//映射共享内存,连接当前进程。

        printf("shmat ok\n");
        printf("data: %s",shmaddr);

        shmdt(shmaddr);//断开连接,因为写端已经消除共享内存了,所以这里不需要调用shmctl函数消除。

        printf("quit\n");

        return 0;
}

运行结果如下图,此处只是简单的对共享内存应用,只是进行一端写,不然会把共享内存写的东西杂在一起的,所以需要后面信号量的学习;
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值