1. 程式人生 > >【Linux】程序間通訊之訊息佇列

【Linux】程序間通訊之訊息佇列

在上一篇部落格裡,我們學習了程序間通訊的一種方式,那就是管道,今天我們繼續學習另一種方式訊息佇列

訊息佇列

一. 什麼是訊息佇列?

  訊息佇列是訊息的連結串列,存放在核心中並由訊息佇列識別符號表示。
  訊息佇列提供了一個從一個程序向另一個程序傳送資料塊的方法,每個資料塊都可以被認為是有一個型別,接受者接受的資料塊可以有不同的型別。
  但是同管道類似,它有一個不足就是每個訊息的最大長度是有上限的(MSGMAX),每個訊息佇列的總的位元組數(MSGMNB),系統上訊息佇列的總數上限(MSGMNI)。可以用cat /proc/sys/kernel/msgmax檢視具體的資料。
  核心為每個IPC物件維護了一個數據結構struct ipc_perm,用於標識訊息佇列,讓程序知道當前操作的是哪個訊息佇列。每一個msqid_ds表示一個訊息佇列,並通過msqid_ds.msg_first、msg_last維護一個先進先出的msg連結串列佇列,當傳送一個訊息到該訊息佇列時,把傳送的訊息構造成一個msg的結構物件,並新增到msqid_ds.msg_first、msg_last維護的連結串列佇列。在核心中的表示如下:
這裡寫圖片描述

二. 特點:

  • 生命週期隨核心,訊息佇列會一直存在,需要我們顯示的呼叫介面刪除或使用命令刪除
  • 訊息佇列可以雙向通訊
  • 克服了管道只能承載無格式位元組流的缺點

三. 訊息佇列函式

1.msgget

功能:建立和訪問一個訊息佇列
原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflag);

引數
key:某個訊息佇列的名字,用ftok()產生
msgflag:有兩個選項IPC_CREAT和IPC_EXCL,單獨使用IPC_CREAT,如果訊息佇列不存在則建立之,如果存在則開啟返回;單獨使用IPC_EXCL是沒有意義的;兩個同時使用,如果訊息佇列不存在則建立之,如果存在則出錯返回。
返回值

:成功返回一個非負整數,即訊息佇列的標識碼,失敗返回-1

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);

呼叫成功返回一個key值,用於建立訊息佇列,如果失敗,返回-1

2.msgctl

功能:訊息佇列的控制函式
原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int
cmd, struct msqid_ds *buf);

引數
msqid:由msgget函式返回的訊息佇列標識碼
cmd:有三個可選的值,在此我們使用IPC_RMID

  • IPC_STAT 把msqid_ds結構中的資料設定為訊息佇列的當前關聯值
  • IPC_SET 在程序有足夠許可權的前提下,把訊息佇列的當前關聯值設定為msqid_ds資料結構中給出的值
  • IPC_RMID 刪除訊息佇列

返回值
成功返回0,失敗返回-1

3.msgsnd

功能:把一條訊息新增到訊息佇列中
原型

#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:指標指向準備傳送的訊息
msgze:msgp指向的訊息的長度(不包括訊息型別的long int長整型)
msgflg:預設為0
返回值:成功返回0,失敗返回-1

訊息結構一方面必須小於系統規定的上限,另一方面必須以一個long int長整型開始,接受者以此來確定訊息的型別

struct msgbuf
{
     long mtye;
     char mtext[1];
};

4.msgrcv

功能:是從一個訊息佇列接受訊息
原型
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
引數:與msgsnd相同
返回值:成功返回實際放到接收緩衝區裡去的字元個數,失敗返回-1

此外,我們還需要學習兩個重要的命令
前面我們說過,訊息佇列需要手動刪除IPC資源
ipcs:顯示IPC資源
ipcrm:手動刪除IPC資源
這裡寫圖片描述

下面用程式碼模擬實現client與server之間的通訊
comm.h

#ifndef _COMM_H_
#define _COMM_H_

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/msg.h>

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

#define SERVER_TYPE 1
#define CLIENT_TYPE 2

int createMsgQueue();
int getMsgQueue();
int destoryMsgQueue(int msg_id);
int sendMsgQueue(int msg_id, int who, char* msg);
int recvMsgQueue(int msg_id, int recvType, char out[]);

#endif

comm.c

#include "comm.h"

static int commMsgQueue(int flags)
{
    key_t key = ftok("/tmp", 0x6666);
    if(key < 0)
    {
        perror("ftok");
        return -1;
    }

    int msg_id = msgget(key, flags);
    if(msg_id < 0)
    {
        perror("msgget");
    }
    return msg_id;
}

int createMsgQueue()
{
    return commMsgQueue(IPC_CREAT|IPC_EXCL|0666);
}

int getMsgQueue()
{
    return commMsgQueue(IPC_CREAT);
}

int destoryMsgQueue(int msg_id)
{
    if(msgctl(msg_id, IPC_RMID, NULL) < 0)
    {
        perror("msgctl");
        return -1;
    }
    return 0;
}

int sendMsgQueue(int msg_id, int who, char* msg)
{
    struct msgbuf buf;
    buf.mtype = who;
    strcpy(buf.mtext, msg);

    if(msgsnd(msg_id, (void*)&buf, sizeof(buf.mtext), 0) < 0)
    {
        perror("msgsnd");
        return -1;
    }
    return 0;
}

int recvMsgQueue(int msg_id, int recvType, char out[])
{
    struct msgbuf buf;
    int size=sizeof(buf.mtext);
    if(msgrcv(msg_id, (void*)&buf, size, recvType, 0) < 0)
    {
        perror("msgrcv");
        return -1;
    }

    strncpy(out, buf.mtext, size);
    out[size] = 0;
    return 0;
}

server.c

#include "comm.h"

int main()
{
    int msgid = createMsgQueue();

    char buf[1024] = {0};
    while(1)
    {
        recvMsgQueue(msgid, CLIENT_TYPE, buf);
        if(strcasecmp("quit", buf) == 0)
            break;
        printf("client# %s\n", buf);

        printf("Please enter# ");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf));
        if(s>0)
        {
            buf[s-1]=0;
            sendMsgQueue(msgid, SERVER_TYPE, buf);
            printf("send done, wait recv...\n");
        }
    }

    destoryMsgQueue(msgid);
    return 0;
}

client.c

#include "comm.h"

int main()
{
    int msgid = getMsgQueue();

    char buf[1024] = {0};
    while(1)
    {
        printf("Please Enter# ");
        fflush(stdout);
        ssize_t s = read(0, buf, sizeof(buf));
        if(s > 0)
        {
            buf[s-1]=0;
            sendMsgQueue(msgid, CLIENT_TYPE, buf);
            if(strcasecmp("quit", buf) == 0)
                break;
            printf("send done, wait recv...\n");
        }

        recvMsgQueue(msgid, SERVER_TYPE, buf);
        printf("server# %s\n", buf);
    }
    return 0;
}

這裡寫圖片描述
這裡寫圖片描述