思路:
类型区分: 服务器用1标识类型,客户端用自己的进程标识类型。对于服务器端来说,接收到一个消息结构体的类型如果为1,表示是客户请求,而mtex 字段的前4个字节存放着不同进程的pid ,后续字节才是真正的数据,服务器回射客户端时,将pid 作为类型,mtex 为实际数据,客户端只接收对应类型的数据,故可以区分不同客户端。
双向通信,既可以客户端到服务器端发送消息,也可以用于服务端到客户端发送消息,而管道只能单向通信。服务端如何区分消息是发送给不同的客户端,那就是用类型进行区分。给不同的客户端发送的消息是不同类型的消息,客户端接收对应类型的消息。类型用什么来标识不同的客户端呢?那就是用进程的PID号码。客户1的进程PID为1234,客户2的进程ID为9876,服务端发给客户端的消息就可以用这两者进行区分。服务器端首先要知道客户端的进程号码,当客户端往服务器端发送消息的时候,就需要指定进程ID号。 客户端发送到服务器端的消息类型总是等于1,并且发送的数据总是包含两部分,一项是pid,另一项是一行数据。服务端收到一个类型为1的用户请求之后,要对请求进行响应,这里我实现的是回射回来,这时候就可以往消息队列发送消息,类型就是不同的PID,即1234或者9876。相应的客户端在接收消息的时候只接收类型为自己PID的消息,从而达到了一个消息队列复用的目的。
服务器端代码
#include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdlib.h> #include <stdio.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]; }; //不停地从各个客户端接收类型为1的消息 void echo_srv(int msgid) { int n; //定义一条消息 struct msgbuf msg; memset(&msg,0,sizeof(msg)); while(1) { //接收的消息类型是1,并且以阻塞的方式接收 if((n=msgrcv(msgid,&msg,MSGMAX,1,0))<0) ERR_EXIT("msgsnd"); //一旦接收到消息,需要将数据部分解析出来 int pid; //把一个char数组前4个字节作为int输出 //前四个字节保存客户端进程号码 pid=*((int*)msg.mtext); fputs(msg.mtext+4,stdout); msg.mtype=pid; msgsnd(msgid,&msg,n,0); } } 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; }
客户端代码
#include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdlib.h> #include <stdio.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 n; int pid; pid=getpid(); struct msgbuf msg; memset(&msg,0,sizeof(msg)); //把一个char数组前4个字节作为int输出 *((int *)msg.mtext)=pid; //消息的类型 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("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); if(msgid==-1) ERR_EXIT("msgget"); echo_cli(msgid); return 0; }
缺陷:
可能存在死锁现象,当服务器端收到客户端的请求之后,要给客户端回射数据,此时服务器端处于往消息队列发送消息的状态,如果这时候很多客户端发起了很多的请求,将消息队列堵满,服务器端往消息队列发送消息的状态就阻塞了,而客户端还在等待消息的回射,则产生了死锁。
改进思路:
服务器端在回射的时候利用的是私有的队列,当一个客户端创建的时候同时创建一个私有队列,并且客户端往服务器端发送消息的时候,将私有队列的标识符传给服务器端,以便服务器端能往私有队列填充数据。服务端通过创建子进程为客户端服务。