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(從客戶端發送來的信息中獲取),然後每個客戶端各自獲取自己的消息。下圖展示瞭如何工作的:


此種多個客戶端和單個服務器使用同一個消息隊列,死鎖總是存在的,客戶端可以填滿消息隊列後,妨礙服務器的發送應答,這些客戶於是阻塞在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);
}
第二種方法就是使用併發服務器,多個客戶端通過同一個消息隊列給服務器發送消息,並且客戶端新建自己的消息隊列,服務器使用子進程來處理每個客戶端的請求,並且使用客戶端新建的消息隊列將信息發送到客戶端。通信模型如圖:

公共頭文件和上面的一樣。

客戶端給服務器發送需要打開的文件名,服務器讀取文件信息逐條發送給客戶端。

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);
}
發佈了101 篇原創文章 · 獲贊 26 · 訪問量 46萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章