Linux進程間通信——消息隊列(一)
點擊上方藍字可直接關注!方便下次閱讀。如果對你有幫助,麻煩點個在看或點個贊,感謝~
小編心聲:昨天剛又完成一個996~
認真寫文章,希望可以幫到大家!
我學習一個東西,喜歡先從整體上了解框架,然後再瞭解所學習的東西是框架中的哪一細分部分。今天就聊一聊Linux系統進程之間的通信。
程序環境:ubuntu16.04 x_64 虛擬機
一、 站得高,望得遠
有三種IPC(進程間通訊 ) 我們稱作 XSI IPC ,即消息隊列、信號量和共享內存
1. XSI IPC
① POSIX 標準 Portable Operating System Interface( 可移植操作系統接口 )
② Single UNIX Specification 是 POSIX 的超集
③ X/Open System Interface(XSI IPC)
符合Single UNIX規範的系統的核心應用程序編程接口
有點兒蒙圈吧,正常正常~
個人理解:聽說過POSIX多線程程序設計吧,就是符合①的可移植操作系統接口的多線程設計,然後②又是①的超集,再然後③是符合②的 ...... 可能很多人就是因爲這些纔不想學一些東西吧,不過這些不清楚也沒多大關係
2. 進程間通信分類
進程間數據通信必須通過內核,因爲不同進程的用戶地址空間是不 同的,他們 各自的全局變量是不可見的。 所以他們通過在內核地址上開闢出一段空間來進行數 據傳輸。
進程間通信根據是否在同一臺主機上進行通信可分爲無名管道和有名管道(FIFO),消息隊列、信號量和共享內存這些都是隻能在同一臺主機上進行通信的
Socket和 Streams( 這個沒接觸過 ) 是可以在不同主機上進行進程通訊的。
3. 進程間通信之管道簡介
①無名管道
②有名管道
無名管道的限制:半雙工
兩個進程需要有公共祖先
有名管道舉例:當在終端連續使用兩個命令時,一條命令的輸出通過管道作爲另一條命令的輸入。
二、XSI IPC 的使用與注意事項
1. 標識符和Key
每個內核中的IPC結構 ( 消息隊列、信號量、共享內存 ) 都用一個非負整數的標 識 符來進行調用。 如,當使用消息隊列發送或接收消息隊列時,需要知道隊列標識符。
標識符是IPC內部的名稱,在外部通信時使用 Key 作爲標識符,每個 IPC 對象都與一個 Key 相關聯。
2. 新建Key的方法及注意事項
get函數的兩個參數分別是 Key 和一個整型 flag( 之後會介紹 get 函數 )
① Key 是 IPC_PRIVATE
② Key 當前未 與 特定的 IPC 結構相結合,並且 flag 中指定了 IPC_CREAT 位
③ ftok( 暫不具體介紹 )
當訪問已存在的隊列時,Key值必須與創建隊列時指定的 Key 值相同,且不應指定 IPC_CREAT
注意 : ①爲了訪問一個現存的隊列,決不能指定 IPC_PRIVATE 作爲 Key ,因爲它總是用於創建一個新隊列。
②如果希望新建一個消息隊列,而且要確保不是引用具有同一標識 符 的現有的消息隊列,需在 flag 中指 定 IPC_CREAT 和 IPC_EXCL 。 這樣,如果消息隊列已經存在則返回值會報錯。
3. 三種形式XSI IPC結構限制
我的系統默認限制如下:
4. 優點和缺點
XSI IPC的主要問題是: IPC 結構是在系統範圍內起作用的,沒有引用計數。這點可以類比 C++ 的智能指針。例如:如果進程創建 了一個消息隊列,並在隊列中放入了幾條消息,然後進程終止,但是該消息隊列及其內容並不會被刪除。
當以下情況出現時消息隊列纔不會繼續存在系統中:
①某個進程調用 msgrcv 或 msgctl 讀取或刪除消息隊列
②某個進程執行 ipcrm(1) 命令刪除息隊列
與管道相比,最後一個訪問管道的進程結束時,管道就徹底被刪除了(可與智能指針類比 ) 。
5. 程序通信例子
① Send:
#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 | IPC_EXCL);
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);
}
printf("You wrote: %s\n",data.text);
//輸入end結束輸入
if(strncmp(buffer, "end", 3) == 0)
running = 0;
sleep(1);
}
exit(EXIT_SUCCESS);
}
② Rcv:
#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);
msgid = msgget((key_t)1234, 0666);
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);
}
③程序運行效果
發送效果:
接收效果:
運行發送程序,根據提示輸入字符串,接收端會收到字符,輸入end消息隊列
終止。
三、小結
程序就是網上最流行的例子,做了微小的改動,下面想幾個問題:
①發送和接收可以對同一個 Key 多次使用不同的進程訪問麼?如果可以效果是什麼樣的,一對多還是多對一?多次訪問屬於正常操作麼?
②使用什麼方式讓發送端與接收端都知道 Key 值呢?
③下次具體介紹 api 時還有其他精彩的用法
參考書籍 《UNIX環境高級編程第三版》
閱讀一手資料,多思考,還是挺好的。