在我的上一篇博文中,讲解了关于消息队列的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 出一个子进程,此时根据私有队列标识符就可以将数据回射到这个队列,这个客户端就可以从私有队列读取到回射的数据。