进程间通信的分类:

    文件

    文件锁

    管道和匿名管道pipe

    信号

    消息队列

    共享内存

    信号量

    互斥量

    条件变量

    读写锁

    套接字

两个进程同时向管道中写数据,内核要保证它们是原子的,内核按照页做限制。

进程间共享信息的三种方式:一个通过文件系统,一个通过内核,一个在应用空间

 

IPC对象的持续性:

  随进程持续:一直存在,直到打开的最后一个进程结束。(如pipe和FIFO)

  随内核持续:一直存在,直到内核自举或者显式删除。(如System V消息队列、共享内存、信号量)

  随文件系统持续:一直存在,直到显式删除,即使内核自举还存在。(POSIX消息队列、共享内存、信号量(如果是使用映射文件来实现))

 查看系统的IPC对象以及它们的一些属性可以使用ipcs命令,如下:

 

其中key表示键,semid表示这个对象的id,owner表示拥有者。共享内存还有个属性是nattch,表示有多少个人连接上共享内存了。

ipcs -l可以查看IPC对象的一些系统限制:

 

 

消息队列提供了从一个进程向另一个进程发送一块数据的方法

每个数据块都有一个类型,接收者进程接收的数据块可以有不同的类型值

消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)。

管道是基于数据流的,先进先出。

消息队列是有边界的,可以后进先出。

消息队列有如下三个限制:

cat  /proc/sys/kernel/msgmax      最大消息长度限制

cat  /proc/sys/kernel/msgmnb      消息队列总的字节数

cat  /proc/sys/kernel/msgmni        消息条目数

 使用man 2 msgctl可以查看消息队列的一些数据结构:

struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};

struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};

消息队列在内核中的表示如下:

 

msq_ids存储了消息的一些元信息,具体的消息由内核来管理。

消息队列相关的一些API函数:

msgget函数原型如下:

  int msgget(key_t key,  int  msgflg)

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

  参数:

    key表示某个消息队列的名字

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

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

 编写如下的msgget测试函数:

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <stdlib.h>
 6 #include <errno.h>
 7 #include <sys/msg.h>
 8 
 9 int main()
10 {
11     int msgid;
12     
13     msgid = msgget(0x1234, 0666);
14     if(msgid < 0)
15     {
16         perror("msgget error");
17         exit(0);
18     }
19     
20     return 0;
21 }

这段程序代表打开一个消息队列,0666代表权限,0代表8进制,执行结果如下:

 

系统中没有key值为0x1234的消息队列,所以出错返回。我们可以根据errno错误码进行更精确的控制。

我们修改msgget函数的第二个参数,程序如下:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/msg.h>

int main()
{
    int msgid;
    
    msgid = msgget(0x1234, 0666 | IPC_CREAT);
    if(msgid < 0)
    {
        perror("msgget error");
        exit(0);
    }
    
    printf("create msg queue success\n");
    
    return 0;
}

现在的参数表示,如果这个消息队列存在就打开旧的,如果不存在就创建新的。

现在我们的系统中是没有消息队列的,执行结果如下:

 

使用ipcs查看系统中的消息队列如下:

 

可以看到确实出现了一个key值为0x1234的消息队列IPC对象。

进一步加参数,如下:

 

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <stdlib.h>
 6 #include <errno.h>
 7 #include <sys/msg.h>
 8 
 9 int main()
10 {
11     int msgid;
12     
13     msgid = msgget(0x1234, 0666 | IPC_CREAT | IPC_EXCL);
14     if(msgid < 0)
15     {
16         if(errno == EEXIST)
17         {
18             printf("msg is exist\n");
19         }
20         perror("msgget error");
21         exit(0);
22     }
23     
24     printf("create msg queue success\n");
25     
26     return 0;
27 }

 

加上IPC_EXCL表示,如果文件存在,若还要创建(加了IPC_CREAT)则返回错误。单独用IPC_EXCL没有意义。

执行结果如下:

 

我们验证key取IPC_PRIVATE的情况,如下:

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <stdlib.h>
 6 #include <errno.h>
 7 #include <sys/msg.h>
 8 
 9 int main()
10 {
11     int msgid;
12     
13     msgid = msgget(IPC_PRIVATE, 0666 );
14     if(msgid < 0)
15     {
16         if(errno == EEXIST)
17         {
18             printf("msg is exist\n");
19         }
20         perror("msgget error");
21         exit(0);
22     }
23     
24     printf("create msg queue success\n");
25     
26     return 0;
27 }

这时候,flag参数的IPC_CREAT和IPC_EXCL都没有作用了,执行结果如下:

此时创建出来的消息队列的key值为0,多次调用这个函数可以创建出多个key为0的消息队列,如下:

 私有的消息队列只能用在本进程或者和本进程有血缘关系的进程(通过fork创建)。将msgid传给其他没有血缘的进程也不能用。

 创建一个IPC对象的逻辑如下:

msgctl函数:

  原型:int msgctl(int msqid,  int cmd,  struct  msqid_ds  *buf)

  参数:

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

    cmd:将要采取的动作

  返回值:成功返回0,失败返回-1

cmd可取的三个值如下:

 

 

IPC对象,消息队列是linux负责持久化,我们可以从linux内核中获取消息队列的信息,或者修改消息队列。

msgctl的测试函数如下:

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <stdlib.h>
 6 #include <errno.h>
 7 #include <sys/msg.h>
 8 
 9 int main()
10 {
11     int msgid;
12     int ret = 0;
13     
14     msgid = msgget(0x1234, 0666);
15     if(msgid < 0)
16     {
17         if(errno == EEXIST)
18         {
19             printf("msg is exist\n");
20         }
21         perror("msgget error");
22         exit(0);
23     }
24     
25     printf("msgid = %d\n", msgid);
26     
27     struct msqid_ds buf;
28     memset(&buf, 0, sizeof(buf));
29     ret = msgctl(msgid, IPC_STAT, &buf);
30     
31     if(ret == -1)
32     {
33         perror("msgctl error");
34         exit(0);
35     }
36     
37     printf("mode = %o\n", buf.msg_perm.mode);
38     
39     return 0;
40 }

系统中已经存在一个我们以前创建的消息队列,key为0x1234,msgid为0,执行程序,结果如下:

 msgsnd函数,消息队列的发送函数:

  原型:int msgsnd(int msqid,  const void* msgp, size_t  msgsz, int  msgflg)

  参数:

    msgid:由msgget返回的消息队列标识码

    msgp:是一个指针,指针指向准备发送的消息

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

    msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情

  返回值:成功返回0,失败返回-1

  msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误

  消息结构在两方面受到制约,首先,它必须小于系统规定的上限值;其次,它必须以一个long int长整数开始,接收者将利用这个长整数确定消息的类型

  消息结构参考形式如下:

struct msgbuf

{

  long  mtype;

  char  mtext[1];

}

测试函数如下:

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <stdlib.h>
 6 #include <errno.h>
 7 #include <sys/msg.h>
 8 
 9 struct msgbuf
10 {
11     long mtype;
12     char mtext[1024];
13 };
14 
15 int main()
16 {
17     int msgid;
18     int ret = 0;
19     
20     msgid = msgget(0x1234, 0666);
21     if(msgid < 0)
22     {
23         if(errno == EEXIST)
24         {
25             printf("msg is exist\n");
26         }
27         perror("msgget error");
28         exit(0);
29     }
30     
31     printf("msgid = %d\n", msgid);
32     
33     struct msgbuf buf;
34     memset(&buf, 0, sizeof(struct msgbuf));
35     buf.mtype = 1;
36     strcpy(buf.mtext, "1111111222222333333");    
37     
38     ret = msgsnd(msgid, &buf, 10, IPC_NOWAIT);
39     if(ret < 0)
40     {
41         perror("msgsnd error");
42         exit(0);
43     }
44     
45     return 0;
46 }

消息类型我们定义为1类型,这个是自己选的。系统中已经存在key值为0x1234的消息队列,我们执行完这个函数后,消息就已经写入到了这个消息队列中,我们怎么验证呢?msgctl可以读出消息队列的信息,我们写如下的验证程序:

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <stdlib.h>
 6 #include <errno.h>
 7 #include <sys/msg.h>
 8 
 9 int main()
10 {
11     int msgid;
12     int ret = 0;
13     
14     msgid = msgget(0x1234, 0666);
15     if(msgid < 0)
16     {
17         if(errno == EEXIST)
18         {
19             printf("msg is exist\n");
20         }
21         perror("msgget error");
22         exit(0);
23     }
24     
25     printf("msgid = %d\n", msgid);
26     
27     struct msqid_ds buf;
28     memset(&buf, 0, sizeof(buf));
29     ret = msgctl(msgid, IPC_STAT, &buf);
30     
31     if(ret == -1)
32     {
33         perror("msgctl error");
34         exit(0);
35     }
36     
37     printf("mode = %o\n", buf.msg_perm.mode);
38     printf("bytes in msg queue : %d\n", buf.__msg_cbytes);
39     printf("msg numbers : %d\n", buf.msg_qnum);
40     
41     return 0;
42 }

执行结果如下:

 

这和我们写到消息队列中的消息是吻合的。

 msgrcv接收消息函数:

  原型:ssize_t  msgrcv(int  msqid,  void *msgp,  size_t  msgsz,  long  msgtyp,  int  msgflg)

  参数:

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

    msgp:是一个指针,指针指向准备接收的消息

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

    msgtype:它可以实现接收优先级的简单形式

    msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事

  返回值:成功返回实际放到接收缓冲区中的字符个数,失败返回-1

msgtype=0  返回队列第一条信息

msgtype>0 返回队列第一条类型等于msgtype的消息

msgtype<0 返回队列第一条类型小于等于msgtype绝对值的消息,并且是满足条件的消息类型最小的消息

msgflg=IPC_NOWAIT,队列没有可读消息时不等待,返回ENOMSG错误。

msgflg=MSG_NOERROR,消息大小超过msgsz时被截断

msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息。

 上面我们已经将消息写到了消息队列中,这个消息队列由内核维护,现在我们下一个读消息的程序,如下:

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 #include <string.h>
 5 #include <stdlib.h>
 6 #include <errno.h>
 7 #include <sys/msg.h>
 8 
 9 struct msgbuf
10 {
11     long mtype;
12     char mtext[1024];
13 };
14 
15 int main()
16 {
17     int msgid;
18     int ret = 0;
19     
20     msgid = msgget(0x1234, 0666);
21     if(msgid < 0)
22     {
23         if(errno == EEXIST)
24         {
25             printf("msg is exist\n");
26         }
27         perror("msgget error");
28         exit(0);
29     }
30     
31     printf("msgid = %d\n", msgid);
32     
33     struct msgbuf buf;
34     memset(&buf, 0, sizeof(struct msgbuf));
35 
36     ret = msgrcv(msgid, &buf, 1024, 0, IPC_NOWAIT);
37     if(ret < 0)
38     {
39         perror("msgrcv error");
40         exit(0);
41     }
42     
43     buf.mtext[ret] = '\0';
44     printf("recv msg : %s\n", buf.mtext);
45     
46     return 0;
47 }

执行结果如下:

 

可以看到,读出的消息正是我们写入的。再次执行我们的验证程序(读取消息队列的状态),结果如下:

 

可以看到,消息已经被我们读走了,现在的队列中已经没有消息了。

综合案例如下:

 

 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 
 4 #include <stdio.h>
 5 #include <string.h>
 6 #include <stdlib.h>
 7 #include <errno.h>
 8 
 9 #include <sys/msg.h>
10 
11 struct msg_buf
12 {
13     long mtype;
14     char data[255];
15 };
16  
17 /* 注意long 和 int 在32bit 和 64bit系统之下是不一样的
18 struct msg_buf
19 {
20     long mtype;
21     char data[255];
22 };
23 */
24  
25 int main()
26 {
27     key_t key;
28     int msgid;
29     int ret;
30     struct msg_buf msgbuf;
31     int msgtype =  getpid();
32 
33     key=ftok("./msgfile",'a');
34     printf("key =[%x]\n",key);
35     
36     printf("sizeof(long):%ld, sizeof(int):%d \n", sizeof(long), sizeof(int));
37     
38     msgid=msgget(key, IPC_CREAT |IPC_EXCL|0666); //通过文件对应
39 
40     if(msgid==-1)
41     {
42         if (errno == EEXIST)
43         {
44             printf("EEXIST:.....\n");
45             key=ftok("./msgfile",'a');
46             msgid=msgget(key, IPC_CREAT|0666); //通过文件对应
47         }
48         else
49         {
50              printf("create error\n");
51             perror("msget: \n");
52             return -1;
53         }
54         
55     }
56     printf("msgid:%d \n", msgid);
57 
58        msgbuf.mtype = msgtype; //        getpid();
59 
60     printf("getpid(): %d \n", getpid());
61     strcpy(msgbuf.data,"test haha");
62     ret = msgsnd(msgid,&msgbuf, sizeof(msgbuf.data), IPC_NOWAIT);
63     if(ret==-1)
64     {
65             printf("send message err\n");
66             perror("senderr");
67             return -1;
68     }
69     sleep(1);
70 
71     memset(&msgbuf,0,sizeof(msgbuf));
72     
73     ret=msgrcv(msgid, &msgbuf, sizeof(msgbuf.data), msgtype, IPC_NOWAIT);
74     if(ret==-1)
75     {
76             printf("recv message err\n");
77             perror("dd");
78             return -1;
79     }
80     printf("recv msg =[%s]\n",msgbuf.data);
81  
82 }

 

ftok是一个产生key值的函数,消息队列、共享内存、信号量都可以使用这个key。ftok第一个参数是路径,应用中可以将某个路径设置成一个环境变量。应用程序中就可以统一用getenv获取这个环境变量的值,得到路径名,然后再将路径名传给ftok函数。

 

posted on 2018-08-05 11:52  周伯通789  阅读(207)  评论(0编辑  收藏  举报