XSI IPC——System V消息队列

System V消息队列
1、key_t键和ftok函数
三种类型的System V IPC使用key_t值作为它们的名字,头文件<sys/types.h>把key_t这个数据类型定义为一个整数。
函数ftok把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键。
#include <sys/ipc.h>
key_t ftok(const char* pathname, int id);
该函数把从pathname导出的信息与id的低序8位组合成一个整数IPC键。
2、ipc_perm结构
内核给每一个IPC对象维护一个信息结构,ipc_perm:
struct ipc_perm{
    uid_t uid; /* owner's user id*/
gid_t gid; /* owner's group id*/
uid_t cuid;/* creator's user id*/
gid_t cgid;/* creator's group id*/
mode_t mode;
ulong_t seq; 
key_t key;
};
3、msgget函数
消息队列是消息的链接表,存放在内核中并由消息队列标识符标识。
msgget用于创建一个新队列或打开一个现存的队列,msgsnd将新消息添加到队列尾端,每个消息包含一个正长整型类型字段,一个非负
长度以及实际数据字节,所有这些都在将消息添加到队列时传送给msgsnd,msgrcv用于从队列中去消息。但并不一定以先进先出次序
取消息,也可以按照消息的类型字段去消息。消息结构msqid_ds:
struct msqid_ds{
    struct ipc_perm msg_perm;
msgqnum_t msg_qnum;
msglen_t msg_qbytes;
pid_t msg_lspid;
pid_t msg_lrpid;
time_t msg_stime;
time_t msg_ctime;
time_t msg_rtime
};
打开一个现存队列或创建一个新队列:msgget
#include<sys/msg.h>
int msgget(key_t key,int flag);
返回值是一个整数标识符,其他三个msg函数就用它来指代该队列。它是基于指定的key产生,而key既可以是ftok的返回值,也可以是
常量IPC_PRIVATE。

flag是读写权限值的组合,还可以与IPC_CREAT或IPC_CREAT|IPC_EXCL按位或。
IPC_EXCL如果该队列已经存在,再加上该字段会报错,具体EXCL的意思类似于文件IO中的open函数中O_EXCL参数的含义。

当创建一个新消息队列时msqid_ds结构如下成员被初始化:
msg_perm结构中的uid,cuid成员被设置为当前进程的有效用户ID,gid和cgid成员被设置为当前进程的有效组ID
flag中的读写权限存放在msg_perm.mode中
msg_qnum,msg_lspid,msg_lrpid,msg_stime,msg_rtime设为0
msg_ctime设置为当前时间
msg_qbytes设置成系统限制值。
4、msgsnd函数
使用msgget打开一个消息队列后,使用msgsnd往其上放置一个消息。
#include <sys/msg.h>
int msgsnd(int msqid,const void* ptr, size_t length,int flag);
其中msqid是由msgget返回的标识符。ptr是一个结构指针,该结构具有如下的模板,它定义在<sys/msg.h>中。
struct msgbuf{
    long mtype; //message type, must be > 0
char mtext[1];//message data,任何形式的数据都允许,不限于文本
};
flag参数可以为0,也可以是IPC_NOWAIT,IPC_NOWAIT标志使得msgsnd调用非阻塞。
5、msgrcv函数
使用msgrcv函数从某个消息队列中读取一个消息。
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void* ptr,size_t length, long type, int flag)
其中ptr参数指定所接收消息的存放位置;
length指定由ptr指向的缓冲区中数据部分的大小;
type指定希望从所给定的队列中读取什么样的信息;type=0,那就返回该队列中的第一个消息;type>0,那就返回其类型值为type的第一个消息;
如果type<0,那就返回其类型值小于或等于type参数的绝对值的消息中类型值最小的第一个消息。
6、msgctl函数
msgctl函数提供在一个消息队列上的各种控制操作。
#include <sys/msg.h>
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
msgctl函数提供三个命令:IPC_RMID,从系统中删除msqid指定的消息队列;IPC_SET,给所指定的消息队列设置其msqid_ds结构的以下四个成员
msg_perm.uid,msg_perm.gid,msg_perm.mode和msg_qbytes。它们的值由buf参数指向的结构中的相应成员;IPC_STAT,通过buf参数

给调用者返回对应所指定消息队列的当前msqid_ds结构。

写个客户-服务器例子,创建两个消息队列,一个队列用来从客户到服务器的消息,一个队列用于从服务器到客户的消息。主要功能是:客户向服务器发现一条消息,服务接收到并输出接收到的客户消息,然后服务器向客户发送一条消息,客户显示服务器发送的消息。

下面代码在测试中使用注释部分的代码会出问题,具体见代码中的注释。

头文件:

#ifndef SYSVMSG_H
#define SYSVMSG_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <error.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/types.h>

#define MSGQ_ID1 27
#define MSGQ_ID2 54
#define SENDMSG_TYPE 1
#define RECVMSG_TYPE 2
#define PATHNAME1 "/tmp/msg1"
#define PATHNAME2 "/tmp/msg2"
#define N 256
#define MSG_R 0400
#define MSG_W 0200
#define MODE IPC_CREAT | MSG_R | MSG_W | MSG_R>>3 | MSG_R>>6
struct Data{
    int a;
    int b;
    //char *buf;//这里也不要用指针
    char buf[N];
};

struct msgbuf{
    long mtype;
    struct Data mdata;//这里不要用指针
};
#endif
Server.c

#include "sysvmsg.h"

void server(int readfd,int writefd){
    struct msgbuf *recv,*send;
    recv = (struct msgbuf*)malloc(sizeof(struct msgbuf));
    send = (struct msgbuf*)malloc(sizeof(struct msgbuf));
    ssize_t n;
    puts("Waiting for client...");
    if((n = msgrcv(readfd,recv,sizeof(struct msgbuf),RECVMSG_TYPE,0)) == -1){
        perror("msgrcv error");
        exit(-1);
    }
    printf("Received message from client: Content = %s, a = %d, b = %d\n",recv->mdata.buf,recv->mdata.a,recv->mdata.b);
    //free(recv);/*这里如果不注释会出错,具体不知道,可能原因是在Client.c中free了,测试环境是centos6*/
    
    puts("===========================================");
    printf("Enter message content: ");
    scanf("%s",send->mdata.buf);
    printf("Enter two number: ");
    scanf("%d%d",&send->mdata.a,&send->mdata.b);
    send->mtype = SENDMSG_TYPE;
    puts("Sending message to client...");
    if(msgsnd(writefd,send,sizeof(struct msgbuf),0) == -1){
        perror("msgsnd error");
        exit(-1);
    }
    //free(send);//同上
}

int main(){
    int readfd,writefd;
    //PATHNAME1: Client send message to Server, Server receive message from Client
    if((readfd = msgget(ftok(PATHNAME1,MSGQ_ID1),MODE)) == -1){
        perror("msgget error");
        exit(-1);
    }
    //PAHTNAME2: Server send message to Client, Client receive message from Server
    if((writefd = msgget(ftok(PATHNAME2,MSGQ_ID2),MODE)) == -1){
        perror("msgget error");
        exit(-1);
    }
    server(readfd,writefd);
}
client.c

#include "sysvmsg.h"

void client(int readfd,int writefd){
    struct msgbuf *recv,*send;
    recv = (struct msgbuf*)malloc(sizeof(struct msgbuf));
    send = (struct msgbuf*)malloc(sizeof(struct msgbuf));
    ssize_t n;
    printf("Enter message content: ");
    scanf("%s",send->mdata.buf);
    puts(send->mdata.buf);
    printf("Enter two number: ");
    scanf("%d %d",&send->mdata.a,&send->mdata.b);
    printf("%d\n",&send->mdata.a);
    send->mtype = RECVMSG_TYPE;
    puts("Sending message to Server");
    if(msgsnd(writefd,send,sizeof(struct msgbuf),0) == -1){
         perror("msgsnd error");
         exit(-1);
    }
    free(send);
    puts("========================================");
    if((n = msgrcv(readfd,recv,sizeof(struct msgbuf),SENDMSG_TYPE,0)) == -1){
        perror("msgrcv error");
        exit(-1);
    }
    printf("Received message from Server: Conten = %s, a = %d, b = %d\n",recv->mdata.buf,recv->mdata.a,recv->mdata.b);
    free(recv);
}

int main(){
    int readfd,writefd;
    
    if((readfd = msgget(ftok(PATHNAME2,MSGQ_ID2),0)) == -1){
        perror("msgget error");
        exit(-1);
    }
    if((writefd = msgget(ftok(PATHNAME1,MSGQ_ID1),0)) == -1){
        perror("msgget error");
        exit(-1);
    }
    client(readfd,writefd);
    msgctl(readfd,IPC_RMID,NULL);
    msgctl(writefd,IPC_RMID,NULL);
    return 0;
}

7、复用消息

消息队列中的消息结构可以由我们自由定义,具备较强的灵活性。通过消息结构可以共享一个队列,进行消息复用。


现在考虑一下单个服务器和多个客户端的情况,此种方式的工作原理:客户端将信息(包含客户端的进程ID)通过消息队列发送到客户端,信息的type统一设置为1,然后服务器读取该消息后,根据客户端的请求获取相应信息并且通过同一消息队列发送到客户端,此时信息的type设置为客户端的进程ID(从客户端发送来的信息中获取),然后每个客户端各自获取自己的消息。下图展示了如何工作的:

XSI IPC——System V消息队列_第1张图片

此种多个客户端和单个服务器使用同一个消息队列,死锁总是存在的,客户端可以填满消息队列后,妨碍服务器的发送应答,这些客户于是阻塞在msgsnd中,服务器同样如此。可检测这种死锁的方法之一是约定服务器对消息队列总是非阻塞写。

详细例子(客户端给服务器发送需要打开的文件名,服务器读取文件信息逐条发送给客户端):

公共头文件:

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

#define MSG_R 0400
#define MSG_W 0200
#define MSG_X 0100
#define MSG_MODE MSG_R | MSG_R >> 3 | MSG_W | MSG_R >> 6 | IPC_CREAT
#define PATHNAME "/tmp/msg"
#define ID 3456
#define N 1024

struct msgbuf{
    int mlen;//发送消息的长度
    long mtype;
    char mdata[N];
};
Server.c
#include "msghead.h"

const long client_type = 1;

void server(int readfd,int writefd){
    struct msgbuf msg;
    ssize_t n;
    char* ptr;
    long pid;
    FILE *fp;
    while(1){
        if((n = msgrcv(readfd,&msg.mtype,sizeof(msg.mdata),client_type,0)) == 0){
            puts("Pathname missing");
            continue;
        }
        msg.mdata[n] = '\0';
        printf("Received message from client: %s\n",msg.mdata);
        if((ptr = strchr(msg.mdata,'#')) == NULL){
            printf("bad request: %s\n",msg.mdata);
            continue;
        }
        *ptr++ = 0;
        pid = atoi(msg.mdata);
        printf("pid = %d,filepath = %s,mdata = %s\n",pid,ptr,msg.mdata);
        msg.mtype = pid;
        if((fp = fopen(ptr,"r")) == NULL){
             puts("Open file failed. Send msg to client");
             int len = strlen(msg.mdata);
             snprintf(msg.mdata+len,sizeof(msg.mdata)-len,": can't open %s\n",strerror(errno));
             memmove(msg.mdata+len+1,ptr,strlen(ptr));
             printf("mdata = %s",msg.mdata); 
             if(msgsnd(writefd,&msg.mtype,strlen(msg.mdata),0) == -1){
                 perror("msgsnd error");
                 exit(-1);
             }
        }else{
            puts("Open file successfully.");
            setvbuf(fp,msg.mdata,_IOLBF,sizeof(msg.mdata));
            while(fgets(msg.mdata,sizeof(msg.mdata),fp) != NULL){
                fflush(fp);
                msgsnd(writefd,&msg.mtype,strlen(msg.mdata),0);
            }
            fclose(fp);
        }
        puts("send completed.");
        if(msgsnd(writefd,&msg,0,0) == -1){
            perror("msgsnd error");
            exit(-1);
        }
    }
}

int main(){
    int msqid;
    if((msqid = msgget(ftok(PATHNAME,ID),MSG_MODE)) == -1){
        perror("msgget error");
        exit(-1);
    }
    server(msqid,msqid);
    exit(0);
}
第二种方法就是使用并发服务器,多个客户端通过同一个消息队列给服务器发送消息,并且客户端新建自己的消息队列,服务器使用子进程来处理每个客户端的请求,并且使用客户端新建的消息队列将信息发送到客户端。通信模型如图:
XSI IPC——System V消息队列_第2张图片

公共头文件和上面的一样。

客户端给服务器发送需要打开的文件名,服务器读取文件信息逐条发送给客户端。

Server.c

#include "msghead.h"

void sig_child(int signo){
     int stat;
     pid_t pid;
     while((pid = waitpid(-1,&stat,WNOHANG)) > 0);
     printf("Catch signal %d\n",signo);
     return;
}

void server(int readfd,int writefd){
    FILE *fp;
    ssize_t n;
    char* ptr;
    struct msgbuf msg;
    signal(SIGCHLD,sig_child);
    while(1){
        msg.mtype = 1;
        if((n = msgrcv(readfd,&msg.mtype,sizeof(msg.mdata),msg.mtype,0)) == 0){
            puts("Pathname missing");
            continue;
        }
        msg.mdata[n] = '\0';
        printf("Received message from client = %s\n",msg.mdata);
        if((ptr = strchr(msg.mdata,'#')) == NULL){
            puts("bad request");
            continue;
        }
        *ptr++ = 0;
        writefd = atoi(msg.mdata); 
        msg.mtype = writefd;
        printf("writefd = %d\n",writefd);
        if(fork() == 0){
            if((fp = fopen(ptr,"r")) == NULL){
                printf("Open file failed, send message to client\n");
                int len = strlen(msg.mdata);
                snprintf(msg.mdata+len,sizeof(msg.mdata)-len,": can't open, %s\n",strerror(errno));
                msg.mlen = strlen(msg.mdata);
                //memmove(msg.mdata,ptr,msg.mlen);
                printf("data = %s\n",msg.mdata);
                if(msgsnd(writefd,&msg.mtype,msg.mlen,0) == -1){
                    perror("msgsnd error");
                    exit(-1);
                }
            }else{
                printf("open file successfully.");
                while(fgets(msg.mdata,sizeof(msg.mdata),fp) != NULL){
                    msg.mlen = strlen(msg.mdata);
                    if(msgsnd(writefd,&msg.mtype,msg.mlen,0) == -1){
                        perror("msgsnd error");
                        exit(-1);
                    }
                }
                fclose(fp);
            }
            puts("send completed");
            msg.mlen = 0;
            if(msgsnd(writefd,&msg.mtype,msg.mlen,0) == -1){
                perror("msgsnd error");
                exit(-1);
            }
        }
    }
}

int main(){
    int readfd,writefd;
    if((readfd = msgget(ftok(PATHNAME,ID),MSG_MODE)) == -1){
        perror("msgget error");
        exit(-1);
    }
    server(readfd,writefd);
    exit(0);
}
Client.c
#include "msghead.h"

void client(int readfd,int writefd){
    struct msgbuf msg;
    snprintf(msg.mdata,sizeof(msg.mdata),"%d",readfd);//将客户端消息队列的msqid发送给服务器
    int len = strlen(msg.mdata);
    msg.mdata[len] = '#';
    fgets(&msg.mdata[len]+1,sizeof(msg.mdata)-len,stdin);//获取标准输入,直接append到msg.mdata
    msg.mlen = strlen(msg.mdata);
    if(msg.mdata[msg.mlen - 1] == '\n') msg.mlen --;
    msg.mtype = 1;
    printf("data = %s\n",msg.mdata);
    if(msgsnd(writefd,&msg.mtype,msg.mlen,0) == -1){
         perror("msgsnd error");
         exit(-1);
    }
    ssize_t n;
    msg.mtype = readfd;
    while((n = msgrcv(readfd,&msg.mtype,sizeof(msg.mdata),msg.mtype,0)) > 0){
        write(1,msg.mdata,n);
    }
    if(n == 0){
        puts("Read file from server is completed");
    }
}

int main(){
    int readfd,writefd;
    if((writefd = msgget(ftok(PATHNAME,ID),0)) == -1){//打开服务器创建的消息队列
        perror("msgget error");
        exit(-1);
    }
    if((readfd = msgget(IPC_PRIVATE,MSG_MODE)) == -1){//新建客户端自己独立的消息队列,使用IPC_PRIVATE
        perror("msgget error");
        exit(-1);
    }
    client(readfd,writefd);
    msgctl(readfd,IPC_RMID,NULL);
    exit(0);
}

你可能感兴趣的:(XSI IPC——System V消息队列)