近正好有一些空餘時間,在這裡總結一下曾經使用過的Linux程序間通信的幾種方法,貼出來幫助有需要的人,也有助于自己總結經驗加深了解。上一次我們梳理了共享記憶體的相關知識,這一次梳理消息隊列。
(一)概念
在介紹消息隊列之前,我們先來簡單說一說System V IPC通信機制。
System V IPC機制最初是由AT&T System V.2版本的UNIX引入的。這些機制是專門用于IPC(Inter-Process Communication 程序間通信)的,它們在同一個版本中被應用,又有着相似的程式設計接口,是以它們通常被稱為System V IPC通信機制。
消息隊列是最後一個System V IPC機制。消息隊列的本質也是在不同的線程之間共享的一段記憶體。隻是它與共享記憶體不同,程式不能夠直接通過API讀取或者寫入這段共享記憶體。而它有着自己的特殊機制,就像一段隊列,發送者可以向消息隊列中加入一段消息,而負責接收的程式責可以從隊列中讀取一則消息。先被發送的消息将先被讀取,也不用程式開發者去考慮同步相關的事情。系統會幫你確定它。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICO0ETMxQDNzEjMxYDM4EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
(二)消息隊列的建立,發送,接收和銷毀
同樣于共享記憶體,System V提供了一套極其類似的API供開發者們操作消息隊列。雖然API非常類似,但是它内部的機制則差異很大,這使得消息隊列和共享記憶體的應用場景也有很大不同。我們先來介紹一下操作消息隊列的主要API。
我們可以使用msgget()函數來建立,或者是得到一個消息隊列:
int msgget( key_t key, int msgflg );
其中,
--key:是消息的列的名字,也是系統内消息隊列的唯一辨別。通路同一個消息隊列的不同程序需要使用同一個名字。
--msgflg:消息隊列的标志,包含9個比特标志位,其内容與建立檔案時的mode相同。有一個特殊的标志IPC_CREAT可以和權限标志以或的形式傳入。
成功是傳回一個整數辨別消息隊列的句柄,失敗是傳回-1。
我們可以使用msgsnd()函數來向一個消息隊列發送資訊:
int msgsnd( int msgid, const void* msg_ptr, size_t msg_sz, int msgflt );
其中,
--msgid:是線程内消息隊列的辨別,是msgget()的傳回值。
--msg_ptr:是消息結構的指針,它可以是使用者自定義的結構體,不過它必須以長整數開頭(即結構提的第一個變量必須是長整數),這個長整數表示消息的類型,這個長整數很重要,因為消息接收函數會根據它對消息進行篩選。
--msg_zs:是消息體的長度,注意它是不包括消息類型這個長整數的,是除其意外的大小。
--msgflt:控制消息隊列的标志,如果在标志中設定了IPC_NOWAIT,在出現諸如消息隊列滿了等情況時,函數會立刻傳回-1,如果不設定的話函數會挂起一段時間。
我們使用msgrcv()函數來從消息隊列中讀取一個消息:
int msgrcv( int msgid, void* msg_ptr, size_t msg_sz, long int msgtype, int msgflg );
其中,
--msgid:是線程内消息隊列的辨別,是msgget()的傳回值。
--msg_ptr:是消息結構的指針,它與msgsnd中的是一樣的。
--msg_zs:是消息體的長度,注意它是不包括消息類型這個長整數的,是除其意外的大小。
--msgtype:消息類型,就是消息開頭的那一段長整數,設定為0則會從消息隊列中讀取第一條消息,設定為>0的值會讀取該類型的第一條消息。如果設定為<0的值,将會讀取小于該值絕對值的所有類型的第一條消息。
--msgflt:控制消息隊列的标志,如果在标志中設定了IPC_NOWAIT,在出現諸如消息隊列滿了等情況時,函數會立刻傳回-1,如果不設定的話函數會挂起一段時間。
我們使用msgctg()函數來控制消息隊列,這一點與共享記憶體非常相似:
int msgctl( int msgid, int command, struct msqid_ds* buf );
其中,
--msg_id:是消息隊列的标示符,也即是msgget()的傳回值。
--command:是要采取的動作,它有三個有效值,如下所示:
值 | 說明 |
---|---|
IPC_STAT | 把buf結構中的值設定為共享記憶體的關聯值 |
IPC_SET | 如果擁有足夠的權限,将共享記憶體的值設定為buf中的值 |
IPC_RMID | 删除共享記憶體段 |
--buf:是一個msgid_ds結構的指針它可以設定消息隊列的關聯值。msgid_ds的結構如下所示:
struct msgid_ds {
uid_t msg_perm.uid;
uid_t msg_perm.gid;
mode_t msg_perm.mode;
}
(三)使用消息隊列通信的例子
現在我們就來嘗試寫兩個簡單例子來使用以下消息隊列,我們首先要寫一個頭檔案,定義我們的消息格式。因為這是個非常簡單的例子,是以我們的消息結構裡隻用一個長整型的消息類型(必須),兩外再加上一段字元串,它表示具體消息内容。我們把它儲存為msgdef.h:
#ifndef _MSGDEF_H_
#define _MSGDEF_H_
#define MSG_FD 1111 /* message id*/
#define MSG_TEXT_SIZE 24 /* message text max size definition */
/* Structure of general message */
typedef struct message_body
{
long int msg_type; /* message type */
char msg_text[MSG_TEXT_SIZE]; /* message text */
}MSG_BODY;
#endif /* _MSGDEF_H_ */
現在我們來完成消息接收者,它将從消息隊列讀取一個消息并且列印到螢幕上,建立一個新檔案msgrecv.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include "msgdef.h"
int main()
{
int result;
int msgfd;
MSG_BODY message;
/* Create a message list. */
msgfd = msgget( MSG_FD, IPC_CREAT | 0666 );
if( msgfd < 0 )
{
printf( "error:can't apply a message list.\n" );
return 1;
}
do
{
/* Get the first message from the message list and print it's text. */
msgrcv( msgfd, &message, MSG_TEXT_SIZE, 0, 0 );
printf( "receive: %s\n", message.msg_text );
}
while( memcmp( message.msg_text, "close", 5 ) != 0 );
/* Close the message list. */
msgctl( msgfd, IPC_RMID, 0 );
return 0;
}
然後我們來完成消息的發送者,它會讀取使用者的輸入并發送到消息隊列。我們把它命名為msgsend.c:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/msg.h>
#include "msgdef.h"
int main()
{
int result;
int msgfd;
MSG_BODY message;
/* Create a message list. */
msgfd = msgget( MSG_FD, IPC_CREAT | 0666 );
if( msgfd < 0 )
{
printf( "error:can't apply a message list.\n" );
return 1;
}
do
{
/* Send the input of users to receiver. */
printf( "Please input something:" );
scanf( "%s", message.msg_text );
msgsnd( msgfd, &message, MSG_TEXT_SIZE, 0 );
}
while( memcmp( message.msg_text, "close", 5 ) != 0 );
return 0;
}
現在我們來編譯一下這兩個程式:
[email protected]:/home/root/workspace/msgq/system-v# gcc msgrecv.c -o msgrecv
[email protected]:/home/root/workspace/msgq/system-v# gcc msgsend.c -o msgsend
嘗試啟動接收者:
[email protected]:/home/root/workspace/msgq/system-v# ./msgrecv
現在我們另外啟動一個終端,嘗試啟動發送者,并輸入一下東西:
[email protected]:/home/root/workspace/msgq/linux# ./msgsend
Please input something:abc
Please input something:hellow
Please input something:fuck
Please input something:ono
Please input something:close
[email protected]:/home/root/workspace/msgq/linux#
然後我們看一下接收者那邊的情況:
[email protected]:/home/root/workspace/msgq/system-v# ./msgrecv
receive: abc
receive: hellow
receive: fuck
receive: ono
receive: close
[email protected]:/home/root/workspace/msgq/system-v#
可以看到輸入的内容被通過消息隊列成功的傳遞了。