System V消息队列实现回射客户/服务器

思路:



类型区分: 服务器用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;
}

缺陷:

可能存在死锁现象,当服务器端收到客户端的请求之后,要给客户端回射数据,此时服务器端处于往消息队列发送消息的状态,如果这时候很多客户端发起了很多的请求,将消息队列堵满,服务器端往消息队列发送消息的状态就阻塞了,而客户端还在等待消息的回射,则产生了死锁。

改进思路:


服务器端在回射的时候利用的是私有的队列,当一个客户端创建的时候同时创建一个私有队列,并且客户端往服务器端发送消息的时候,将私有队列的标识符传给服务器端,以便服务器端能往私有队列填充数据。服务端通过创建子进程为客户端服务。

猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/80502755