接着上一篇文章的内容总结,上一篇文章总结了消息队列函数中的前两个msgget函数和msgctl函数,接下来学习总结msgsnd函数和msgrcv函数。
1.msgsnd函数
- 功能:把一条消息添加到消息队列中
- 原型:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); - 参数:
- msqid:由msgget函数返回的消息队列标识码
- msgp:是一个指针,指向准备发送的消息
- msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
- msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
- 返回值:成功返回0;失败返回-1
(1)msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误
(2)消息结构在两方面受到制约。首先,它必须小于系统规定的上限值(MSGMAX);其次,它必须以一个long int长整数开始,接收者函数利用这个长整数确定消息的类型
(3)消息结构参考形式如下:
1 2 3 4 |
struct msgbuf { long mtype; char mtext[1]; }; |
(4)考虑如下示例:
1)接受三个参数,第二个参数为发送消息的长度msgsz;第三个参数为消息类型mtype。
2)调用msget函数打开消息队列,并定义一个消息结构体struct msgbuf *ptr,令ptr->mtype指向type。最后调用msgsnd函数:
msgsnd(msgid, ptr, len, 0);
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 42 |
#include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #define ERR_EXIT(m)\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) struct msgbuf{ long mtype; char mtext[1]; }; int main(int argc, char *argv[]) { if (argc != 3){ fprintf(stderr, "Usage: %s <bytes> <type>\n", argv[0]); exit(EXIT_FAILURE); } int len =atoi(argv[1]); int type = atoi(argv[2]); int msgid; msgid = msgget(1234, 0); if(msgid == -1) ERR_EXIT("msgget"); struct msgbuf *ptr; ptr = (struct msgbuf*)malloc(sizeof(long) + len); ptr->mtype = type; if (msgsnd(msgid, ptr, len, 0) < 0) ERR_EXIT("msgsnd"); return 0; } |
3)编译后,发送一个长度为100字节,类型为1的消息到消息队列:
1 2 3 4 5 6 7 8 9 10 |
$ ./msg_send 100 1 //查看 $ ./msg_stat msgget success msgid=65536 mode=600 bytes=100 number=1 msgmax=65536 |
查看结果可知,消息队列中字节大小为100,消息个数为1(因为我们只发送了一次消息到消息队列,这里的1跟消息类型1不同)。
2.msgrcv函数
- 功能:从一个消息队列接受消息
- 原型:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtype, int msgflg); - 参数:
- msqid:由msgget函数返回的消息队列标识码
- msgp:是一个指针,指针指向准备接收的消息
- msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
- msgtype:它可以实现接收优先级的简单形式
- msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
- 返回值:成功返回实际放到接收缓冲区里的字符个数;失败返回-1
其中,对msgtype有:
- msgtype=0:返回队列第一条消息,即按顺序接收
- msgtype>0:返回队列第一条类型等于msgtype的消息
- msgtype<0:返回队列第一条类型小于等于msgtype绝对值的消息
- msgflg=IPC_NOWAIT:队列没有可读消息不等待,返回ENOMSG错误
- msgflg=MSG_NOERROR:消息大小超过msgsz时被截断
- msgtype>0且msgflg=MSG_EXCEPT:接受类型不等于msgtype的第一条消息
(1)考虑如下示例
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
#include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #define ERR_EXIT(m)\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) #define MSGMAX 8192 struct msgbuf{ long mtype; char mtext[1]; }; int main(int argc, char *argv[]) { int flag = 0; int type = 0; while(1){ int opt = getopt(argc, argv, "nt:"); if (opt == '?') exit(EXIT_FAILURE); if (opt == -1) break; switch(opt){ case 'n': flag |= IPC_NOWAIT; break; case 't': type = atoi(optarg); break; } } int msgid; msgid = msgget(1234, 0); if(msgid == -1) ERR_EXIT("msgget"); struct msgbuf *ptr; ptr = (struct msgbuf*)malloc(sizeof(long) + MSGMAX); ptr->mtype = type; int n = 0; if (( n = msgrcv(msgid, ptr, MSGMAX, type, flag )) < 0) ERR_EXIT("msgrcv"); printf("read %d bytes type = %ld\n", n, ptr->mtype); return 0; } |
其中使用了getopt函数,当指定-n选项时,即意味着flag增加了IPC_NOWAIT选项;-t选型后可接消息的类型。
1)编译运行,接收消息类型为1的消息:
1 2 |
$ ./msg_rcv -t 1 read 100 bytes type = 1 |
2)不指定消息类型,则type=0,按顺序接收队列中的消息
1 2 |
$ ./msg_rcv read 200 bytes type = 2 |
3)指定-n选项,以非阻塞的方式接收消息,比如接收一个类型不存在的消息,就会阻塞,此时若指定-n,则不会阻塞:
1 2 |
$ ./msg_rcv -n -t444 msgrcv: No message of desired type |
(2)由上示例可知,消息队列可以按照指定的方式来接收消息,而不用像管道一样按着先进先出的顺序进行接收。
3.消息队列实现回射客户/服务器
首先看如下实现方案:
(1)该模型中,我们只用到了一个队列,它既可以保存从客户到服务器端的消息,也可以保存从服务器到客户端的消息。达到了队列的复用,并且可以用于双向通信。
(2)那么服务器端如何区分消息是发送给不同的客户端的呢?这里我们可以使用类型来进行区分,给不同的客户端发送的消息是不同类型的消息。所以客户端接收的时候,只需接收对应类型的消息即可。
(3)那么又该用什么来表示这些客户端的类型呢?这里我们使用客户端进程PID来表示客户端的类型。
(4)还有一个问题,服务器端怎么获知客户端的进程PID(即客户端类型)呢?可以这么解决,所有客户端给服务器发送的消息的类型都置为1,并且其发送的数据要包括两项内容:一项为客户端进程PID,另一项则为需要发送的数据。
具体实现如下:
服务端:
1)首先创建一个众所周知的键——即消息队列1234,然后调用echo_srv;
2)echo_srv中,不断接受类型为1的消息:
n = msgrcv(msgid, &msg, MSGMAX, 1, 0);
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 42 43 44 45 46 47 48 49 50 51 52 53 |
#include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #define ERR_EXIT(m)\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) #define MSGMAX 8192 struct msgbuf{ long mtype; char mtext[MSGMAX]; }; void echo_srv(int msgid) { int n; struct msgbuf msg; memset(&msg, 0, sizeof(msg)); while(1){ if(( n = msgrcv(msgid, &msg, MSGMAX, 1, 0)) < 0) ERR_EXIT("msgrcv"); int pid; pid = *((int*)msg.mtext); fputs(msg.mtext+4, stdout); msg.mtype = pid; msgsnd(msgid, &msg, n, 0); memset(&msg, 0, sizeof(msg)); } } int main(int argc, char *argv[]) { int msgid; msgid = msgget(1234, IPC_CREAT | 0666); if(msgid == -1) ERR_EXIT("msgget"); echo_srv(msgid); return 0; } |
客户端:
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 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
#include<unistd.h> #include<sys/types.h> #include<sys/ipc.h> #include<sys/msg.h> #include<stdio.h> #include<stdlib.h> #include<errno.h> #include<string.h> #define MSGMAX 8192 #define ERR_EXIT(m)\ {\ perror(m);\ exit(EXIT_FAILURE);\ }while(0) struct msgbuf{ long mtype; char mtext[MSGMAX]; }; void echo_cli(int msgid) { int pid; pid = getpid(); struct msgbuf msg; memset(&msg, 0, sizeof(msg)); msg.mtype = 1; *((int *)msg.mtext) =pid; int n; msg.mtype =1; while(fgets(msg.mtext+4, MSGMAX, stdin) != NULL){ if(msgsnd(msgid, &msg, 4+strlen(msg.mtext+4), 0) < 0) ERR_EXIT("msgsnd"); memset(msg.mtext+4, 0, MSGMAX-4); if ((n = msgrcv(msgid, &msg, MSGMAX, pid, 0)) < 0) ERR_EXIT("msgrcv"); fputs(msg.mtext+4, stdout); memset(msg.mtext+4, 0, MSGMAX-4); } } int main(int argc, char *argv[]) { int msgid; msgid = msgget(1234, 0); if(msgid == -1) ERR_EXIT("msgget"); echo_cli(msgid); return 0; } |