UNPv2:进程间通信(三) System V 消息队列

1概述

 System V消息队列使用消息队列标识符标识。具有足够特权(参看IPC权限)的任何进程都可以往一个给定队列放置一个消息,或从一个给定队列读出一个消息。

对于系统中的每个消息队列,内核维护一个定义在<sys/msg.h>头文件中的信息结构。

struct msqid_ds {

   struct ipc_perm msg_perm;

   struct msg *msg_first;     /* first message on queue,unused */

   struct msg *msg_last;      /* last message in queue,unused */

   __kernel_time_t msg_stime; /* last msgsnd time */

   __kernel_time_t msg_rtime; /* last msgrcv time */

   __kernel_time_t msg_ctime; /* last change time */

   unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */

   unsigned long msg_lqbytes; /* ditto */

   unsigned short msg_cbytes; /* current number of bytes on queue */

   unsigned short msg_qnum;   /* number of messages in queue */

   unsigned short msg_qbytes; /* max number of bytes on queue */

   __kernel_ipc_pid_t msg_lspid;  /* pid of last msgsnd */

   __kernel_ipc_pid_t msg_lrpid;  /* last receive pid */

};

 假设一个消息队列有三个消息,消息长度分别为1字节、2字节和3字节,消息类型分别为100200300,该消息队列中的消息链表可如图1所示。


2函数

头文件

#include <sys/msg.h>

函数

int msgget(key_t key, int oflag);

参数

key:键值,由ftok生成或IPC_PRIVATE

oflag:读写权限与打开或创建一个消息队列标志的或值

创建或打开:IPC_CREATIPC_CREAT | IPC_EXCL

读写权限:MSG_RMSG_W等等

返回值

成功返回非负标识符,出错返回-1

功能

创建一个新的消息队列或打开一个已存在的消息队列

说明

当创建一个新消息队列时,msqid_ds结构的如下成员被初始化:

1)msg_perm中的uidcuid被设置成当前进程的有效用户IDgidcgid被设置成当前进程的有效组ID

2)oflag中的读写权限存放在msg_perm.mode

3)msg_qnummsg_lspidmsg_lrpidmsg_stimemsg_rtime被设置为0

4)msg_ctime被设置为当前时间

5)msg_qbytes被设置成系统限制值


头文件

#include <sys/msg.h>

函数

int msgsnd(int msgqid, const void *ptr, size_t length, int flag);

参数

msgqid:由msgget消息队列标识符

ptr:自定义结构指针,包含消息类型和消息数据。

nbytes0,则无消息数据;若发送的最长消息是512字节,则可定义下列结构:

 struct msgbuf{

 long mtype;      /*消息类型,必须为正整数*/

 char mtext[256]; /*消息数据*/

};

length:消息数据字节大小,由自定义结构msgbuf可知该为256

flag标志位,可以为0,也可以为IPC_NOWAITMSG_EXCEPTMSG_NOERROR

对发送消息来说,比较有意义的flagIPC_NOWAIT。在消息队列满时,若指定IPC_NOWAIT则使得msgsnd立即出错返回EAGAIN;若没有指定,则进程阻塞直到下述情况出现为止:

 a)有空间可以容纳要发送的消息。

 b)从系统中删除了此队列。

 c)捕捉到一个信号,并从信号处理程序返回。

返回值

成功返回0,出错返回-1

功能

往消息队列添加一个消息


头文件

#include <sys/msg.h>

函数

ssize_t msgrcv(int msqid, void *ptr, size_t length, long type, int flag);

参数

msqidmsgget返回的消息队列标识符

ptr:自定义结构指针

length:消息数据字节大小,由自定义结构msgbuf可知该为256

type消息类型,type值非0用于以非先进先出次序读消息。

 type == 0 返回队列中的第一个消息

 type > 0  返回队列中消息类型为type的第一个消息

 type < 0  返回队列中消息类型值小于或等于type绝对值的消息。

flag标志位,可以为0,也可以为IPC_NOWAITMSG_EXCEPTMSG_NOERROR

返回值

成功返回读取缓冲区中数据的字节数,出错返回-1

功能

从消息队列中读取一个消息


头文件

#include <sys/msg.h>

函数

int msgctl(int msqid, int cmd, struct msqid_ds *buff);

参数

msqid:消息队列标识符

cmdmsgctl提供3个命令,

IPC_RMID删除消息队列

IPC_SET  设置msqid_ds结构的4个成员:msg_bytemsg_perm中的udigidmode

IPC_STAT 获取消息队列的msqid_ds结构

返回值

成功返回0,出错返回-1

功能

提供在一个消息队列上的各种控制操作


3应用

示例1:每个应用一个队列

示例2:每个客户一个队列

//头文件msg.h
#include <stdio.h>  
#include <stdlib.h>  
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>  
#include <sys/ipc.h>  
#include <sys/msg.h>  
#include <sys/stat.h>

#define PATHNAME "/tmp/msgserv"  
#define MSG_R 0400
#define MSG_W 0200

#define BUFSIZE 256  
#define MAXLINE 100
#define SVMSG_MODE (MSG_R | MSG_W | MSG_R>>3 | MSG_W>>6)
/*自定义消息结构*/
typedef struct msgbuf{  
    long mtype;          /*消息类型*/
    char mtext[BUFSIZE]; /*消息内容*/ 
}msgbuf_t;  

typedef struct clibuf{  
    int msgqid;              /*消息队列标识符*/
    char pathname[MAXLINE];  /*路径名*/ 
}clibuf_t;  
//客户端client.c
#include "msg.h"

int main(int argc, char *argv[])
{
    int ret;
    int readid;
    int writeid;
    key_t key;
    ssize_t n;
    msgbuf_t msg;
    clibuf_t cli;
    char pathname[MAXLINE];

    if (argc < 2)
    {
        printf("usage: ./a.out <pathname>\n");
        exit(1);
    }

    key = ftok(PATHNAME, 0);
    if (key == -1)
    {
        printf("ftok error..\n");
        exit(1);
    }

    /*打开一个已经存在的服务器消息队列*/
    writeid = msgget(key, 0);
    if (writeid == -1)
    {
        printf("msgget error: %s\n", strerror(errno));
        exit(1);
    }
    
    /*创建客户专属的私有消息队列*/
    readid = msgget(IPC_PRIVATE, IPC_CREAT |  SVMSG_MODE);
    if (readid == -1)
    {
        printf("msgget error:%s\n", strerror(errno));
        exit(1);
    }

    /*填充私有队列标识符和路径名*/
    cli.msgqid = readid;
    snprintf(pathname, sizeof(pathname), "%s", argv[1]);
    n = strlen(pathname);
    if (pathname[n-1] == '\n')/*去除标准输入的路径名的换行符*/
        n--;
    pathname[n] = '\0';
    snprintf(cli.pathname, n+1, "%s", pathname);

    /*向服务器发送标识符和路径名消息*/
    msg.mtype = 1;
    memcpy(msg.mtext, (char *)&cli, sizeof(cli));
    ret = msgsnd(writeid, &msg, BUFSIZE, IPC_NOWAIT);
    if (ret == -1)
    {
        msgctl(readid, IPC_RMID, NULL);
        printf("smsgsnd error: writeid=%d, ret=%d, %s\n", writeid, ret, strerror(errno));
        exit(1);
    }
    
    /*从服务器读出路径名内容并显示到显示器*/
    while ((n = msgrcv(readid, &msg, BUFSIZE, 2, IPC_NOWAIT)) > 0)/*容易出错的是将第三个参数设置为sizeof(msg)*/
    {
        write(STDOUT_FILENO, msg.mtext, n);
        memset(msg.mtext, 0x0, BUFSIZE);
    }
    
    /*删除客户专属的私有消息队列*/
    ret = msgctl(readid, IPC_RMID, NULL);
    if (ret == -1)
    {
        printf("client delete priv_msg error:%s..\n", strerror(errno));
        exit(1);
    }

    exit(0);
}
//服务器server.c
#include "msg.h"

static void sig_chld(int signo)
{
    int stat;
    pid_t pid;
    
    while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
        ;
    return ;
}

int main()
{
    int ret;
    int fd;
    int msgqid;
    int privqid;
    key_t key;
    ssize_t n;
    pid_t pid;
    FILE *fp;
    msgbuf_t msg;
    clibuf_t *cli;
    char pathname[MAXLINE];
    char buf[BUFSIZE];

    /*确保服务器消息文件存在*/
    fd = open(PATHNAME, O_RDONLY | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH);
    if (fd == -1)
    {
        printf("server open %s error\n", PATHNAME);
        exit(1);
    }
    close(fd);

    key = ftok(PATHNAME, 0);
    if (key == -1)
    {
        printf("server ftok error..\n");
        exit(1);
    }

    /*创建服务器消息队列*/
    msgqid = msgget(key, IPC_CREAT | SVMSG_MODE);
    if (msgqid == -1)
    {
        printf("msgget error..\n");
        exit(1);
    }

    if (signal(SIGCHLD, sig_chld) == SIG_ERR)
    {
        printf("signal error..\n");
        exit(1);
    }
    int i;
    /*并发处理每个客户的请求*/
    for (;;)
    {
        /*读取客户的私有消息队列标识符和路径名*/
        ret = msgrcv(msgqid, &msg, BUFSIZE, 1, 0);
        if (ret == -1)
        {
            printf("msgrcv priqid and pathname error: msgqid=%d, ret=%d, %s\n", msgqid, ret, strerror(errno));
            continue;
        }

        /*解析出客户私有消息队列标识符和路径名*/
        if (msg.mtype == 1)
        {
            cli = (clibuf_t *)msg.mtext;
            privqid = cli->msgqid;
            snprintf(pathname, sizeof(pathname), "%s", cli->pathname);
        }
        else
        {
            continue;
        }

        /*生成子进程处理客户的请求*/
        pid = fork();
        if (pid < 0)
        {
            printf("fork error\n");
        }
        else if (pid == 0)
        {
            /*打开路径名*/
            fd = open(pathname, O_RDONLY | O_CREAT, S_IRUSR | S_IRGRP | S_IROTH);
            if (fd == -1)
            {
                printf("open error: %s", strerror(errno));
                sprintf(msg.mtext, "open '%s' error: %s\n", pathname, strerror(errno));
                msgsnd(privqid, &msg, sizeof(msg), IPC_NOWAIT);
                exit(1);
            }
         
            /*读取路径名的内容并发送给客户*/
            while ((n = read(fd, (void *)buf, BUFSIZE)) > 0)
            {
                msg.mtype = 2;
                memset(msg.mtext,0x0, BUFSIZE);
                memcpy(msg.mtext, buf, n);
                ret = msgsnd(privqid, &msg, BUFSIZE, IPC_NOWAIT);/*容易出错的是将第三个参数设置为sizeof(msg)*/
                if (ret == -1)
                {
                    printf("msgsnd error: id=%d, ret=%d, %s\n", privqid, ret, strerror(errno));
                    break;
                }
            }
            exit(0);
        }
    }
    exit(0);
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值