Linux 消息隊列 【1】


摘錄自 《Linux網絡編程》宋敬彬,孫海濱著

Linux 消息隊列

消息隊列是內核地址空間中的內部鏈表,通過Linux內核在各個進程之間傳遞內容。消息順序地發送到消息隊列中,並以幾種不同的方式從隊列中獲取,每個消息隊列可以用IPC標識符唯一的進行標識。內核中的消息隊列是通過IPC的標識符來區別的,不同的消息隊列之間是相對獨立的。每個消息隊列中的消息,又構成一個獨立的鏈表。

  1. 消息緩衝區結構

    常用的結構是msgbuf結構。程序員可以以這個結構爲模板定義自己的消息結構。在頭文件<linux/msg.h>中,它的定義如下

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

    在結構msgbuf中有兩個成員:

    • mtype : 消息類型,以正數表示。區分消息,可以自定義
    • mtext : 消息數據

    消息數據的類型爲char, 長度爲1。但在構建自己的消息結構時,這個域不一定要爲char或者長度爲1.可以根據實際情況自行設定,這個域可以存放任意形式的數據,示例如下

    struct msgmbuf{
        long mtype;
        char mtext[10];
        long length;
    };
    

    上面定義的消息結構與系統模板定義的不一致,但mtype一致。消息在通過內核在進程間收發時,內核不對mtext域進行轉換,任意的消息都可以發送。具體的轉換工作是在應用程序之間進行的。但是消息的大小有一個內部的限制。在Linux中(如Ubuntu 16),它在/usr/include/linux/msg.h中的定義如下

    #define MSGMAX  8192   /* <= INT_MAX */   /* max size of message (bytes) */
    

    消息總大小不能超過8192個字節,其中包含mtype成員的大小,它的長度爲當前機器long類型的長度。

  2. 結構msgid_ds

    內核msgid_ds結構-IPC對象分爲3類,每一類都有一個內部數據結構,該數據結構是由內核維護的。對於消息隊列而言,它的內部數據結構爲msgid_ds。對於系統上創建的每個消息隊列,內核均爲其創建,存儲和維護該結構的一個實例。該結構在Linux/msg.h中定義如下

        /* Obsolete, used only for backwards compatibility and libc5 compiles */
    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 */
    };
    

  3. 結構ipc_perm

    內核將IPC對象的許可權限信息存放在ipc_perm類型的結構中。例如 mshid_ds結構體的成員msg_perm就是ipc_perm類型的,它的定義在 linux/ipc.h 中,如下所示

    /* Obsolete, used only for backwards compatibility and libc5 compiles */
    struct ipc_perm
    {
        __kernel_key_t	key; //函數msgget()使用的鍵值
        __kernel_uid_t	uid; //用戶UID
        __kernel_gid_t	gid; //用戶GID
        __kernel_uid_t	cuid; //建立者的UID
        __kernel_gid_t	cgid; //建立者的GID
        __kernel_mode_t	mode; //權限
        unsigned short	seq; //序列號
    };
    

消息隊列程序示例

/*******************************************************************************************
* Filename       : msg.c
* Description    :
* Created        : 2019-01-01 17:45:30
*******************************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <errno.h>
#include <time.h>

#define Equit() return -1

//輸出消息屬性
void msg_show_attr(int msg_id, struct msqid_ds *msg_info){
	int ret = -1;
	sleep(1);
	ret = msgctl(msg_id, IPC_STAT, msg_info);
	if(-1 == ret){
		printf("獲取消息失敗\n");
		return;
	}

	printf("\n");
	printf("消息中當前字節數: %d\n", msg_info->__msg_cbytes);
	printf("隊列中消息數: %d\n", msg_info->msg_qnum);
	printf("隊列中最大字節數: %d\n", msg_info->msg_qbytes);
	printf("最後發送消息的進程PID: %d\n", msg_info->msg_lspid);
	printf("最後接收消息的進程PID: %d\n", msg_info->msg_lrpid);
	printf("最後發送消息的時間: %s",ctime(&(msg_info->msg_stime)));
	printf("最後接收消息的時間: %s", ctime(&(msg_info->msg_rtime)));
	printf("最後變化時間: %s", ctime(&(msg_info->msg_ctime)));
	printf("消息UID: %d\n", msg_info->msg_perm.uid); //permission
	printf("消息GID: %d\n", msg_info->msg_perm.gid);
}

int main(void){
	int ret = -1;
	int msg_flags, msg_id;
	key_t key;
	
	//消息的緩衝區結構
	struct msgmbuf{
		long mtype;
		char mtext[13];
	};

	struct msqid_ds msg_info;
	struct msgmbuf msg_buf;

	int msg_sflags, msg_rflags; //發送和接收標誌
	char *msgpath = "/tmp/msg"; //消息key的產生路徑
	ret = mkdir(msgpath, S_IRWXU); //創建文件夾

	if(-1 == ret){
		if(errno != EEXIST){
			printf("error: %s\n", strerror(errno));
			return -1;
		}
	}

	key = ftok(msgpath, 'b'); //產生key
	if(-1 == key){
		printf("failed to create key\n");
		printf("error: %s\n", strerror(errno));
		return -1;
	}else{
		printf("key created\n");
	}

	//msg_flags = IPC_CREAT|IPC_EXCL; //消息標誌位
	msg_flags = IPC_CREAT; //消息標誌位
	msg_id = msgget(key, msg_flags|00666); //建立消息
	if(-1 == msg_id){
		printf("消息建立失敗, 原因: %s\n",strerror(errno));
		Equit();
	}

//	msg_show_attr(msg_id, &msg_info); //輸出消息屬性

	msg_sflags = IPC_NOWAIT; //非阻塞
	msg_buf.mtype = 10;
	memcpy(msg_buf.mtext, "測試消息", sizeof("測試消息"));
	printf("%s\n", msg_buf.mtext);

	ret = msgsnd(msg_id, &msg_buf, sizeof("測試消息"), msg_sflags);//發送消息

	if(-1 == ret){
		printf("發送消息失敗, error: %s\n",strerror(errno));
	}

	msg_show_attr(msg_id, &msg_info);

	msg_rflags = IPC_NOWAIT|MSG_NOERROR;
	ret = msgrcv(msg_id, &msg_buf, 10, 10, msg_rflags);
	if(-1 == ret){
		printf("接收消息失敗\n");
	}
	else{
		printf("接收消息成功, 長度: %d\n",ret);
	}

	msg_show_attr(msg_id, &msg_info);

	msg_info.msg_perm.uid = 8;
	msg_info.msg_perm.gid = 8;
	msg_info.msg_qbytes = 12345;

	ret = msgctl(msg_id, IPC_SET, &msg_info); //設置消息屬性
	if(-1 == ret){
		printf("設置消息屬性失敗\n");
		return 0;
	}

	msg_show_attr(msg_id, &msg_info);

	ret = msgctl(msg_id, IPC_RMID, NULL); //刪除消息隊列
	if(-1 == ret){
		printf("刪除消息失敗\n");
		return 0;
	}

	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章