進程間通信:消息隊列概念及代碼

前言

接下討論的IPC機制,它們最初由System V版本的Unix引入。由於這些機制都出現在同一個版本中並且有着相似的編程接口,所以它們被稱爲System V IPC機制。接下來的內容包括:

信號量:用於管理對資源的訪問。

共享內存:用於在程序之間高效地共享數據。

消息隊列:在程序之間傳遞數據。

操作系統中的同步和異步:https://blog.csdn.net/qq_38289815/article/details/81012826

進程間通信:管道和命名管道(FIFO)  https://blog.csdn.net/qq_38289815/article/details/104742682

進程間通信:信號量  https://blog.csdn.net/qq_38289815/article/details/104762940

進程間通信:共享內存  https://blog.csdn.net/qq_38289815/article/details/104776076

 

消息隊列

消息隊列與命名管道有許多相似之處,但少了在打開和關閉管道方面的複雜性。但使用消息隊列並未解決我們在使用命名管道時遇到的一些問題,如管道滿時的阻塞問題。消息隊列提供了一種在兩個不相關的進程之間傳遞數據的簡單且有效的方法。與命名管道相比,消息隊列的優勢在於,它獨立於發送和接收進程而存在,這消除了在同步命名管道的打開和關閉時可能產生的一些困難。

消息隊列提供了一種從一個進程向另一個進程發送一個數據塊的方法。 每個數據塊都被認爲含有一個類型,接收進程可以獨立地接收含有不同類型的數據結構。優點:我們可以通過發送消息來避免命名管道的同步和阻塞問題;可以用一些方法來提前查看緊急消息。缺點:消息隊列與命名管道一樣,每個數據塊都有一個最大長度的限制,系統中所有隊列所包含的全部數據塊的總長度也有一個上限。Linux系統用宏定義MSGMAX和MSGMNB來表示一條消息的最大長度和一個隊列的最大長度。

 

消息隊列函數的定義如下:

#include <sys/msg.h>

int msgctl(int msgid, int cmd, struct msgid_ds *buf);
int msgget(key_t key, int msgflg);
int msgrcv(int msgid, void *msg_ptr, size_t msg_sz, long int msgtype, int msgflg);
int msgsnd(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);

 

msgget()函數

msgget函數用來創建和訪問一個消息隊列。它的原型爲:

int msgget(key_t, key, int msgflg);
成功時返回一個以key命名的消息隊列的標識符(非零整數),失敗時返回-1。

與其他的IPC機制一樣,程序必須提供一個鍵來命名某個特定的消息隊列。msgflg是一個權限標誌,表示消息隊列的訪問權限,它與文件的訪問權限一樣。msgflg可以與IPC_CREAT做或操作,表示當key所命名的消息隊列不存在時創建一個消息隊列,如果key所命名的消息隊列存在時,IPC_CREAT標誌會被忽略,而只返回一個標識符。

 

msgsnd()函數

msgsnd函數用來把消息添加到消息隊列中。它的原型爲:

int msgsend(int msgid, const void *msg_ptr, size_t msg_sz, int msgflg);
成功時函數返回0,失敗時返回-1。如果調用成功,消息數據的一份副本將被放到消息隊列中。

msgid是由msgget函數返回的消息隊列標識符。

msg_ptr是一個指向準備發送消息的指針,但是消息的數據結構卻有一定的要求,指針msg_ptr所指向的消息結構一定要是以一個長整型成員變量開始的結構體(還有長度必須小於系統規定的上限),接收函數將用這個成員來確定消息的類型。所以消息結構要定義成這樣: 

struct my_message {
    long int message_type;
    /* The data you wish to transfer */
};

msg_sz是msg_ptr指向的消息的長度,注意是消息的長度,而不是整個結構體的長度,也就是說msg_sz是不包括長整型消息類型成員變量的長度。
msgflg 用於控制在消息隊列滿或隊列消息達到系統範圍的限制時將要發生的事情如果msgflg中設置IPC_NOWAIT狀態,函數將立刻返回,不發送消息並且返回值爲-1。如果msgflg中IPC_NOWAIT標誌被清除,則發送進程將掛起以等待隊列中騰出可用空間。

 

msgrcv()

msgrcv函數用來從一個消息隊列獲取消息,它的原型爲:

int msgrcv(int msgid, void *msg_ptr, size_t msg_st, long int msgtype, int msgflg);
調用成功時,該函數返回放到接收緩存區中的字節數,消息被複制到由msg_ptr指向的用戶分配的緩存區中,然後刪除消息隊列中的對應消息。失敗時返回-1。

msgid, msg_ptr, msg_st 的作用函數msgsnd函數的一樣。

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

msgflg 用於控制當隊列中沒有相應類型的消息可以接收時將發生的事情。如果msgflg中設置IPC_NOWAIT狀態,函數將立刻返回,返回值爲-1。如果msgflg中IPC_NOWAIT標誌被清除,則發送進程將掛起以等待隊列中騰出可用空間。

 

msgctl()

msgctl函數用來控制消息隊列,它與共享內存的shmctl函數相似,它的原型爲:

int msgctl(int msgid, int command, struct msgid_ds *buf);
成功時返回0,失敗時返回-1。

msgid是由msgget返回的消息隊列標識符。

command是將要採取的動作,它可以取3個值

        IPC_STAT:把msgid_ds結構中的數據設置爲消息隊列的當前關聯值,即用消息隊列的當前關聯值覆蓋msgid_ds的值。

        IPC_SET:如果進程有足夠的權限,就把消息列隊的當前關聯值設置爲msgid_ds結構中給出的值

        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;
};

 

使用消息隊列完成進程間通信

由於可以讓不相關的進程完成通信,所以我們在這裏將編寫兩個程序,msg1.c用於接收消息,msg2.c用於發送消息。允許兩個程序都可以創建消息隊列,但只有接收者在接收完最後一個消息之後刪除

#include <stdio.h>  //msg1.c
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include <errno.h>

struct msg_st
{
	long int my_msg_type;
	char text[BUFSIZ];
};

int main(int argc, char **argv)
{
	int msgid;
	struct msg_st data;
	long int msg_to_receive = 0;   

	// 建立消息隊列
	msgid = msgget((key_t)1234, 0666 | IPC_CREAT);
	if (msgid == -1)
	{
		fprintf(stderr, "msgget failed width error: %d\n", errno);
		exit(EXIT_FAILURE);
	}

	// 從隊列中獲取消息,直到遇到end消息爲止
	while (1)
	{
		if (msgrcv(msgid, (void *)&data, BUFSIZ, msg_to_receive, 0) == -1)
		{
			fprintf(stderr, "msgrcv failed width erro: %d", errno);
		}

		printf("You wrote: %s", data.text);

		// 遇到end結束
		if (strncmp(data.text, "end", 3) == 0)
		{
			break;
		}
	}

	// 刪除消息隊列
	if (msgctl(msgid, IPC_RMID, 0) == -1)
	{
		fprintf(stderr, "msgctl(IPC_RMID) failed\n");
	}

	exit(EXIT_SUCCESS);
}
#include <stdlib.h>  //msg2.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/msg.h>
#include <errno.h>

#define MAX_TEXT 512

struct msg_st
{
	long int my_msg_type;
	char text[MAX_TEXT];
};

int main(int argc, char **argv)
{
	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 error: %d\n", errno);
		exit(EXIT_FAILURE);
	}

	// 向消息隊裏中寫消息,直到寫入end
	while (1)
	{
		printf("Enter some text: \n");
		fgets(buffer, BUFSIZ, stdin);
		data.my_msg_type = 1; 
		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)
		{
			break;
		}

		sleep(1);
	}

	exit(EXIT_SUCCESS);
}

實驗解析

發送者程序通過msgget來創建一個消息隊列,然後用msgsnd向隊列中增加消息。接收者用msgget獲得消息隊列標識符,然後開始接收消息,直到接收到特殊的“end”爲止。然後它用msgctl來刪除消息隊列以完成清理工作。

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