linux网络编程之System V 消息队列(二):消息队列实现回射客户/服务器和 msgsnd、msgrcv 函数

一、msgsnd 和 msgrcv 函数

  #include <sys/types.h>   #include <sys/ipc.h>   #include <sys/msg.h>

功能:把一条消息添加到消息队列中 原型 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); 参数 msgid: 由msgget函数返回的消息队列标识码 msgp:是一个指针,指针指向准备发送的消息结构体 msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型 msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情 返回值:成功返回0;失败返回-1

msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误。为0表示阻塞等待 消息结构在两方面受到制约。首先,它的具体数据必须小于系统规定的上限值MSGMAX;其次,它必须以一个long int长整数开始,接收者函数将利用这个长整数确定消息的类型。

消息结构参考形式如下: struct msgbuf { long  mtype; char mtext[1]; };

The  mtext  field  is an array (or other structure) whose size is specified by msgsz, a nonnegative integer value.Messages of zero length (i.e., no mtext field) are permitted. 

即mtex 这块区域可以是个数组或者结构体,大小由参数msgsz 指明。

功能:是从一个消息队列接收消息 原型 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); 参数 msgid: 由msgget函数返回的消息队列标识码 msgp:是一个指针,指针指向准备接收的消息结构体 msgsz:是msgp指向的最大消息长度,这个长度不含保存消息类型的那个long int长整型 msgtype:它可以实现接收优先级的简单形式 msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事 返回值:成功返回实际放到接收缓冲区里去的字符个数,失败返回-1

msgtype=0返回队列第一条信息 msgtype>0返回队列第一条类型等于msgtype的消息  msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息,并且是满足条件的消息类型最小的消息 msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。 msgflg=MSG_NOERROR,消息大小超过msgsz时被截断 msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息。

二、消息队列实现回射客户/服务器

在前面的系列文章中,我们都是使用socket 套接字来实现回射客户/服务器程序,现在尝试使用消息队列来实现,主要就是利用上面介绍的两个函数msgsnd,msgrcv 。

对于服务器端来说,接收到一个消息结构体的类型如果为1,表示是客户请求,而mtex 字段的前4个字节存放着不同进程的pid ,后续字节才是真正的数据,服务器回射客户端时,将pid 作为类型,mtex 为实际数据,客户端只接收对应类型的数据,故可以区分不同客户端。

程序如下:

echoser.c

#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 = 0;
    while (1)
    {

        if ((nrcv = msgrcv(msgid, &msg, MSGMAX, 1, 0)) < 0);
        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;
}

echocli.c

#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, 4 + strlen(msg.mtext + 4), 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);
    if (msgid == -1)
        ERR_EXIT("msgget");

    echo_cli(msgid);

    return 0;
}

程序逻辑不复杂,就不多说了,编译运行服务器端,再开两个客户端,可以看到正常回射输出。

但上述程序是存在死锁的风险的,当开了多个客户端,将队列写满了,此时服务器端想要写入就会阻塞,而因为客户端一旦发送了数据就阻塞等待服务器端回射类型为pid的消息,即队列的消息不会减少,此时就会形成死锁,即使服务器端是非阻塞地写入,此时会返回EAGAIN 的错误,程序逻辑来说我们也会使其不断地尝试去写入,而不是粗暴地将其退出进程,这样还是会死锁。

对此问题可以多开几个私有的队列进行服务,如下:

即某个客户端先创建一个私有消息队列,然后将私有消息队列标识符和具体数据发到共享的队列,服务器fork 出一个子进程,此时根据私有队列标识符就可以将数据回射到这个队列,这个客户端就可以从私有队列读取到回射的数据。

参考:

《UNP》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起

我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点。由于前面两章已经涵盖了依赖注入在管道构建过...

3037
来自专栏唐郑望的专栏

Go 语言之三驾马车

Go 语言的三个核心设计: interface 、goroutine 、 channel。

1.6K0
来自专栏木木玲

Netty 源码解析 ——— AdaptiveRecvByteBufAllocator

1882
来自专栏大数据和云计算技术

MongoDB系列8:MongoDB集合的增量更新

在关系型数据库中,经常会遇到这样的场景:用某张表或是多张表的关联产生的结果集,然后持续地更新另外一张表的数据,有时为了方便,只更新变化的数据,即增量更新。那么在...

943
来自专栏大数据架构

Java进阶(二)当我们说线程安全时,到底在说什么

这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)。

43813
来自专栏Golang语言社区

Go语言Goroutine与Channel内存模型

Go语言内存模型规定了在一个goroutine中一个变量的读取的情况下,确保能够观察到在其他另外goroutine中写入同样变量的值。也就是说,如果在多个gor...

33310
来自专栏郑剑的专栏

linux 内存管理初探

本文主要介绍 linux 内存组织结构和页面布局,内存碎片产生原因和优化算法,linux 内核几种内存管理的方法,内存使用场景以及内存使用的那些坑。

8115
来自专栏Golang语言社区

Go语言Goroutine与Channel内存模型

Go语言内存模型规定了在一个goroutine中一个变量的读取的情况下,确保能够观察到在其他另外goroutine中写入同样变量的值。也就是说,如果在多个gor...

4154
来自专栏大内老A

WCF技术剖析之二十三:服务实例(Service Instance)生命周期如何控制[上篇]

服务调用的目的体现在对某项服务功能的消费上,而功能的实现又定义在相应的服务类型中。不论WCF服务端框架处理服务调用请求的流程有多么复杂,最终都落实在服务实例的激...

2258
来自专栏java系列博客

单例模式的各种实现

1716

扫码关注云+社区