【技術篇】linux進程間通訊--消息隊列

(一)什麼是消息隊列

       消息隊列提供了一個進程給另外一個進程發送數據塊的方法,每個數據塊都可以被認爲是由自己的類型的,接受者接受的數據塊可以有不同的類型;不像管道和命名管道那樣,必須以先進先出的方式接收數據。

       同管道類似的是,他有一個不足的地方就是每個消息的最大長度是有上限的(MSGMAX),每個仙子隊列的總的字節數(MSGMNB),系統上消息隊列的總數上限(MSGMNI),可以用cat/proc/sys/kernel/msgmax查看具體的數據。

(二)消息隊列的使用

        linux消息隊列的API都定義在sys/msg.h頭文件中,其中包括四個系統調用:msgget、msgsnd、msgrcv、msgctl;

    2.1msgget系統調用

         用來創建一個消息隊列,或者獲取一個已存在的消息隊列,定義如下:

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

【參數 key】:鍵值,用來標識一個全局唯一的消息隊列。

【參數 msgflg】:一個權限標誌,表示消息隊列的訪問權限,它與文件的訪問權限一樣。msgflg可以與IPC_CREAT做或操作,表示當key所命名的消息隊列不存在時創建一個消息隊列,如果key所命名的消息隊列存時,IPC_CREAT標誌會被忽略,而只返回一個標識符。

【函數返回值】成功返回一個以key命名的消息隊列的標識符(非零整數),失敗時返回-1.

        在shmget用於創建內存,與之關聯的內核數據結構shmid_ds將被創建並初始化,shmid_ds結構體的定義和初始化參照《linux高性能服務器編程》263頁瞭解。

    2.2msgsnd系統調用

        用來將一條信息添加到消息隊列中,定義如下:

#include <sys/msg.h>
int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

【參數 msgid】:是由msgget系統調用返回的消息隊列標識符。

【參數 msg_ptr】:指向一個準備發送消息的指針,消息必須被定義爲如下:

    struct my_message
    {
        long int message_type;  /* 消息類型,必須是一個正整數 */
        char mtext[512]; /* 消息數據 */       
    };

【參數 msg_sz】:是指消息的數據部分(mtext)的長度,這個長度可以爲0,代表沒有數據。

【參數 msgflg】:控制msgsnd的行爲。它通常僅支持IPC_NOWAIT標誌,即以非阻塞的方式發送消息。默認情況下,發送消息時如果消息隊列滿了,則msgsnd將會阻塞。若IPC_NOWAIT標誌被指定,則msgsnd將會立即返回並設置werrno爲EAGAIN.具體情況參照《linux高性能服務器編程》264頁。

【函數返回值】:如果調用成功,消息數據的一分副本將被放到消息隊列中,並返回0,失敗時返回-1並設置errno.成功後回修改內核數據結構msqid_ds的部分字段。

    2.3msgrcv系統調用

      用來從消息隊列中獲取消息,定義如下:

#include <sys/msg.h>
int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);

【參數 msgtype】:指定接受哪一種類型的消息。可以實現一種簡單的接收優先級。如果msgtype爲0,就獲取隊列中的第一個消息。如果它的值大於零,將獲取具有相同消息類型的第一個信息。如果它小於零,就獲取類型等於或小於msgtype的絕對值的第一個消息。

【參數 msgflg】:用於控制當隊列中沒有相應類型的消息可以接收時將發生的事情。

【函數返回值】:調用成功時,該函數返回放到接收緩存區中的字節數,消息被複制到由msg_ptr指向的用戶分配的緩存區中,然後刪除消息隊列中的對應消息。失敗時返回-1.

 參數msgid, msg_ptr, msg_st的作用和函數msgsnd的參數意義相同一。

   2.4msgctl系統調用

     用來控制消息隊列的某些屬性,定義如下:

#include <sys/msg.h>
int msgctl(int msgid, int command, struct msgid_ds *buf);

【參數 id】:同上。

【參數 command】:command是將要採取的動作,它可以取6個值,這裏列舉常用的3個:
    (1)IPC_STAT:把msgid_ds結構中的數據設置爲消息隊列的當前關聯值,即用消息隊列的當前關聯值覆蓋msgid_ds的值。
    (2)IPC_SET:如果進程有足夠的權限,就把消息列隊的當前關聯值設置爲msgid_ds結構中給出的值
    (3)IPC_RMID:刪除消息隊列

【參數 buf】:是指向msgid_ds結構的指針,它指向消息隊列模式和訪問權限的結構。msgid_ds結構至少包括以下成員:
    struct msgid_ds
    {
        uid_t shm_perm.uid;
        uid_t shm_perm.gid;
        mode_t shm_perm.mode;
    };

【函數返回值】:成功時返回0,失敗時返回-1.

(三)代碼演示

      消息隊列可以讓不相關的進程進行行通信,所以我們在這裏將會編寫兩個程序,msgreceive和msgsned來表示接收和發送信息。根據正常的情況,我們允許兩個程序都可以創建消息,但只有接收者在接收完最後一個消息之後,它才把它刪除。

接受數據源代碼:recv.c

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/msg.h>
 
struct msg_st
{
	long int msg_type;
	char text[BUFSIZ];
};
 
int main()
{
	int running = 1;
	int msgid = -1;
	struct msg_st data;
	long int msgtype = 0; //注意1
 
	//建立消息隊列
	msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
	if(msgid == -1)
	{
		fprintf(stderr, "msgget failed with error: %d\n", errno);
		exit(EXIT_FAILURE);
	}
	//從隊列中獲取消息,直到遇到end消息爲止
	while(running)
	{
		if(msgrcv(msgid, (void*)&data, BUFSIZ, msgtype, 0) == -1)
		{
			fprintf(stderr, "msgrcv failed with errno: %d\n", errno);
			exit(EXIT_FAILURE);
		}
		printf("You wrote: %s\n",data.text);
		//遇到end結束
		if(strncmp(data.text, "end", 3) == 0)
			running = 0;
	}
	//刪除消息隊列
	if(msgctl(msgid, IPC_RMID, 0) == -1)
	{
		fprintf(stderr, "msgctl(IPC_RMID) failed\n");
		exit(EXIT_FAILURE);
	}
	exit(EXIT_SUCCESS);
}

發送數據源代碼:write.c

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>
 
#define MAX_TEXT 512
struct msg_st
{
	long int msg_type;
	char text[MAX_TEXT];
};
 
int main()
{
	int running = 1;
	struct msg_st data;
	char buffer[BUFSIZ];
	int msgid = -1;
 
	//建立消息隊列
	msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
	if(msgid == -1)
	{
		fprintf(stderr, "msgget failed with error: %d\n", errno);
		exit(EXIT_FAILURE);
	}
 
	//向消息隊列中寫消息,直到寫入end
	while(running)
	{
		//輸入數據
		printf("Enter some text: ");
		fgets(buffer, BUFSIZ, stdin);
		data.msg_type = 1;    //注意2
		strcpy(data.text, buffer);
		//向隊列發送數據
		if(msgsnd(msgid, (void*)&data, MAX_TEXT, 0) == -1)
		{
			fprintf(stderr, "msgsnd failed\n");
			exit(EXIT_FAILURE);
		}
		//輸入end結束輸入
		if(strncmp(buffer, "end", 3) == 0)
			running = 0;
		sleep(1);
	}
	exit(EXIT_SUCCESS);
}

(四)消息隊列與命名管道的比較

       消息隊列跟命名管道有不少的相同之處,通過與命名管道一樣,消息隊列進行通信的進程可以是不相關的進程,同時它們都是通過發送和接收的方式來傳遞數據的。在命名管道中,發送數據用write,接收數據用read,則在消息隊列中,發送數據用msgsnd,接收數據用msgrcv。而且它們對每個數據都有一個最大長度的限制。

      與命名管道相比,消息隊列的優勢在於:

     1、消息隊列也可以獨立於發送和接收進程而存在,從而消除了在同步命名管道的打開和關閉時可能產生的困難。

     2、同時通過發送消息還可以避免命名管道的同步和阻塞問題,不需要由進程自己來提供同步方法。

     3、接收程序可以通過消息類型有選擇地接收數據,而不是像命名管道中那樣,只能默認地接收。

參考文章:

https://blog.csdn.net/wei_cheng18/article/details/79661495

https://blog.csdn.net/ljianhui/article/details/10287879

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