在我的上一篇博文中,讲解了关于消息队列的msgsnd和msgrcv函数的使用,这里我们试着来实现一个回射客户/服务器。
基本框架如下:
对于客户端: 用进程的PID进行区分类型,发往服务器的类型(mtype)总是1,包含自己的PID,还有一行消息。
对于回射服务器端: 创建一个消息队列,指定键值是1234,服务器不停地接受类型是1的消息,解析出数据部分的pid(mtext的前四个字节)——回射即可。注意回射回来的时候就没有必要再加上pid了,mtext直接写数据就可以了。
可以用下面四句话概括:
1)server进程接收时, 指定msgtyp为0, 从队首不断接收消息
2)server进程发送时, 将mtype指定为接收到的client进程的pid
3)client进程发送的时候, mtype指定为自己进程的pid
4)client进程接收时, 需要将msgtyp指定为自己进程的pid, 只接收消息类型为自己pid的消息
- /* Server */
- #include<stdlib.h>
- #include<sys/ipc.h>
- #include<sys/msg.h>
- #include<sys/types.h>
- #include<unistd.h>
- #include<errno.h>
- #include<string.h>
- #define ERR_EXIT(m) \
- do { \
- perror(m); \
- exit(EXIT_FAILURE); \
- } while(0)
- #define MSGMAX 8192
- struct msgbuf
- {
- long mtype;
- char mtext[MSGMAX];
- };
- void echo_ser(int msgid)
- {
- struct msgbuf msg;
- memset(&msg, 0, sizeof(msg));
- int nrcv ;
- while (1)
- {
- if ((nrcv = msgrcv(msgid, &msg, sizeof(msg.mtext), 1, 0)) < 0); //指定接受优先级为1的(msgtyp)
- int pid = *((int *)msg.mtext);
- fputs(msg.mtext + 4, stdout);
- msg.mtype = pid;
- msgsnd(msgid, &msg, nrcv, 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_ser(msgid);
- return 0;
- }
- /* Client */
- #include<stdio.h>
- #include<stdlib.h>
- #include<sys/ipc.h>
- #include<sys/msg.h>
- #include<sys/types.h>
- #include<unistd.h>
- #include<errno.h>
- #include<string.h>
- #define ERR_EXIT(m) \
- do { \
- perror(m); \
- exit(EXIT_FAILURE); \
- } while(0)
- #define MSGMAX 8192
- struct msgbuf
- {
- long mtype;
- char mtext[MSGMAX];
- };
- void echo_cli(int msgid)
- {
- int nrcv;
- int pid = getpid();
- struct msgbuf msg;
- memset(&msg, 0, sizeof(msg));
- msg.mtype = 1;
- *((int *)msg.mtext) = pid;
- while (fgets(msg.mtext + 4, MSGMAX, stdin) != NULL) //客户端输入信息
- {
- if (msgsnd(msgid, &msg, MSGMAX, IPC_NOWAIT) < 0)
- ERR_EXIT("msgsnd");
- memset(msg.mtext + 4, 0, MSGMAX - 4);
- if ((nrcv = msgrcv(msgid, &msg, MSGMAX, pid, 0)) < 0)
- ERR_EXIT("msgsnd");
- fputs(msg.mtext + 4, stdout);
- memset(msg.mtext + 4, 0, MSGMAX - 4);
- }
- }
- int main(int argc, char *argv[])
- {
- int msgid;
- msgid = msgget(1234, 0); //打开名为1234的消息队列
- if (msgid == -1)
- ERR_EXIT("msgget");
- echo_cli(msgid);
- return 0;
- }
但上述程序是存在死锁的风险的,当同时开了多个客户端,将队列写满了,此时服务器端想要写入就会阻塞,而因为客户端一旦发送了数据就阻塞等待服务器端回射类型为pid的消息,即队列的消息不会减少,此时就会形成死锁,互相等待。非阻塞方式发送也不行,因为队列已满,会发生EAGAIN错误。
对此问题我们的解决方法是采用多个消息队列:
即某个客户端先创建一个私有消息队列,然后将私有消息队列标识符和具体数据通过共享的队列发送给Server,服务器fork 出一个子进程,此时根据私有队列标识符就可以将数据回射到这个队列,这个客户端就可以从私有队列读取到回射的数据。