Linux进程间通信(IPC)编程实践 消息队列实现回射客户/服务器

基本框架如下:

对于客户端: 用进程的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 出一个子进程,此时根据私有队列标识符就可以将数据回射到这个队列,这个客户端就可以从私有队列读取到回射的数据。

猜你喜欢

转载自blog.csdn.net/weixin_40039738/article/details/81114533