进程间通信之消息队列和共享内存

消息队列

之前我们解析了一下进程间通信的管道通信,今天我们来看一下另外俩种通信方式:消息队列和共享内存。

那什么是消息队列呢?之前我们说管道在进程间通信的时候是基于字节流的,而消息队列在进程间通信的时候是基于数据块的,

并且是有类型的数据块。消息队列提供了一个从一个进程向另外一个进程发送一个有不同类型值的数据块的方法。

每个消息队列都有一个msqid_ds 结构与其相关联,在/usr/include/linux/msg.h 中可以查到:

进程间通信之消息队列和共享内存_第1张图片

此结构定义了队列的当前状态,其中第一个成员 struct ipc_perm 是IPC对象数据结构,内核为每一个IPC对象维护一个数据结构。这个

在/usr/include/linux/ipc.h 中也可以查到:

进程间通信之消息队列和共享内存_第2张图片

接下来我们看一下消息队列相关函数:

msgget函数

功能:用来创建和访问一个消息队列

函数原型:int msgget(key_t key,int msgflg);

参数:

key:某个消息队列的名字,可通过ftok函数获得,为了确保进程间通信,我们必须保证俩个进程的key值相同。

#include

key_t ftok(const char *path,int id);

path参数必须引用一个现有的文件。

msgflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的,最常用的两个是

           IPC_CREAT,IPC_EXCL。如果希望创建一个新的消息队列,那么必须在flag中同时指定IPC_CREAT和IPC_EXCL位,如果

           消息队列已经存在就会造成出错。如果在flag中单独指定IPC_CREAT位,消息队列不存在就创建,存在就返回不会报错。

返回值:成功返回一个非负整数,即该消息队列的标识码;失败返回-1.

msgctl函数:

功能:消息队列的控制
函数原型:int msgctl(int msqid,int cmd,struct msqid_ds *buf);
参数:     
         msqid:由msgget函数返回的消息队列标识码     
         cmd:是将要采取的动作,(有三个可取值,在下文中会说明)
返回值:成功返回0,失败返回-1

cmd将要采取的三个动作:

IPC_STAT:该命令用来获取消息队列对应的 msqid_ds 数据结构,并将其保存到 buf 指定的地址空间。 

IPC_SET:该命令用来设置消息队列的属性,要设置的属性存储在buf中。

 IPC_RMID:从内核中删除 msqid 标识的消息队列。 

msgsnd函数:

功能:把一条消息添加到消息队列中
函数原型:int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflag);
返回值:成功返回0;失败返回-1
msgrcv函数:
功能:从一个消息队列接受消息
原型:ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
返回值:成功返回实际放到接收缓冲区里的字符个数,失败返回-1

这俩个函数的参数比较想象,我们放在一起说:

msqid:由msgget函数返回的消息队列标识码

msgq:是一个指针,指向准备发送(接收)的消息。消息结构参考形式如下:

struct msgbuf{
      long  type;//进程间发送数据块的类型值
      char   text[1];//消息存放的地方
};

msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型

msgtyp(msgrcv函数独有):可以指定想要哪种消息,如果为0,返回队列中的第一个消息;如果大于0,返回队列中消息类型为

                                             msgtyp的第一个消息;如果小于0,返回队列中消息类型值小于等于msgtyp绝对值的消息,如果这种消

                                             息有若干个,则取类型值最小的消息。

msgflag:可以将msgflag值指定为IPC_NOWAIT,使操作不阻塞。这样,如果队列已满或者没有所指定类型的消息可用,则使得

               msgsnd立即出错返回EAGAIN或者使得msgrcv返回-1.

接下来我们用代码实现一下server与client之间的通信:

Makefile:

  1 
  2 .PHONE:all
  3 all:client server
  4 
  5 client:client.c comm.c
  6     gcc -o $@ $^
  7 server:server.c comm.c
  8     gcc -o $@ $^
  9 .PHONE:clean
 10 
 11 clean:
 12     rm -f client server

comm.h:

  1 #ifndef _COMM_H_
  2 #define _COMM_H_
  3 
  4 #include
  5 #include
  6 #include
  7 #include
  8 #include
  9 
 10 #define PATHNAME "."
 11 #define PROJ_ID 0x6666
 12 
 13 
 14 #define SERVER_TYPE 1
 15 #define CLIENT_TYPE 2
 16 
 17 struct msgbuf{
 18     long type;
 19     char test[100];
 20 };
 21 
 22 int createMsgQueue();//chuangjian xiaoxi duilie
 23 int getMsgQueue();//huode xiaoxi duilie
 24 int destroyMsgQueue(int msgid);
 25 int sendMsgQueue(int msgid,int TYPE,char* msg);
 26 int recvMsgQueue(int msgid,int TYPE,char msg[]);
 27 
 28 
 29 #endif
                                                                                                                                                      

comm.c:

  1 #include"comm.h"
  2 
  3 static int commMsgQueue(int flag)
  4 {
  5     key_t key = ftok(PATHNAME,PROJ_ID);
  6     if(key < 0){
  7         perror("ftok");
  8         return -1;
  9     }
 10     else{
 11         int msgid = msgget(key,flag);
 12         if(msgid < 0){
 13             perror("msgget");
 14             return -2;
 15         }
 16         return msgid;
 17     }
 18 }
 19 
 20 int createMsgQueue(){
 21     return commMsgQueue(IPC_CREAT | IPC_EXCL | 666);
 22 }
 23 
 24 int getMsgQueue(){
 25     return commMsgQueue(IPC_CREAT);
 26 }
 27 
 28 
 29 int destroyMsgQueue(int msgid){
 30     if(msgctl(msgid,IPC_RMID,NULL) < 0){
 31         perror("msgtcl");
 32         return -1;
 33     }
 34     return 0;
 35 }
 36 
 37 
 38 int sendMsgQueue(int msgid,int TYPE,char* msg){
 39     struct msgbuf buf;
 40     buf.type = TYPE;
 41     strcpy(buf.test,msg);
 42     if(msgsnd(msgid,&buf,sizeof(buf.test),0) < 0){
 43         perror("msgsnd");
 44         return -1;
 45     }
 46     return 0;
 47 }
 48 
 49 
 50 int recvMsgQueue(int msgid,int TYPE,char msg[]){
 51     struct msgbuf buf;
 52     if(msgrcv(msgid,&buf,sizeof(buf.test),TYPE,0) < 0){
 53         perror("msgrecv");
 54         return -1;
 55     }
 56     strcpy(msg,buf.test);
 57     return 0;
 58 }

server.c:

  1 #include"comm.h"
  2 
  3 int main()
  4 {
  5     int msgid = createMsgQueue();
  6     char buf[1024];
  7     while(1){
  8         buf[0] = 0;
  9         recvMsgQueue(msgid,CLIENT_TYPE,buf);
 10         if(strcasecmp("quit",buf) == 0){
 11             return -1;
 12         }
 13         printf("client# %s\n",buf);
 14 
 15         printf("Please Enter#\n");
 16         ssize_t s = read(0,buf,sizeof(buf));
 17         if(s > 0){
 18             buf[s-1] = 0;
 19             sendMsgQueue(msgid,SERVER_TYPE,buf);
 20         }
 21     }
 22     destroyMsgQueue(msgid);
 23     return 0;
 24 }

client.c:

  1 #include"comm.h"
  2 
  3 int main()
  4 {
  5     int msgid = getMsgQueue();
  6     char buf[1024];
  7     while(1){
  8         buf[0] = 0;
  9         ssize_t s = read(0,buf,sizeof(buf));
 10         if(s > 0){
 11             buf[s-1] = 0;
 12             sendMsgQueue(msgid,CLIENT_TYPE,buf);
 13         }
 14     recvMsgQueue(msgid,SERVER_TYPE,buf);
 15     printf("server# %s\n",buf);
 16     printf("Please Enter#\n");
 17     }
 18 
 19     return 0;
 20 }
下面用俩个终端运行一下:

进程间通信之消息队列和共享内存_第3张图片

我们再学俩来学习俩条命令:ipcs和ipcrm命令

ipcs:显示IPC资源

ipcrm:手动删除IPC资源

而对消息队列执行这俩条命令要加 -q选项,如下图所示:

进程间通信之消息队列和共享内存_第4张图片


进程间通信之消息队列和共享内存_第5张图片
通过以上,我们总结一下通过消息队列的进程间通信,很明显的我们可以看到消息队列可以实现双向通信,而不是像管道一样只能单向通信,还有不同于管道的是消息队列的生命周期随内核,除非人工杀死它。


共享内存

接下来我们解析一下进程间通信的另一种方式:共享内存。

共享内存区是最快的IPC形式,因为它不需要把数据拷贝到内核,直接是进程与进程之间互相传递信息。

先在物理内存中开辟一段共享内存,然后通过页表各自映射到共享它的进程的地址空间,这是理解共享内存的主要思想。

内核为每个共享内存维护着一个数据结构,该结构至少要为每个共享内存段包含以下成员:

进程间通信之消息队列和共享内存_第6张图片

共享内存函数

shmget函数

功能:用来创建共享内存
原型:int shmget(key_t key,size_t size,int shmflg);
参数:
     key,这个共享内存段名字,获得方法和上面一样
     size:共享内存大小
     shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode标志是一样的
返回值:成功就返回一个非负整数,即该共享内存段的标识码;失败返回-1

shmat函数

功能:将共享内存段连接到进程地址空间
原型:void *shmat(int shmid,const void *shmaddr,int shmflg);
参数
     shmid:共享内存标识
     shmadrr指定连接的地址

shmdt函数

功能:将共享内存段与当前进程脱离
原型:int shmdt(const void *shmaddr);
参数:
      shmaddr:由shmat所返回的指针
返回值:成功返回0;失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

功能:用于控制共享内存
原型:int shmctl(int shmid,int cmd,struct shmid_ds *buf);
参数
      shmid:由shmget返回的共享内存标识码
      cmd:将要采取的动作(有三个可取值,与上文提到的一样)
       buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

下面来段代码我们看一下俩个进程怎样通过共享内存形成进程间通信的

Makefile:

  1 .PHONE:all
  2 all:client server
  3 
  4 client:client.c comm.c
  5     gcc -o $@ $^
  6 server:server.c comm.c
  7     gcc -o $@ $^
  8 
  9 .PHONE:clean
 10 clean:
 11     rm -f client server

comm.h:

  1 #ifndef _COMM_H_
  2 #define _COMM_H_
  3 
  4 
  5 #include
  6 #include
  7 #include
  8 #include
  9 
 10 #define PATHNAME "."
 11 #define PROJ_ID 0x7777
 12 
 13 int creatShm(int size);
 14 int destroyShm(int shmid);
 15 int getShm(int size);
 16 
 17 #endif

comm.c:

  1 #include"comm.h"
  2 
  3 int commShm(int size,int flag)
  4 {
  5     key_t key = ftok(PATHNAME,PROJ_ID);
  6     if(key < 0){
  7         perror("ftok");
  8         return -1;
  9     }
 10     int shmid = shmget(key,size,flag);
 11     if(shmid < 0){
 12         perror("shmget");
 13         return -2;
 14     }
 15     return shmid;
 16 }
 17 int createShm(int size)
 18 {
 19     commShm(size,IPC_CREAT|IPC_EXCL|0666);
 20 }
 21 int getShm(int size)
 22 {
 23     commShm(size,IPC_CREAT|0666);
 24 }
 25 int destroyShm(int shmid)
 26 {
 27     if(shmctl(shmid,IPC_RMID,NULL) < 0){
 28         perror("shmtcl");
 29         return -1;
 30     }
 31     return 0;
 32 }
                 

server.c:

  1 #include"comm.h"
  2 
  3 int main()
  4 {
  5     int shmid = createShm(2048);
  6 
  7     char *arr = shmat(shmid,NULL,0);
  8     sleep(2);
  9     int i = 0;
 10     while(i++ < 9){
 11         printf("client:%s\n",arr);
 12         sleep(1);
 13     }
 14 
 15     shmdt(arr);
 16     sleep(10);
 17     destroyShm(shmid);
 18     return 0;
 19 }

client.c:

   1 #include"comm.h"
  2 
  3 
  4 int main()
  5 {
  6     int shmid = getShm(2048);
  7     sleep(1);
  8     char *arr = shmat(shmid,NULL,0);
  9     sleep(2);
 10     int i = 0;
 11     while(i<9){
 12         arr[i] = 'A';
 13         i++;
 14         arr[i] = 0;
 15         sleep(1);
 16     }
 17 
 18     shmdt(arr);
 19     return 0;
 20 }

我们打开俩个终端来运行一下:

进程间通信之消息队列和共享内存_第7张图片进程间通信之消息队列和共享内存_第8张图片

同样我们可以用ipcs -m查看我们创建出来的共享内存,也可以用ipcs -m 加要删除的共享内存shmid 就可以删除共享内存。

因为我们在server.c里已经通过函数删除过共享内存了,运行时就不必再使用命令去删除了。

通过以上内容我们可以总结一下共享内存的特点:

共享内存生命周期随内核,这个同消息队列一样。

共享内存速度快,是最快的IPC形式。

共享内存没有互斥与同步机制。(互斥与同步机制将会在下一篇信号量中写到)

你可能感兴趣的:(Linux)