运行结果

avatar

代码实现

client.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <stdio.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <stdlib.h>
#define MSGKEY 75

struct msgform
{
long mtype;
char mtext[1000];
}msg;

int msgqid;

void client()
{
/*打开75#消息队列*/
msgqid=msgget(MSGKEY,0777);
while(1)
{
msg.mtype=10;
scanf("%s",msg.mtext);
printf("(client)sent\n");
/*发送消息*/
msgsnd(msgqid,&msg,1024,0);
/*接收消息*/
msgrcv(msgqid,&msg,1030,1,0);
printf("(clinet)received:%s\n",msg.mtext);
}
exit(0);
}
int main()
{
client();
return 0;
}

server.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <stdlib.h>

#define MSGKEY 75

struct msgform
{
long mtype;
char mtext[1000];
}msg;

int msgqid;

void server()
{
/*创建75#消息队列*/
msgqid=msgget(MSGKEY,0777|IPC_CREAT);
do
{
/*接收消息*/
msgrcv(msgqid,&msg,1030,10,0);
printf("(server)received:%s\n",msg.mtext);

msg.mtype=1;
scanf("%s",msg.mtext);
printf("(server)sent\n");
/*发送消息*/
msgsnd(msgqid,&msg,1024,0);
}while(1);
/*删除消息队列,归还资源*/
msgctl(msgqid,IPC_RMID,0);
exit(0);
}
int main()
{
server();
return 0;
}

消息队列

在许多方面看来,消息队列类似于有名管道,但是却没有与打开与关闭管道的复杂关联。然而,使用消息队列并没有解决我们使用有名管道所遇到的问题,例如管道上的阻塞。消息队列提供了一种在两个不相关的进程之间传递数据的简单高效的方法。与有名管道比较起来,消息队列的优点在独立于发送与接收进程,这减少了在打开与关闭有名管道之间同步的困难。消息队列提供了一种由一个进程向另一个进程发送块数据的方法。另外,每一个数据块被看作有一个类型,而接收进程可以独立接收具有不同类型的数据块。消息队列的好处在于我们几乎可以完全避免同步问题,并且可以通过发送消息屏蔽有名管道的问题。更好的是,我们可以使用某些紧急方式发送消息。坏处在于,与管道类似,在每一个数据块上有一个最大尺寸限制,同时在系统中所有消息队列上的块尺寸上也有一个最大尺寸限制。尽管有这些限制,但是 X/Open 规范并没有定义这些限制的具体值,除了指出超过这些尺寸是某些消息队列功能失败的原因。

消息队列函数定义

1
2
3
4
5
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
int msgget(key_t key, int msgflg);
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);

与信息号和共享内存一样,头文件 sys/types.h 与 sys/ipc.h 通常也是需要的。

Msgget()

使用 msgget 函数创建与访问一个消息队列:

1
int msgget(key_t key, int msgflg);

与其他 IPC 工具类似,程序必须提供一个指定一个特定消息队列的 key 值。特殊值IPC_PRIVATE 创建一个私有队列,这在理论上只可以为当前进程所访问。与信息量和共享内存一样,在某些 Linux 系统上,消息队列并不是私有的。因为私有队列用处较少,因而这并不是一个严重问题。与前面一样,第二个参数,msgflg,由 9 个权限标记组成。要创建一个新的消息队列,由 IPC_CREAT 特殊位必须与其他的权限位进行或操作。设置 IPC_CREAT
标记与指定一个已存在的消息队列并不是错误。如果消息队列已经存在,IPC_CREAT 标记只是简单的被忽略。如果成功,msgget 函数会返回一个正数作为队列标识符,如果失败则会返回-1。

Msgsnd()

msgsnd 函数允许我们将消息添加到消息队列:

1
int msgsnd(int msqid, const void *msg_ptr, size_t msg_sz, int msgflg);

消息结构由两种方式来限定。第一,他必须小于系统限制,第二,必须以 long int 开始,这在接收函数中会用作一个消息类型。
通常在使用消息时,是以如下形式来定义消息结构:

1
2
3
4
struct my_message {
long int message_type;
/* The data you wish to transfer */
}

因为 message_type 用于消息接收,所以不能简单的忽略他。必须定义我们自己的数据结构来包含并对其进行初始化,从而他可以包含一个可知的值。

  • 第一个参数,msgid,是由 msgget 函数所返回的消息队列标识符。
  • 第二个参数,msg_ptr,是一个指向要发送消息的指针,正如前面所描述的,这个消息必须以 long int 类型开始。
  • 第三个参数,msg_sz,是由 msg_ptr 所指向的消息的尺寸。这个尺寸必须不包含 long int 消息类型。
  • 第四个参数,msgflg,控制如果当前消息队列已满或是达到了队列消息的系统限制时如何处理。如果 msgflg 标记设置了 IPC_NOWAIT,函数就会立即返回而不发送消息,并且返回值
    为-1。如果 msgflg 标记清除了 IPC_NOWAIT 标记,发送进程就会被挂起,等待队列中有可用的空间。

函数如果成功,函数会返回 0,并且系统就会复制一份消息数据并将其放入消息队列;如果失败,则会返回-1。

Msgrcv()

msgrcv 函数由一个消息队列中收取消息:

1
int msgrcv(int msqid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
  • 第一个参数,msqid,是由 msgget 函数所返回的消息队列标记符。
  • 第二个参数,msg_ptr,是一个指向将要接收消息的指针,正如在 msgsnd 函数中所描述的,这个消息必须以 long int 类型开始。
  • 第三个参数,msg_sz,是由 msg_ptr 所指向的消息的尺寸,并不包含 long int 消息类型。
  • 第四个参数, msgtype,是一个 long int 类型,允许一个接收优先级形式的实现。如果 msgtype的值为 0,队列中第一个可用的消息就会被接收。如果其值大于 0,具有相同消息类型的第一个消息就会被接收。如果其值小于 0,第一个具有相同类型或是小于 msgtype 绝对值的消息就会被接收。
    • 如果只是简单的希望以其发送的顺序来接收消息,可以将 msgtype 设置为 0。
    • 如果希望接收特殊消息类型的消息,可以将msgtype 设置为等于这个值。
    • 如果希望接收消息类型为 n 或是小于 n 的值,我们可以将msgtype 设置为-n。
  • 第五个参数, msgflg,控制当没有合适类型的消息正在等待被接收时如何处理。如果在 msgflg中设置了 IPC_NOWAIT 位,调用就会立即返回,而返回值为-1。如果 msgflg 标记中消除了
    IPC_NOWAIT 位,进程就会被挂起,等待一个合适类型的消息到来。如果成功, msgrcv 会返回放入接收缓冲区中的字节数,消息会被拷贝到由 msg_ptr 所指
    向的用户分配缓冲区中,而数据就会由消息队列中删除。如果失败则会返回-1。

Msgctl()

msgctl()这与共享内存中的控制函数类似

1
2
3
4
5
6
7
8
int msgctl(int msqid, int command, struct msqid_ds *buf);

// msqid_ds 结构至少包含下列成员:
struct msqid_ds {
uid_t msg_perm.uid;
uid_t msg_perm.gid
mode_t msg_perm.mode;
}
  • 第一个参数,msqid,是由 msgget 函数所返回的标记符。
  • 第二个参数,command,是要执行的动作。他可以取下面三个值:命令 描述 IPC_STAT 设置 msqid_ds 结构中的数据来反射与消息队列相关联的值。 IPC_SET 如果进程有权限这样做,这个命令会设置与 msqid_ds 数据结构中所提供的消息队列相关联的值。IPC_RMID 删除消息队列。

如果成功则会返回 0,如果失败则会返回-1。当进程正在 msgsnd 或是 msgrcv 函数中等待时如果消息队列被删除,发送或接收函数就会失败。