思路:
类型区分: 服务器用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;
}
缺陷:
可能存在死锁现象,当服务器端收到客户端的请求之后,要给客户端回射数据,此时服务器端处于往消息队列发送消息的状态,如果这时候很多客户端发起了很多的请求,将消息队列堵满,服务器端往消息队列发送消息的状态就阻塞了,而客户端还在等待消息的回射,则产生了死锁。
改进思路:
服务器端在回射的时候利用的是私有的队列,当一个客户端创建的时候同时创建一个私有队列,并且客户端往服务器端发送消息的时候,将私有队列的标识符传给服务器端,以便服务器端能往私有队列填充数据。服务端通过创建子进程为客户端服务。