1. 程式人生 > >多程序程式設計之程序間通訊-管道和訊息佇列

多程序程式設計之程序間通訊-管道和訊息佇列

1.程序間通訊

Linux作為一種新興的作業系統,幾乎支援所有的Unix下常用的程序間通訊方法:管道、訊息佇列、共享記憶體、訊號量、套介面等等。

2.管道

管道是程序間通訊中最古老的方式,它包括無名管道(或者匿名管道)和有名管道兩種,前者用於父程序和子程序間的通訊,後者用於運行於同一臺機器上的任意兩個程序間的通訊。

2.1無名管道

2.1.1無名管道pipe

無名管道由pipe()函式建立:

 #include <unistd.h> 
 int pipe(int filedis[2]); 

引數filedis返回兩個檔案描述符:filedes[0]為讀而開啟,filedes[1]為寫而開啟。filedes[1]的輸出是filedes[0]的輸入。下面的例子示範瞭如何在父程序和子程序間實現通訊。
跨越fork呼叫的管道:
例子程式碼:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    int data_processed;
    int file_pipes[2];
    const char some_data[] = "123";
    char buffer[BUFSIZ + 1];
    pid_t fork_result;

    memset(buffer, '\0', sizeof(buffer));

    if
(pipe(file_pipes) == 0) { fork_result = fork(); if (fork_result == -1) { fprintf(stderr, "Fork failure"); exit(EXIT_FAILURE); } // We've made sure the fork worked, so if fork_result equals zero, we're in the child process. if (fork_result == 0
) { // sleep(1);//如果父程序在子程序之前退出,則可以在兩個輸出內容之間看到shell的提示符 data_processed = read(file_pipes[0], buffer, BUFSIZ); printf("Read %d bytes: %s\n", data_processed, buffer); exit(EXIT_SUCCESS); } // Otherwise, we must be the parent process. else { data_processed = write(file_pipes[1], some_data, strlen(some_data)); printf("Wrote %d bytes\n", data_processed);//不管sleep(1)還是sleep(10)多少,都是先執行父程序??? } } exit(EXIT_SUCCESS); }

執行結果:
這裡寫圖片描述

基於管道的程序間通訊:

int main()
{
    int mycount=0;
    ifstream m_InStream("subdata.txt");//讀取的任務列表
    int data_processed;
    int file_pipes[3][2];
    string some_data;

    pid_t fork_result[3];
    for (int i = 0; i < 3; ++i)
    {
        if (pipe(file_pipes[i]) == 0)
        {
            fork_result[i] = fork();    
            if (fork_result[i] == 0) 
            {
                close(file_pipes[i][1]);
                char buf[1024] = {0};
                while(true)
                {
                    int readsize = read(file_pipes[i][0], buf, sizeof(buf)+1);
                    buf[readsize]='\0';//注意這裡,可以存在字元之後有其他字元,可以嘗試下,註釋掉該語句的效果!!
                    // printf("%X,%X,%X\n",buf[readsize],buf[readsize+1],buf[readsize+2]);
                    printf("Read %d byte,data=%s\n",readsize, buf);
                    std::cout << flush;// fflush(stdout);
                    if (strncmp(buf, "exit", 4) == 0)
                    {
                        break;
                    }
                }
                exit(0);        
            }
            else if(fork_result[i] == -1)
            {
                fprintf(stderr, "Fork failure");
                exit(EXIT_FAILURE);
            }
        }
    }

    string oneline;
    int count=0;
    while(getline(m_InStream, oneline,'\n'))
    {
        some_data = oneline;
        char bufs[128] = {0};
        strncpy(bufs,some_data.c_str(),some_data.size());
        data_processed = write(file_pipes[count][1], bufs, strlen(bufs));
        printf("Wrote %d bytes,data=%s\n", data_processed,bufs);
        count++;
        if(count>=3)
        {
            count = count -3;           
        }
        sleep(1);
    }
    for(int i=0; i<3; ++i)
    {
        char bufs[128] = {0};
        snprintf(bufs, sizeof(bufs), "exit");
        write(file_pipes[i][1], bufs, strlen(bufs));
        sleep(1);
    }
    int status =0;
    int mpid =0;
    for(int i=0;i<3;i++)
    {
        mpid = wait(&status);
        printf("pid[%d] is exit with status[%d]\n",mpid,status);
    }
    return 0;
}

執行結果如下:
這裡寫圖片描述

2.1.2 popen和pclose

如果不用上述底層的pipe函式的話,可以採用較高階的popen和pclose來建立和關閉管道。
函式原型:
FILE *popen(const char * command ,const char *mode)
int pclose(FILE * stream)
引數commend是shell命令。
引數mode是一個字元指標,r或w,分別表示popen函式的返回值是一個讀開啟檔案指標,還是寫開啟檔案指標,失敗時返回值為NULL,並設定出錯變數errno。popen()會呼叫fork()產生子程序,然後從子程序中呼叫/bin/sh -c來執行引數command的指令。引數type可使用“r”代表讀取,呼叫程式利用popen函式返回的FILE*檔案流指標,就可以通過常用的stdio庫函式(如fread)來讀取被呼叫程式的輸出;“w”代表寫入,呼叫程式可以用fwrite呼叫向被呼叫程式傳送資料,而被呼叫程式可以在自己的標準輸入流上讀取這些資料。向管道寫資訊還是讀資訊取決該引數。
1)讀取外部程式的輸出(r模式):

/*popen函式
(1)建立管道
(2)呼叫fork函式建立子程序
(3)執行exec函式呼叫,呼叫/bin/sh -c 執行commend中的命令字串,返回標準I/O 檔案指標 
*/
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
int main( void )
{
    FILE *fp;
    char buffer[80];

    fp = popen("cat /etc/passwd","r");
    fgets(buffer,sizeof(buffer),fp);//這裡只打印出第一行
    printf("%s",buffer);
    pclose(fp);
    return 0; 
}

執行結果:
root:x:0:0:root:/root:/bin/bash
2)將輸出送往popen(w模式):

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    FILE *write_fp;
    char buffer[BUFSIZ + 1];

    sprintf(buffer, "Once upon a time, there was...\n");
    write_fp = popen("od -c", "w");
    if (write_fp != NULL) {
        fwrite(buffer, sizeof(char), strlen(buffer), write_fp);//往命令列傳送資料
        pclose(write_fp);
        exit(EXIT_SUCCESS);
    }
    exit(EXIT_FAILURE);
}

執行結果:
這裡寫圖片描述
該程式使用帶有引數“w”的popen啟動od -c命令,即可實現向該命令傳送資料。通過fwrite方式給od -c命令傳送了一個字串,該命令收到該字串並進行處理,最後把處理結果列印到自己的標準輸出上。
在命令列上的等效命令:
echo “Once upon a time, there was…”| od -c
3)傳遞更多資料:
上面展示的都是通過一次fread和fwrite來收發資料,當需要以塊方式傳遞不清楚輸出資料長度的情況呢?此時可以通過多個fread和fwrite將資料氛圍多個部分進行處理。
例子如下:

//liu.cpp
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int main()
{
    FILE *read_fp;
    char buffer[BUFSIZ + 1];
    int chars_read;

    memset(buffer, '\0', sizeof(buffer));
    read_fp = popen("ps ax", "r");
    if (read_fp != NULL) {
        chars_read = fread(buffer, sizeof(char), BUFSIZ/10, read_fp);
        while (chars_read > 0) {
            buffer[chars_read - 1] = '\0';
            printf("Reading %d:-\n %s\n", BUFSIZ/10, buffer);//注意這裡設定的bufsize
            chars_read = fread(buffer, sizeof(char), BUFSIZ/10, read_fp);
        }
        pclose(read_fp);
        exit(EXIT_SUCCESS);
    }
    exit(EXIT_FAILURE);
}

該程式會連續從檔案流中讀取資料,直到沒有更多的資料。注意,儘管ps命令的執行需要一些時間,Linux會安排程序排程,從而讓兩個程式在可以執行時繼續執行。如果讀程序liu沒有資料可讀,它會被掛起,直到有資料到達。如果寫程序ps產生的輸出超過可用緩衝區的長度,它就會被掛起,直到讀程序讀取其中的一些資料。
執行結果如下圖:
這裡寫圖片描述

2.2 有名管道(也叫做命名管道)

基於有名管道,我們可以實現不相關程序之間的資料交換,而不必有一個共同的祖先程序。有名管道是基於FIFO檔案來完成的,有名管道是一種特殊型別的檔案,它在系統檔案中以檔名的形式存在,行為和上述介紹的無名管道pipe類似。

2.2.1 建立有名管道

有名管道可由兩種方式建立:命令列方式mknod系統呼叫和函式mkfifo。下面的兩種途徑都在當前目錄下生成了一個名為myfifo的有名管道:
方式一:mkfifo(“my_fifo”,”rw”);
方式二:mknod my_fifo p ,my_fifo 是指檔名
在程式中可以通過以下兩個不同的函式進行呼叫:

#include <sys/types.h>  
#include <sys/stat.h>  
int mkfifo(const char *pathname, mode_t mode);  
int mknod(const char *pathname, mode_t mode | S_FIFO, (dev_t)0);  

生成了有名管道後,就可以使用一般的檔案I/O函式如open、close、read、write等來對它進行操作。
有名管道建立示例程式碼:

/*建立有名管道*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
    int res = mkfifo("tmp/my_fifo", 0777);
    if (res == 0)
        printf("FIFO created\n");
    exit(EXIT_SUCCESS);
}

2.2.2 訪問FIFO檔案

與通過pipe呼叫建立管道不同,FIFO是以命名檔案的形式存在,而不是開啟的檔案描述符,所以在對它進行讀寫操作之前必須先開啟它。FIFO也用open和close函式進行開啟和關閉。傳遞給open函式的是FIFO的路徑名,而不是一個一般下的檔案。
下面即是一個簡單的例子,假設我們已經建立了一個名為my_fifo的有名管道。
1)使用open開啟FIFO檔案

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);

開啟FIFO檔案和普通檔案的區別有兩點:
(1)是不能以O_RDWR模式開啟FIFO檔案進行讀寫操作。這樣做的行為是未定義的。之所以這樣限制,是因為我們通常使用FIFO只是為了單向傳遞資料,所以沒有必要使用這個模式。如果一個管道以讀/寫方式開啟,程序就會從這個管道讀回它自己的輸出。

如果確實需要在程式之間雙向傳遞資料,最好使用一對FIFO或管道,一個方向使用一個,或者採用先關閉再重新開啟FIFO的方法來明確地改變資料流的方向。

(2)是對標誌位的O_NONBLOCK選項的用法。
使用這個選項不僅改變open呼叫的處理方式,還會改變對這次open呼叫返回的檔案描述符進行的讀寫請求的處理方式。
O_RDONLY、O_WRONLY和O_NONBLOCK標誌共有四種合法的組合方式:
flags=O_RDONLY:open將會呼叫阻塞,除非有另外一個程序以的方式開啟同一個FIFO,否則一直等待,不會返回。

flags=O_RDONLY|O_NONBLOCK:即使此時沒有其他程序以的方式開啟FIFO,此時open也會成功並立即返回,此時FIFO被讀開啟,而不會返回錯誤。

flags=O_WRONLY:open將會呼叫阻塞,除非有另外一個程序以的方式開啟同一個FIFO,否則一直等待。

flags=O_WRONLY|O_NONBLOCK:該函式的呼叫總是立即返回,但如果此時沒有其他程序以的方式開啟,open呼叫將返回-1,並且此時FIFO沒有被開啟。如果缺少有一個程序以讀方式開啟FIFO檔案,那麼我們就可以通過它返回的檔案描述符對這個FIFO檔案進行寫操作。
我們可以通過傳遞不同引數來觀察FIFO的不同行為。

2)不帶O_NONBLOCK標誌的O_RDONLY和O_WRONLY
程式碼如下:
注意,open函式返回的檔案描述符一定是最小的未用的描述符數字。

// Let's start with the header files, a #define and the check that the correct number
// of command-line arguments have been supplied.

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "tmp/my_fifo"

int main(int argc, char *argv[])
{
    int res;
    int open_mode = 0;
    int i;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <some combination of\
               O_RDONLY O_WRONLY O_NONBLOCK>\n", *argv);
        exit(EXIT_FAILURE);
    }

// we now set the value of open_mode from those arguments.

    for(i = 1; i < argc; i++) {
        if (strncmp(*++argv, "O_RDONLY", 8) == 0)
             open_mode |= O_RDONLY;
        if (strncmp(*argv, "O_WRONLY", 8) == 0)
             open_mode |= O_WRONLY;
        if (strncmp(*argv, "O_NONBLOCK", 10) == 0)
             open_mode |= O_NONBLOCK;
     }

// We now check whether the FIFO exists and create it if necessary.
// Then the FIFO is opened and output given to that effect while the program
// catches forty winks. Last of all, the FIFO is closed.
//用access檢查FIFO檔案是否存在,如果不存在則進行建立
    if (access(FIFO_NAME, F_OK) == -1) {
        res = mkfifo(FIFO_NAME, 0777);
        if (res != 0) {
            fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }

    printf("Process %d opening FIFO\n", getpid());
    res = open(FIFO_NAME, open_mode);
    printf("Process %d result %d\n", getpid(), res);
    sleep(5);
    if (res != -1) (void)close(res);
    printf("Process %d finished\n", getpid());
    exit(EXIT_SUCCESS);
}

執行結果(將讀取程式放在後臺執行):
這裡寫圖片描述
上述有名管道先啟動了讀程序,並在open呼叫中等待,當第二個程序開啟FIFO檔案時,兩個程式繼續執行。注意,讀程序和寫程序在open呼叫處取得同步。

3)帶O_NONBLOCK標誌的O_RDONLY和不帶該標誌的O_WRONLY執行結果
讀程序執行open呼叫並立刻繼續執行(所以輸出了result 3的結果),即使此時沒有程序的存在。隨後,寫程序開始執行,它也在執行open呼叫後立刻繼續執行,但是這次是因為FIFO已經被讀程序開啟。
執行結果如下圖所示:
這裡寫圖片描述
如果./liu O_RDONLY O_NONBLOCK &執行之後,沒有立刻執行./liu O_WRONLY,則51983的程序可能直接接著輸出對應的finished,主要看下一個程序執行的時間是否在sleep了5秒之內。

4)對FIFO進行讀寫操作:
使用O_NONBLOCK模式會影響的對FIFO的read和write呼叫。
對一個空的、阻塞的FIFO(即沒有用O_NONBLOCK標誌開啟)的read呼叫將等待,直到有資料可以讀取時才繼續執行。與此相反,對一個空的、非阻塞的FIFO的read呼叫將立刻返回0位元組。
對一個滿的、阻塞的FIFO的write呼叫將等待,直到資料可以被寫入時才繼續執行。如果非阻塞的FIFO不能接收所有寫入的資料,它將按下面的規則執行:
(1)如果請求寫入的資料長度小於等於PIPE_BUF位元組,呼叫失敗,資料不能寫入。
(2)如果請求寫入的資料長度大於PIPE_BUF位元組,將寫入部分資料,返回實際寫入的位元組數,返回值也可能是0。

FIFO的長度是需要考慮的一個很重要的因素。系統對任一時刻在一個FIFO中可以存在的資料長度是有限制的,它由#define PIPE_BUF語句定義,通常可以在標頭檔案limits.h中找到它。在linux和許多其他類UNIX系統中,它的值通常是4096位元組,但在某些系統中它可能會小到512位元組。系統規定:在一個以O_WRONLY方式(即阻塞方式)開啟的FIFO中,如果寫入的資料小於等於PIPE_BUF,那麼或者寫入全部位元組,或者一個位元組都不寫入。

雖然,對只有一個FIFO寫程序和一個FIFO讀程序的簡單情況來說,這個限制並不是非常重要的,但只使用一個FIFO並允許多個不同的程式向一個FIFO**讀程序傳送請求的情況是很就常見的。如果幾個不同的程式嘗試同時向FIFO寫資料,能否保證來自不同程式的資料庫不相互交錯就非常關鍵了。也就是說,每個寫操作**都必須是”原子化”的。怎樣才能做到這一點呢?
如果能保證所有的寫請求是發往一個阻塞的FIFO的,並且每個請求的資料長度小於等於PIPE_BUF位元組,系統就可以確定資料塊不會交錯在一起。通常將每次通過FIFO傳遞的資料長度限制為PIPE_BUF位元組是個好辦法,除非只使用一個寫程序和讀程序.
使用FIFO實現程序間通訊例子:
生產者程式:

//liu.cpp
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF
#define TEN_MEG (1024 * 1024 * 10)

int main()
{
    int pipe_fd;
    int res;
    int open_mode = O_WRONLY;
    int bytes_sent = 0;
    char buffer[BUFFER_SIZE + 1];

    if (access(FIFO_NAME, F_OK) == -1) {
        res = mkfifo(FIFO_NAME, 0777);
        if (res != 0) {
            fprintf(stderr, "Could not create fifo %s\n", FIFO_NAME);
            exit(EXIT_FAILURE);
        }
    }

    printf("Process %d opening FIFO O_WRONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d result %d\n", getpid(), pipe_fd);

    if (pipe_fd != -1) {
        while(bytes_sent < TEN_MEG) {
            res = write(pipe_fd, buffer, BUFFER_SIZE);
            if (res == -1) {
                fprintf(stderr, "Write error on pipe\n");
                exit(EXIT_FAILURE);
            }
            bytes_sent += res;
        }
        (void)close(pipe_fd); 
    }
    else {
        exit(EXIT_FAILURE);        
    }

    printf("Process %d finished\n", getpid());
    exit(EXIT_SUCCESS);
}

第二個程式是消費者程式,它從FIFO讀取資料並丟棄它們。在執行這兩個程式的同時,用time命令對讀程序進行計時。
消費者程式:

//liu2.cpp
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#define FIFO_NAME "tmp/my_fifo"
#define BUFFER_SIZE PIPE_BUF

int main()
{
    int pipe_fd;
    int res;
    int open_mode = O_RDONLY;
    char buffer[BUFFER_SIZE + 1];
    int bytes_read = 0;

    memset(buffer, '\0', sizeof(buffer));

    printf("Process %d opening FIFO O_RDONLY\n", getpid());
    pipe_fd = open(FIFO_NAME, open_mode);
    printf("Process %d result %d\n", getpid(), pipe_fd);

    if (pipe_fd != -1) {
        do {
            res = read(pipe_fd, buffer, BUFFER_SIZE);
            bytes_read += res;
        } while (res > 0);
        (void)close(pipe_fd);
    }
    else {
        exit(EXIT_FAILURE);
    }

    printf("Process %d finished, %d bytes read\n", getpid(), bytes_read);
    exit(EXIT_SUCCESS);
}

執行結果如下所示:
這裡寫圖片描述
兩個程式使用的都是阻塞模式的FIFO,首先啟動liu程式(寫程序/生產者),它將阻塞以等待讀程序開啟這個FIFO。liu2(消費者)啟動以後,寫程序解除阻塞並開始向管道寫資料。同時,讀程序也開始從管道中讀取資料。

linux會安排好這兩個程序之間的排程,使它們在可以執行的時候執行,在不能執行的時候阻塞。因此,寫程序將在管道滿時阻塞,讀程序將在管道空時阻塞。
time命令的輸出顯示,讀程序只運行了不到0.01秒的時間,卻讀取了10MB的資料,這說明管道(至少在現在Linux系統中的實現)在程式之間傳遞資料是很有效率的。

3 訊息佇列

訊息佇列就是一個訊息的連結串列,可以把訊息看作一個記錄,具有特定的格式以及特定的優先順序。一個有寫許可權的程序按照一定的規則對訊息佇列進行資訊新增,對訊息佇列有讀許可權的程序則可以從訊息佇列中讀走訊息,從而實現程序間的通訊。。
程式碼版本1:
涉及函式:
1)建立新訊息佇列或者取得已經存在的訊息佇列
int msgget(key_t key, int msgflg);
引數:
key:可以認為是一個埠號,是用來命名某個特定的訊息佇列,進行訊息佇列標識。
msgflg:
IPC_CREAT值,若沒有該佇列,則建立一個並返回新識別符號;若已存在,則返回原識別符號。
IPC_EXCL值,若沒有該佇列,則返回-1;若已存在,則返回0。
2)向佇列讀/寫訊息
原型:
int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
引數:
msqid:訊息佇列的標識碼
msgp:指向訊息緩衝區的指標,此位置用來暫時儲存傳送和接收的訊息,是一個使用者可定義的通用結構,形態如下:

         struct msgstru 
         { long mtype; /* 訊息型別,必須 > 0 */ 
           char mtext[1024]; /* 訊息文字 */ 
         };

msgsz:訊息的大小。
msgtyp:從訊息佇列內讀取的訊息形態。如果值為零,則表示訊息佇列中的所有訊息都會被讀取。
msgflg:用以控制當佇列中沒有對應型別的訊息可以接收時的處理邏輯。對於msgsnd函式,msgflg控制著當前訊息佇列滿或訊息佇列到達系統範圍的限制時將要發生的事情。如果msgflg設定為IPC_NOWAIT,則在msgsnd()執行時若是訊息佇列已滿,則msgsnd()將不會阻塞,不會發送資訊,立即返回-1。如果執行的是msgrcv(),則在訊息佇列呈空時,不做等待馬上返回-1,並設定錯誤碼為ENOMSG。當msgflg為0時,msgsnd()及msgrcv()在佇列呈滿或呈空的情形時,採取阻塞等待的處理模式。此時對於傳送程序而已傳送程序將掛起以等待佇列中騰出可用的空間;對於接收程序而言,該程序將會掛起以等待一條相應型別的資訊到達。
3)設定訊息佇列屬性
原型:int msgctl ( int msgqid, int cmd, struct msqid_ds *buf );
引數:msgctl 系統呼叫對 msgqid 標識的訊息佇列執行 cmd 操作,系統定義了 3 種 cmd 操作: IPC_STAT , IPC_SET , IPC_RMID
IPC_STAT : 該命令用來獲取訊息佇列對應的 msqid_ds 資料結構,並將其儲存到 buf 指定的地址空間。
IPC_SET : 該命令用來設定訊息佇列的屬性,要設定的屬性儲存在buf中。
IPC_RMID : 從核心中刪除 msqid 標識的訊息佇列。
receive程式碼:

/*receive.cpp */
#include<iostream>
#include <fstream>
#include <string>
#include <memory.h>
#include<stdio.h>
#include<map>
#include<vector>
#include<algorithm>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h> 
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

using namespace std;
#define MSGKEY 1024
#define ChildNum 5
struct msgstru
{
    long msgtype;
    char msgtext[2048];
};

/*子程序,監聽訊息佇列*/
void childproc()
{
    struct msgstru msgs;
    int msgid,ret_value;
    char str[512];
    /* First, we set up the message queue. */
    // msgid = msgget((key_t)MSGKEY, 0666 | IPC_CREAT);//該鍵值則唯一對應一個訊息佇列
    while(1)
    {
        msgid = msgget(MSGKEY,IPC_EXCL );/*檢查訊息佇列是否存在 */
        if(msgid < 0){
            printf("msq not existed! errno=%d [%s]\n",errno,strerror(errno));
            exit(EXIT_FAILURE);
        }
        /*接收訊息佇列*/
        ret_value = msgrcv(msgid,&msgs,sizeof(struct msgstru),0,0);
        int len = strlen(msgs.msgtext);
        // std::cout<<len<<std::endl;
        msgs.msgtext[len] = '\0';
        // std::cout<<"pid="<<getpid()<<","<<msgs.msgtext<<std::endl;
        if(ret_value == -1)
        {
            fprintf(stderr, "msgrcv failed with error: %d\n", errno);//訊息佇列中的資訊被取完??
            exit(EXIT_FAILURE);//訊息佇列為空的時候,就跳出。也可以設計成,訊息佇列為空時,不跳出,而是等待。
        }
        else
        {
            printf("pid=%d,data=%s\n",getpid(),msgs.msgtext);           
        }
        if (strncmp(msgs.msgtext, "end", 3) == 0)
        {
            exit(EXIT_SUCCESS);//換成break的效果呢???是不一樣的啊
        }
        //因為在send的時候,只send了一個end,當該標誌資訊被讀取之後,其他的程序自然是讀取不到資訊的,

    }
    return;
}

int main()
{
    int i,cpid;

    /* create 5 child process */
    for (i=0;i<ChildNum;i++){
        cpid = fork();
        if (cpid < 0)
            printf("fork failed\n");
        else if (cpid ==0) /*child process*/
            childproc();
    }
    int status =0;
    int mpid =0;
    // std::cout<<"father pid="<<getpid()<<std::endl;
    for(int i=0;i<ChildNum;i++)
    {
        mpid = wait(&status);
        printf("pid[%d] is exit with status[%d]\n",mpid,status);
    }
    return 1;
}

send程式碼:

/*send.c*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>

using namespace std;
#define MSGKEY 1024

struct msgstru
{
   long msgtype;
   char msgtext[2048]; 
};

main()
{
    struct msgstru msgs;
    int msg_type;
    char str[256];
    int ret_value;
    int msqid;

    msqid=msgget(MSGKEY,IPC_EXCL );  /*檢查訊息佇列是否存在*/
    if(msqid < 0){
        msqid = msgget(MSGKEY,IPC_CREAT|0666);/*建立訊息佇列*/
        if(msqid <0){
            printf("failed to create msq | errno=%d [%s]\n",errno,strerror(errno));
            exit(-1);
        }
    } 
    ifstream m_InStream("subdata.txt");
    string oneline;
    int running=1;
    // while(running)//getline(m_InStream, oneline,'\n') 
    int linenun=0;
    while (running)
    {
        if(!getline(m_InStream, oneline,'\n'))
        {
            oneline = "end";
            running = 0;
        }
        linenun++;
        // printf("input message type[0=end process]:");
        // scanf("%d",&msg_type);
        // if (msg_type == 0)
            // break;
        // printf("input message to be sent:");
        // scanf ("%s",str);
        strncpy(str,oneline.c_str(),256);
        msgs.msgtype = linenun;//這裡只是順便記錄下,該欄位的值,可以寫
        strcpy(msgs.msgtext, str);
        /* 傳送訊息佇列 */
        std::cout<<"pid="<<getpid()<<","<<msgs.msgtext<<std::endl;
        ret_value = msgsnd(msqid,&msgs,sizeof(struct msgstru),0);//訊息佇列識別符號,準備發現資訊的指標,資訊的長度,控制標誌位
        sleep(1);
        if ( ret_value < 0 ) {
            printf("msgsnd() write msg failed,errno=%d[%s]\n",errno,strerror(errno));
            exit(-1);
        }
    }
    msgctl(msqid,IPC_RMID,0); //刪除訊息佇列
    //如果這行程式碼註釋掉的話,則receive程序不會在接收一次send發出的任務之後,就全部子程序全部退出,而是send傳送一次(即啟動一次send程式)退出一個程序,這裡啟動了5個程序,所以,需要啟動5次send程式,receive程式才會退出(每次退出一個程序)。
}

執行結果如下:
send的結果:
這裡寫圖片描述
receive的結果:
這裡寫圖片描述
對於receive之後的結果,由於在getline的時候,在最後一行之後,send程式中往訊息佇列中寫入的是一個end字元,而在receive方,只有其中一個程序獲取到end字元,然後,正常退出該程序,而其他的程序,則是由於訊息佇列中資訊為空,獲取訊息佇列失敗而退出的。所以,在結果圖中可以看到有4個程序的訊息佇列獲取結果失敗,43提示符,從而退出程序,256的提示符。
檢視訊息佇列的命令列:
檢視全部ipc物件資訊:
#ipcs -a
這裡寫圖片描述
檢視訊息佇列資訊
#ipcs -q
檢視共享記憶體資訊
#ipcs -m
檢視訊號量資訊
#ipcs -s
注意,由於設定的訊息佇列的最大佔用byte是有限制的,檢視命令如下:
# ipcs -l
這裡寫圖片描述
從上面的結果我們可以看出訊息佇列中,最多可以容納65536 bytes。超過的話,訊息是無法新增到指定的佇列的。
刪除IPC物件的ipcrm
ipcrm -[smq] ID 或者ipcrm -[SMQ] Key
-q -Q刪除訊息佇列資訊 例如ipcrm -q 98307
-m -M刪除共享記憶體資訊
-s -S刪除訊號量資訊
如果把兩個程式合併到一起,寫成一個程式呢?
至於共享記憶體,訊號量和套接字在下文再繼續介紹。

相關推薦

程序程式設計程序通訊-管道訊息佇列

1.程序間通訊 Linux作為一種新興的作業系統,幾乎支援所有的Unix下常用的程序間通訊方法:管道、訊息佇列、共享記憶體、訊號量、套介面等等。 2.管道 管道是程序間通訊中最古老的方式,它包括無名管道(或者匿名管道)和有名管道兩種,前者用於父程序和

程序通訊---管道訊息佇列

程序間通訊的目的:資料傳輸:一個程序需要將它的資料傳送給另一個程序資源共享:對個程序之間共享同樣的資源通知事件:一個程序需要向另一個或一組程序傳送訊息,通知它們發生了什麼事件程序控制:有些程序希望完全控制另一個程序的執行(如:Debug程序)程序間通訊的發展:管道:Syste

程序通訊——管道訊息佇列,共享記憶體

程序間通訊的本質是讓兩個不相干的程序看到同一份資源。這個資源是由作業系統提供的一個檔案。程序間通訊的目的:1.資料傳輸:一個程序需要將它 的資料傳送給另一個程序。2.資源共享:多個程序之間共享同樣的資源。3.通知事件:一個程序需要向另一個(組)程序傳送訊息,通知它們發生了

程序程式設計程序通訊-共享記憶體,訊號量套接字

1. 背景 本文將介紹程序通訊中的訊號量,共享記憶體和套接字方法。 2. 訊號量 2.1 訊號量的定義 為了防止出現因多個程式同時訪問一個共享資源而引發的一系列問題,我們需要一種方法,它可以通過生成並使用令牌來授權,在任一時刻只能有一個執行執行緒

程序程式設計程序通訊

  程序間通訊(Interprocess Communication, IPC),經典的IPC:管道、FIFO、訊息佇列、訊號量以及共享儲存和套接字。   一、管道   管道是UNIX系統IPC的最古老的形式,所有的UNIX系統都提供此種通訊機制。   1·、兩

程序通訊---管道訊息佇列、共享記憶體

程序通訊分為低階通訊和高階通訊。 低階通訊是指程序互斥與同步,包括訊號、訊號量、管程等。 高階通訊方式有管道、訊息佇列、共享記憶體以及網路通訊中的套接字。 匿名管道PIPE: 管道是連線兩個程序的檔案,

程序通訊方式總結——訊息佇列

        Linux/Unix系統IPC是各種程序間通訊方式的統稱,但是其中極少能在所有Linux/Unix系統實現中進行移植。隨著POSIX和Open Group(X/Open)標準化的推進

執行緒執行緒通訊waitnotify的使用

使用wait和notify方法實現執行緒之間的通訊,注意,這兩個方法是Object類的方法,也就是說Java為所有的物件都提供的這兩個方法。 1 wait和notify必須配合synchronized關鍵字使用。 2 wait方法釋放鎖,notify方法不釋

Linux程式設計學習筆記----程序通訊——管道

程序通訊概述 在Linux系統中,程序是一個獨立的資源管理單元,但是獨立而不孤立,他們需要之間的通訊,因此便需要一個程序間資料傳遞、非同步、同步的機制,這個機制顯然需要由OS來完成管理和維護。如下: 1、同一主機程序間資料互動機制:無名管道(PIPE),有名管道(FIF

Linux的程序程式設計-二-程序通訊訊息佇列

1.1         系統V訊息佇列 訊息佇列中的每個訊息都有如下的資料結構: struct msgbuf { long mtype;         // 訊息型別 char mtext[n];      // 訊息內容,n由使用者自己定義 }; 1.1.1      

併發程式設計程序,路複用,multiprocess模組

併發 1. 背景知識 2. 什麼是程序 3. 程序排程 4. 併發與並行 5 同步\非同步\阻塞\非阻塞(重點) 6.multiprocess模組 7.殭屍程序與孤兒程序1.背景知識一作業系統的作用: 1:隱藏醜陋複雜的硬體介面,提供良好的抽象介面 2:管

Linux講解 程序通訊 管道

  今天我們講解程序間的通訊,首先回顧一下程序的概念:程序是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。程式是指令、資料及其組織形式的描述,程

33-程序通訊——管道

1. 什麼是管道   管道是unix系統最古老的IPC通訊方式了,適合於有血緣關係的程序之間完成資料傳輸,比如父子程序,兄弟程序。    管道允許一個數據流向另一個程序,管道中的資料流向是單向的。這樣程序可以通過檔案描述符1連線到管道寫入端,另一個程序通過檔案

程序通訊的方式——訊號、管道訊息佇列、共享記憶體

多程序: 首先,先來講一下fork之後,發生了什麼事情。 由fork建立的新程序被稱為子程序(child process)。該函式被呼叫一次,但返回兩次。兩次返回的區別是子程序的返回值是0,而父程序的返回值則是新程序(子程序)的程序 id。將子程序id返回給父程序的理由是

程序通訊-管道通訊

兩個程序的通訊,每個程序各自有不同的地址空間,每個地址空間的資料資訊是獨立的,任何一個程序的全域性變數在另一個程序中都看不到。例如,父程序中有一個全域性變數a = 0;在子程序中改變a的值不會影響父程序中a值的結果,因為子程序所有的資料資訊都拷貝(寫時拷貝)自父程序,兩個程

程序通訊方式執行緒同步機制總結

多程序之間通訊方式:           檔案對映:本地之間           共享記憶體:本地之間           匿名管道:本地之間           命名管道:跨伺服器           郵件槽:一對多的傳輸資料,通常通過網路向一臺Windo

Linux程序通訊(IPC)程式設計實踐(十二)Posix訊息佇列--基本API的使用

posix訊息佇列與system v訊息佇列的差別: (1)對posix訊息佇列的讀總是返回最高優先順序的最早訊息,對system v訊息佇列的讀則可以返回任意指定優先順序的訊息。 (2)當往一個空佇列放置一個訊息時,posix訊息佇列允許產生一個訊號或啟動一個執行緒,

程序通訊管道

一、常用的方式 (1) 管道 (2) System V (3) POSIX 二、目的 (1) 資料傳輸 (2) 資源共享 (3) 通知事件 (4) 程序控制 三、 本質 讓兩個不同的程序看到一份公共的資源 四、匿名管道 1. 簡單用法: 

程序通訊-管道(PIPE)有名管道(FIFO)

1.2有名管道的建立 該函式的第一個引數是一個普通的路勁名,也就是建立後FIFO的名字。第二個引數與開啟普通檔案的open()函式中的mode引數相同。如果mkfifo的一個引數是一個已經存在路勁名時,會返回EEXIST錯誤,所以一般典型的呼叫程式碼首先會檢查是否返回該錯誤,如果確實返回該錯誤,

Linux程序通訊--訊號,管道訊息佇列,訊號量,共享記憶體,socket

Linux 傳統的程序間通訊有很多,如各類管道、訊息佇列、記憶體共享、訊號量等等。但它們都無法介於核心態與使用者態使用,原因如表 通訊方法 無法介於核心態與使用者態的原因 管道(不包括命名管道) 侷限於父子程序間的通訊。 訊息佇列 在硬、軟中斷中無法無阻塞地接收資料。 訊號量 無法介於核