关于System V 消息队列介绍

关于SystemV消息队列
操作函数:

int msgctl(int msqid, int cmd, struct msqid_ds*buf);
操作选项:
    IPC_RMID  从系统中删除mqisd指定的消息队列.仍在队列上的消息都丢弃
    IPC_SET   给所指定的消息队列设置其mqsid_ds结构的几个成员: uid, gid, mode,msq_qbytes
    IPC_STAT  给调用者返回所指定的消息队列对应的msqid_ds结构

既然了解了Posix消息队列, 期间也谈到了两者之间一些差别, 那么这里就不再赘述了
下面贴上操作的实例:

//创建函数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <unistd.h>

#define MSG_MODE    S_IROTH | S_IWOTH

int main(int ac, char *av[])
{
    int c, flag, mqid;
    key_t key;

    if(ac < 2){
       fprintf(stderr, "Usage : <e> /path/to/file\n");
        exit(-1);
    }

    flag = MSG_MODE | IPC_CREAT;
    while((c = getopt(ac, av,"e")) != -1)
    {
        switch(c)
        {
        case 'e':
           flag |= IPC_EXCL;
           break;
        }
    }

    if(optind != ac-1)
        exit(-1);
    key = ftok(av[optind], 0);
    mqid = msgget(key, flag);
    if(mqid < 0){
       perror("msgget error");
        exit(-1);
    }
    return 0;
}


//消息发送函数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <unistd.h>


struct msgbuf{
    long mtype;
    char mtext[0];
};

int main(int ac, char *av[])
{
    int mqid;
    int type, len;
    struct msgbuf *buf;

    if(ac != 4){
       fprintf(stderr, "Usage : msgs </path/to/file> <bytes><type>");
        exit(-1);
    }

    len = atoi(av[2]);
    type = atoi(av[3]);

    mqid = msgget(ftok(av[1], 0),0);
    if(mqid < 0){
       perror("msgget error ");
        exit(-1);
    }

    buf =calloc(sizeof(long)+len, sizeof(char));
    buf->mtype = type;

    if(msgsnd(mqid, buf, len, 0)< 0){
        perror("msgsnderror");
        exit(-1);
    }
    return 0;
}


//消息接收
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <fcntl.h>
#include <unistd.h>

struct msgbuf{
    long mtype;
    char mtext[0];
};

int main(int ac, char *av[])
{
    int c, flag, mqid;
    long type;
    ssize_t n;
    struct msgbuf *buf;

    type = flag = 0;
    while((c=getopt(ac, av,"nt:")) != -1)
    {
        switch(c)
        {
        case'n':                   //添加非阻塞标志
           flag |= IPC_NOWAIT;
           break;
        case't':                   //指定要取出的消息的type
           type = atol(optarg);
           break;
        }
    }

    if(optind != ac-1)
        exit(-1);
    
    mqid =msgget(ftok(av[optind],0), 0);
    if(mqid < 0){
       perror("msgget error");
        exit(-1);
    }

    buf = (struct msgbuf*)malloc(8192+sizeof(long));
    n = msgrcv(mqid, buf,8192+sizeof(long), type, flag);
    printf("read %d bytes, type= %d\n", n, buf->mtype);
    return 0;
}



//队列删除
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h> 
#include <fcntl.h>
#include <unistd.h>

int main(int ac, char *av[])
{
    int mqid;
    if(ac != 2){
       fprintf(stderr, "Usage : msgrmid </path/to/file>");
        exit(-1);
    }

    mqid = msgget(ftok(av[1], 0),0);
    if(mqid < 0){
       perror("msgget error");
        exit(-1);
    }

    msgctl(mqid, IPC_RMID, NULL);
    return 0;
}
除了使用msgctl执行队列的删除操作, 我们还可以使用shell命令 ipcrm 来删除System V IPC
以上这几个函数都能正常执行
还可以注意的是, 可以看到以上函数都调用msgget得到该消息队列的msqid,但这不是必须的, 因为msqid可以通过shell命令ipcs 来得到
自然, ipcs 和 ipcrm 也适用于SystemV的信号量和共享内存区


示例1: 使用一个消息队列实现服务器

在关于FIFO的章节中, 已经谈到用FIFO实现一个服务器与多个本地客户端交互的模型, 当时使用了一个固定的服务器FIFO, 以及每个客户端自身携带一个FIFO. 在本章消息队列章节中, 我们如果使用消息队列实现此种模型, 只需要使用一个消息队列就足矣.

因为消息队列中的每个消息都有一个type, 且我们可以根据type随机取出其中的某个消息. 不像FIFO或是Posix消息队列一样. 我们就可以通过这一点来用一个消息队列实现服务器与客户端的交互

 

实现思路:

                                                       

给每个方向的消息使用不同的消息类型

类型为1的消息是从客户到服务器的消息.

所有其他消息的类型都为服务器到客户端的消息, 每个消息的类型皆对应于每个客户端的PID

这样一来, 服务器每次msgrcv类型为1的消息 ; 客户端每次msgrcv类型为自身PID的消息

 

 

示例2: 使用多个消息队列实现服务器

上面, 我们使用一个消息队列实现了服务器与客户端的通信, 现在, 我们让每个客户端通过IPC_PRIVATE生成拥有自己的消息队列.

 

实现思路:

                                                

既然现在每个客户都有了自己的消息队列, 那么我们就要服务器在每次处理完消息后打开客户端的消息队列来传输信息.

既然我们可以通过shell命令ipcs来获取System V IPC的msqid来对消息队列进行处理, 那么这里我们也只需要客户端将此msqid传过来, 服务器就可以不用使用ftok, msgget了, 直接发送就行

Msqid可以放在消息里, 然后在fork时父进程传入子进程处理

这里还使用fork并发实现了此服务器版本

具体实现略过

 

 

消息队列与select, poll
如果服务器不仅要处理网络中的请求, 还要处理IPC连接.因为网络的特性, 我们可以使用select和poll来处理,那么在处理IPC时能否使用select或poll呢?
答案是否定的, 因为System V IPC由各自的标识符而不是描述符标识
如果我们有这种需求的话, 就要用到管道+子进程了
之所以需要子进程, 先回忆Posix中这种情况是如何处理的? 当时并没有使用子进程, 而是直接信号+管道,但是System V不支持通知操作, 因此必须阻塞在空消息队列上等待消息的到来, 于是我们使用了子进程.
在一个消息准备好被处理时, msgrcv返回,子进程接着就从中读出消息, 进而将得到的消息通过管道发送给父进程, 父进程select返回从管道读处数据,很明显, 消息被传递了三次. 如果想要避免这种复杂的传输, 那么在子进程与父进程之间创建一段共享内存区也许是不错的选择, 管道则是用来通知

当然, 如果System V支持窥探消息, 例如recv, recvmsg支持的MSG_PEEK的能力, 这样子进程就能在消息到来后侦测到它,从而通过管道提醒父进程去读取, 但System V不支持这个特性

 

 

关于MSG_PEEK

另外, 关于MSG_PEEK这个属性, 下面做一个简单的介绍:
比如,server向client发送数据"String1\r\nString2",要求"\r\n"之前的数据String1在第一次recv中接收,剩下的recv调用读取String2部分的数据。因为tcp是stream协议,且String1数据不是定长的,不知到长度, 所以没有办法保证一次recv调用能准确读到String1结尾,除非一次读取一个字符。此时,recv的MSG_PEEK参数就发挥作用。

recv的原型是
ssize_t recv(int s, void *buf, size_t len, int flags); 
通常flags都设置为0,此时recv函数读取tcp buffer中的数据到buf中,并从tcp buffer中移除已读取的数据。把flags设置为MSG_PEEK,仅把tcp buffer中的数据读取到buf中,并不把已读取的数据从tcp buffer中移除,再次调用recv仍然可以读到刚才读到的数据。

针对上面的场景,recv(fd, buf,nbuf, MSG_PEEK) 查看数据,查看"\r\n"的位置pos,再recv(fd, buf, pos+2, 0) 读取(并移除)数据。
当然,真正的recv之前调用使用MSG_PEEK导致额外的一次函数调用。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值