linux 進程間通訊 學習筆記

參考html

進程間通訊(IPC,InterProcess Communication)是指在不一樣進程之間傳播或交換信息。編程

IPC的方式一般有管道(包括無名管道和命名管道)、消息隊列、信號量、共享存儲、Socket、Streams等。其中 Socket和Streams支持不一樣主機上的兩個進程IPC。c#

以Linux中的C語言編程爲例。數組

管道

管道,一般指無名管道,是 UNIX 系統IPC最古老的形式。安全

  • 特色

1.它是半雙工的(即數據只能在一個方向上流動),具備固定的讀端和寫端。服務器

2.它只能用於具備親緣關係的進程之間的通訊(也是父子進程或者兄弟進程之間)。網絡

3.它能夠當作是一種特殊的文件,對於它的讀寫也可使用普通的read、write 等函數。可是它不是普通的文件,並不屬於其餘任何文件系統,而且只存在於內存中。多線程

  • 原型
#include <unistd.h>
int pipe(int fd[2]);    // 返回值:若成功返回0,失敗返回-1

當一個管道創建時,它會建立兩個文件描述符:fd[0]爲讀而打開,fd[1]爲寫而打開。以下圖:函數

clipboard.png
要關閉管道只需將這兩個文件描述符關閉便可。測試

  • 例子

單個進程中的管道幾乎沒有任何用處。因此,一般調用 pipe 的進程接着調用 fork,這樣就建立了父進程與子進程之間的 IPC 通道。以下圖所示:

clipboard.png
若要數據流從父進程流向子進程,則關閉父進程的讀端(fd[0])與子進程的寫端(fd[1]);反之,則可使數據流從子進程流向父進程。

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


#define MAX_MSG_SIZE 1024

int main(int argc, char** argv)
{
    pid_t pid = -1;
    int pipefd[2] = {0};
   
    /**
     * 建立的一個pipe會有兩個文件描述符,pipefd[0]是讀取數據的,pipefd[1]是寫入數據的。
     */
    if (0 != pipe(pipefd))
    {
        printf("create pipe failed with errno = %d.\n", errno);
        return 0;
    }
    
    pid = fork();

    if (pid < 0)
    {
        printf("call fork failed.\n");
    }
    /**
     * 這是父進程,若是咱們要測試子進程發送數據給父進程,則父進程須要關閉寫入端(pipefd[1]),
     * 而子進程須要關閉讀取端(pipefd[0])
     */
    else if (pid > 0) // this is parent process
    {
        char msg[MAX_MSG_SIZE] = {0};
        close(pipefd[1]);
        read(pipefd[0], msg, MAX_MSG_SIZE);
        printf("we recved : %s.\n", msg);
    }
    else if (pid == 0)
    {
        char* wmsg = "hello verybody!!!";
        close(pipefd[0]);
        write(pipefd[1], wmsg, strlen(wmsg));
    }

    printf("fuck, game over! pid=%d\n", getpid());

    return 0;
}

執行結果

root@iZbp1anc6yju2dks3nw5j0Z:~/test/ipc# ./pipe
fuck, game over! pid=14921
we recved : hello verybody!!!.
fuck, game over! pid=14920

FIFO

FIFO,也稱爲命名管道,它是一種文件類型。

    • 特色
    1. FIFO能夠在無關的進程之間交換數據,與無名管道不一樣。
    2. FIFO有路徑名與之相關聯,它以一種特殊設備文件形式存在於文件系統中。
    • 原型
    #include <sys/stat.h>
    // 返回值:成功返回0,出錯返回-1
    int mkfifo(const char *pathname, mode_t mode);

    其中的 mode 參數與open函數中的 mode 相同。一旦建立了一個 FIFO,就能夠用通常的文件I/O函數操做它。

    當 open 一個FIFO時,是否設置非阻塞標誌(O_NONBLOCK)的區別:

    • 若沒有指定O_NONBLOCK(默認),只讀 open 要阻塞到某個其餘進程爲寫而打開此 FIFO。相似的,只寫 open 要阻塞到某個其餘進程爲讀而打開它。
    • 若指定了O_NONBLOCK,則只讀 open 當即返回。而只寫 open 將出錯返回 -1 若是沒有進程已經爲讀而打開該 FIFO,其errno置ENXIO。

    • 例子

    FIFO的通訊方式相似於在進程中使用文件來傳輸數據,只不過FIFO類型文件同時具備管道的特性。在數據讀出時,FIFO管道中同時清除數據,而且「先進先出」。下面的例子演示了使用 FIFO 進行 IPC 的過程:
    fifo_read.c

    #include <sys/stat.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <string.h>
    
    #define FIFO_FILE_PATH  "/tmp/fifo_test"
    #define MAX_FIFO_MSG_SIZE  1024
    
    
    int main(int argc, char** argv)
    {
        if (-1 == access(FIFO_FILE_PATH, F_OK))
        {
            printf("fifo file is not exist, and try to create fifo file.\n");
        }
        
        int fd = open(FIFO_FILE_PATH, O_RDONLY);
        char buffer[MAX_FIFO_MSG_SIZE] = {0};
        int len = read(fd, buffer, MAX_FIFO_MSG_SIZE);
        if ( len <= 0 )
        {
            printf("read fifo file return nothing.\n");
            return -1;
        }
    
        printf("read msg result is : %s.\n", buffer);
    
        return 0;
    }

    fifo_write.c

    #include <sys/stat.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <errno.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <string.h>
    
    #define FIFO_FILE_PATH  "/tmp/fifo_test"
    
    
    int main(int argc, char** argv)
    {
    
        if (-1 == access(FIFO_FILE_PATH, F_OK))
        {
            printf("fifo file is not exist, and try to create fifo file.\n");
            if (0 != mkfifo(FIFO_FILE_PATH, 0777))
            {
                printf("create fifo file failed with error : %s\n", strerror(errno));
                return -1;
            }
        }
    
        /*
         *  以只寫方式打開FIFO文件,不能以讀寫方式打開FIFO,固然咱們也能夠設置爲O_WRONLY | O_NONBLOCK,那麼此時是
         *  以非阻塞的方式打開文件,對於非阻塞,咱們講網絡編程的時候會講到
         */
        int fd = open(FIFO_FILE_PATH, O_WRONLY);
        if (-1 == fd)
        {
            printf("open fifo file failed.\n");
            return -1;
        }
    
        char* msg = "sexy lady, what's your LINE number.\n";
        int len = strlen(msg);
        if( write(fd, msg, len) < len)
        {
            printf("only write ");
        }
    
        for(;;);
    
        return 0;
    }

    運行結果:

    root@iZbp1anc6yju2dks3nw5j0Z:~/test/ipc# ./fifo_write
    root@iZbp1anc6yju2dks3nw5j0Z:~/test/ipc# ./fifo_read
    read msg result is : sexy lady, what's your LINE number.

    上述例子能夠擴展成 客戶進程—服務器進程 通訊的實例,write_fifo的做用相似於客戶端,能夠打開多個客戶端向一個服務器發送請求信息,read_fifo相似於服務器,它適時監控着FIFO的讀端,當有數據時,讀出並進行處理,可是有一個關鍵的問題是,每個客戶端必須預先知道服務器提供的FIFO接口,下圖顯示了這種安排:

    clipboard.png

    消息隊列

    消息隊列,是消息的連接表,存放在內核中。一個消息隊列由一個標識符(即隊列ID)來標識。

    • 特色

      1.消息隊列是面向記錄的,其中的消息具備特定的格式以及特定的優先級。

      2.消息隊列獨立於發送與接收進程。進程終止時,消息隊列及其內容並不會被刪除。

      3.消息隊列能夠實現消息的隨機查詢,消息不必定要以先進先出的次序讀取,也能夠按消息的類型讀取。

    • 原型
    #include <sys/msg.h>
    // 建立或打開消息隊列:成功返回隊列ID,失敗返回-1
    int msgget(key_t key, int flag);
    // 添加消息:成功返回0,失敗返回-1
    int msgsnd(int msqid, const void *ptr, size_t size, int flag);
    // 讀取消息:成功返回消息數據的長度,失敗返回-1
    int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
    // 控制消息隊列:成功返回0,失敗返回-1
    int msgctl(int msqid, int cmd, struct msqid_ds *buf);

    在如下兩種狀況下,msgget將建立一個新的消息隊列:

    • 若是沒有與鍵值key相對應的消息隊列,而且flag中包含了IPC_CREAT標誌位。
    • key參數爲IPC_PRIVATE。

    函數msgrcv在讀取消息隊列時,type參數有下面幾種狀況:

    • type == 0,返回隊列中的第一個消息;
    • type > 0,返回隊列中消息類型爲 type 的第一個消息;
    • type < 0,返回隊列中消息類型值小於或等於 type 絕對值的消息,若是有多個,則取類型值最小的消息。

    能夠看出,type值非 0 時用於以非先進先出次序讀消息。也能夠把 type 看作優先級的權值。

    • 例子

    下面寫了一個簡單的使用消息隊列進行IPC的例子,服務端程序一直在等待特定類型的消息,當收到該類型的消息之後,發送另外一種特定類型的消息做爲反饋,客戶端讀取該反饋並打印出來。
    msg_server.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/msg.h>
    
    // 用於建立一個惟一的key
    #define MSG_FILE "/etc/passwd"
    
    // 消息結構
    struct msg_form {
        long mtype;
        char mtext[256];
    };
    
    int main()
    {
        int msqid;
        key_t key;
        struct msg_form msg;
        
        // 獲取key值
        if((key = ftok(MSG_FILE,'z')) < 0)
        {
            perror("ftok error");
            exit(1);
        }
    
        // 打印key值
        printf("Message Queue - Server key is: %d.\n", key);
    
        // 建立消息隊列
        if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
        {
            perror("msgget error");
            exit(1);
        }
    
        // 打印消息隊列ID及進程ID
        printf("My msqid is: %d.\n", msqid);
        printf("My pid is: %d.\n", getpid());
    
        // 循環讀取消息
        for(;;) 
        {
            msgrcv(msqid, &msg, 256, 888, 0);// 返回類型爲888的第一個消息
            printf("Server: receive msg.mtext is: %s.\n", msg.mtext);
            printf("Server: receive msg.mtype is: %d.\n", msg.mtype);
    
            msg.mtype = 999; // 客戶端接收的消息類型
            sprintf(msg.mtext, "hello, I'm server %d", getpid());
            msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
        }
        return 0;
    }

    msg_client.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/msg.h>
    
    // 用於建立一個惟一的key
    #define MSG_FILE "/etc/passwd"
    
    // 消息結構
    struct msg_form {
        long mtype;
        char mtext[256];
    };
    
    int main()
    {
        int msqid;
        key_t key;
        struct msg_form msg;
    
        // 獲取key值
        if ((key = ftok(MSG_FILE, 'z')) < 0) 
        {
            perror("ftok error");
            exit(1);
        }
    
        // 打印key值
        printf("Message Queue - Client key is: %d.\n", key);
    
        // 打開消息隊列
        if ((msqid = msgget(key, IPC_CREAT|0777)) == -1) 
        {
            perror("msgget error");
            exit(1);
        }
    
        // 打印消息隊列ID及進程ID
        printf("My msqid is: %d.\n", msqid);
        printf("My pid is: %d.\n", getpid());
    
        // 添加消息,類型爲888
        msg.mtype = 888;
        sprintf(msg.mtext, "hello, I'm client %d", getpid());
        msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
    
        // 讀取類型爲777的消息
        msgrcv(msqid, &msg, 256, 999, 0);
        printf("Client: receive msg.mtext is: %s.\n", msg.mtext);
        printf("Client: receive msg.mtype is: %d.\n", msg.mtype);
        return 0;
    }

    信號量

    信號量(semaphore)與已經介紹過的 IPC 結構不一樣,它是一個計數器。信號量用於實現進程間的互斥與同步,而不是用於存儲進程間通訊數據。

    • 特色
      1.信號量用於進程間同步,若要在進程間傳遞數據須要結合共享內存。

      2.信號量基於操做系統的 PV 操做,程序對信號量的操做都是原子操做。

      3.每次對信號量的 PV 操做不只限於對信號量值加 1 或減 1,並且能夠加減任意正整數。

      4.支持信號量組。

    • 原型

    最簡單的信號量是隻能取 0 和 1 的變量,這也是信號量最多見的一種形式,叫作二值信號量(Binary Semaphore)。而能夠取多個正整數的信號量被稱爲通用信號量。

    Linux 下的信號量函數都是在通用的信號量數組上進行操做,而不是在一個單一的二值信號量上進行操做。

    #include <sys/sem.h>
    // 建立或獲取一個信號量組:若成功返回信號量集ID,失敗返回-1
    int semget(key_t key, int num_sems, int sem_flags);
    // 對信號量組進行操做,改變信號量的值:成功返回0,失敗返回-1
    int semop(int semid, struct sembuf semoparray[], size_t numops);  
    // 控制信號量的相關信息
    int semctl(int semid, int sem_num, int cmd, ...);

    當semget建立新的信號量集合時,必須指定集合中信號量的個數(即num_sems),一般爲1; 若是是引用一個現有的集合,則將num_sems指定爲 0 。

    在semop函數中,sembuf結構的定義以下:

    struct sembuf 
    {
        short sem_num; // 信號量組中對應的序號,0~sem_nums-1
        short sem_op;  // 信號量值在一次操做中的改變量
        short sem_flg; // IPC_NOWAIT, SEM_UNDO
    }

    其中 sem_op 是一次操做中的信號量的改變量:

    • 若sem_op > 0,表示進程釋放相應的資源數,將 sem_op 的值加到信號量的值上。若是有進程正在休眠等待此信號量,則換行它們。
    • 若sem_op < 0,請求 sem_op 的絕對值的資源。

      • 若是相應的資源數能夠知足請求,則將該信號量的值減去sem_op的絕對值,函數成功返回。
      • 當相應的資源數不能知足請求時,這個操做與sem_flg有關。

        • sem_flg 指定IPC_NOWAIT,則semop函數出錯返回EAGAIN。
        • sem_flg 沒有指定IPC_NOWAIT,則將該信號量的semncnt值加1,而後進程掛起直到下述狀況發生:

          • 當相應的資源數能夠知足請求,此信號量的semncnt值減1,該信號量的值減去sem_op的絕對值。成功返回;
          • 此信號量被刪除,函數smeop出錯返回EIDRM;
          • 進程捕捉到信號,並從信號處理函數返回,此狀況下將此信號量的semncnt值減1,函數semop出錯返回EINTR
    • 若sem_op == 0,進程阻塞直到信號量的相應值爲0:

      • 當信號量已經爲0,函數當即返回。
      • 若是信號量的值不爲0,則依據sem_flg決定函數動做:

        • sem_flg指定IPC_NOWAIT,則出錯返回EAGAIN。
        • sem_flg沒有指定IPC_NOWAIT,則將該信號量的semncnt值加1,而後進程掛起直到下述狀況發生:

          • 信號量值爲0,將信號量的semzcnt的值減1,函數semop成功返回;
          • 此信號量被刪除,函數smeop出錯返回EIDRM;
          • 進程捕捉到信號,並從信號處理函數返回,在此狀況將此信號量的semncnt值減1,函數semop出錯返回EINTR

    在semctl函數中的命令有多種,這裏就說兩個經常使用的:

    • ETVAL:用於初始化信號量爲一個已知的值。所須要的值做爲聯合semun的val成員來傳遞。在信號量第一次使用以前須要設置信號量。
    • IPC_RMID:刪除一個信號量集合。若是不刪除信號量,它將繼續在系統中存在,即便程序已經退出,它可能在你下次運行此程序時引起問題,並且信號量是一種有限的資源。
    • 例子

    sem_data.h

    #ifndef MUTIPROCESS_SHM_DATA_H__
    #define MUTIPROCESS_SHM_DATA_H__
    
    
    #define MAX_MSG_SIZE  1024
    #define SHARED_BUFFER_KEY 1234
    #define SEM_KEY   5678
    
    struct shared_buffer_t
    {
        int written;  /* 標誌,0 : 可寫, 非0:可讀 */
        char buffer[MAX_MSG_SIZE];
    };
    
    
    union semun
    {
        int val;
        struct semid_ds *buf;
        unsigned short *arry;
    };
    
    
    #endif
    //sem_read.cpp
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/sem.h>
    
    #include "sem_data.h"
    
    int main(int argc, char **argv)
    {
        int shared_buffer_id = shmget(SHARED_BUFFER_KEY, sizeof(shared_buffer_t), 0666 | IPC_CREAT);
        if (shared_buffer_id == -1)
        {
            printf("create shared buffer failed with error = %s.\n", strerror(errno));
            return -1;
        }
    
        /* 將建立的共享內存映射到當前進程的地址空間 */
        void *shared_buffer_ptr = shmat(shared_buffer_id, (void *)0, 0);
        if ((void *)-1 == shared_buffer_ptr)
        {
            printf("attaches shared buffer failed with error=%s.\n", strerror(errno));
            return -1;
        }
    
        shared_buffer_t *shared = (shared_buffer_t *)shared_buffer_ptr;
        /* 通知對端,我已經能夠讀數據了,你如今能夠寫入數據了 */
        //建立信號量
        int sem_id = semget(SEM_KEY, 1, 0666 | IPC_CREAT);
        if (sem_id == -1)
        {
            printf("create sem failed with error=%s.", strerror(errno));
            return -1;
        }
    
        //初始化信號量
        union semun sem_union;
        sem_union.val = 0;
        if (semctl(sem_id, 0, SETVAL, sem_union) == -1)
        {
            printf("init sem failed with error=%s.", strerror(errno));
            // 刪除信號量
            semctl(sem_id, 0, IPC_RMID, sem_union);
            return -1;
        }
    
        int index = 0;
    
        while (1)
        {
            // 等待信號量
            struct sembuf sem_buffer;
            sem_buffer.sem_num = 0;
            sem_buffer.sem_op = -1;
            sem_buffer.sem_flg = SEM_UNDO;
            if (semop(sem_id, &sem_buffer, 1) == -1)
            {
                printf("wait sem failed with error=%s.\n", strerror(errno));
                return -1;
            }
    
            // 若是非0,則能夠讀取內容
            if (0 != shared->written)
            {
                char msg[MAX_MSG_SIZE] = {0};
                strcpy(msg, shared->buffer);
                shared->written = 0;
                printf("recved content is : %s, index = %d.\n", msg, index);
                index++;
                shared->written = 0;
            }
    
            // 發送信號量,離開臨界區
            sem_buffer.sem_op = 1;
            if (semop(sem_id, &sem_buffer, 1) == -1)
            {
                printf("leave sem failed with error=%s\n", strerror(errno));
                return -1;
            }
        }
    
        //把共享內存從當前進程中分離
        if (shmdt(shared_buffer_ptr) == -1)
        {
            fprintf(stderr, "dettaches shared buffer failed.\n");
            return -1;
        }
    
        //刪除共享內存
        if (shmctl(shared_buffer_id, IPC_RMID, 0) == -1)
        {
            fprintf(stderr, "remove shared buffer failed.\n");
            return -1;
        }
    
        return 0;
    }
    //sem_write.cpp
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    #include <string.h>
    #include <sys/sem.h>
    #include <sys/types.h>
    
    #include "sem_data.h"
    
    int main(int argc, char **argv)
    {
        int shared_buffer_id = shmget(SHARED_BUFFER_KEY, sizeof(shared_buffer_t), 0666 | IPC_CREAT);
        if (shared_buffer_id == -1)
        {
            printf("create shared buffer failed with error = %s.\n", strerror(errno));
            return -1;
        }
    
        /* 將建立的共享內存映射到當前進程的地址空間 */
        void *shared_buffer_ptr = shmat(shared_buffer_id, (void *)0, 0);
        if ((void *)-1 == shared_buffer_ptr)
        {
            printf("attaches shared buffer failed with error=%s.\n", strerror(errno));
            return -1;
        }
    
        //建立信號量
        int sem_id = semget(SEM_KEY, 1, 0666 | IPC_CREAT);
        if (sem_id == -1)
        {
            printf("create sem failed with error=%s.", strerror(errno));
            return -1;
        }
    
        //初始化信號量
        semun sem_union;
        sem_union.val = 1;
        if (semctl(sem_id, 0, SETVAL, sem_union) == -1)
        {
            printf("init sem failed with error=%s.", strerror(errno));
            // 刪除信號量
            semctl(sem_id, 0, IPC_RMID, sem_union);
            return -1;
        }
    
        pid_t pid = 0;
        // 開啓多個進程
        for (int i = 0; i < 4; i++)
        {
            pid_t pid = fork();
            if (pid == 0)
            {
                break;
            }
        }
    
        // 父進程
        if (pid > 0)
        {
            for (;;)
                ;
        }
    
        while (1)
        {
            //等待信號量, 即執行P操做
            struct sembuf sem_buffer;
            sem_buffer.sem_num = 0;
            sem_buffer.sem_op = -1;
            sem_buffer.sem_flg = SEM_UNDO;
            if (semop(sem_id, &sem_buffer, 1) == -1)
            {
                printf("wait sem failed with error=%s.\n", strerror(errno));
                return -1;
            }
    
            shared_buffer_t *shared = (shared_buffer_t *)shared_buffer_ptr;
            char *msg = "lee ge stay alone, please contact me with my LINE : leege.\n";
            strcpy(shared->buffer, msg);
    
            shared->written = 1;
            // 已經寫完,離開臨界區
            sem_buffer.sem_op = 1;
            if (semop(sem_id, &sem_buffer, 1) == -1)
            {
                printf("leave sem failed with error=%s\n", strerror(errno));
                return -1;
            }
            usleep(100000); /*100 毫秒*/
        }
    
        //把共享內存從當前進程中分離
        if (shmdt(shared_buffer_ptr) == -1)
        {
            fprintf(stderr, "dettaches shared buffer failed.\n");
            return -1;
        }
    
        //釋放信號量
        if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
        {
            printf("release sem failed with error=%s.\n", strerror(errno));
            return -1;
        }
    
        return 0;
    }

    共享內存

    共享內存(Shared Memory),指兩個或多個進程共享一個給定的存儲區。

    • 特色

      • 共享內存是最快的一種 IPC,由於進程是直接對內存進行存取。
      • 由於多個進程能夠同時操做,因此須要進行同步。
      • 信號量+共享內存一般結合在一塊兒使用,信號量用來同步對共享內存的訪問。
    • 原型
    #include <sys/shm.h>
    // 建立或獲取一個共享內存:成功返回共享內存ID,失敗返回-1
    int shmget(key_t key, size_t size, int flag);
    // 鏈接共享內存到當前進程的地址空間:成功返回指向共享內存的指針,失敗返回-1
    void *shmat(int shm_id, const void *addr, int flag);
    // 斷開與共享內存的鏈接:成功返回0,失敗返回-1
    int shmdt(void *addr); 
    // 控制共享內存的相關信息:成功返回0,失敗返回-1
    int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

    當用shmget函數建立一段共享內存時,必須指定其 size;而若是引用一個已存在的共享內存,則將 size 指定爲0 。

    當一段共享內存被建立之後,它並不能被任何進程訪問。必須使用shmat函數鏈接該共享內存到當前進程的地址空間,鏈接成功後把共享內存區對象映射到調用進程的地址空間,隨後可像本地空間同樣訪問。

    shmdt函數是用來斷開shmat創建的鏈接的。注意,這並非從系統中刪除該共享內存,只是當前進程不能再訪問該共享內存而已。

    shmctl函數能夠對共享內存執行多種操做,根據參數 cmd 執行相應的操做。經常使用的是IPC_RMID(從系統中刪除該共享內存)。

    • 例子

    下面這個例子,使用了【共享內存+信號量+消息隊列】的組合來實現服務器進程與客戶進程間的通訊。

    • 共享內存用來傳遞數據;
    • 信號量用來同步;
    • 消息隊列用來 在客戶端修改了共享內存後 通知服務器讀取。
    //server.c
    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/shm.h>  // shared memory
    #include<sys/sem.h>  // semaphore
    #include<sys/msg.h>  // message queue
    #include<string.h>   // memcpy
    
    // 消息隊列結構
    struct msg_form {
        long mtype;
        char mtext;
    };
    
    // 聯合體,用於semctl初始化
    union semun
    {
        int              val; /*for SETVAL*/
        struct semid_ds *buf;
        unsigned short  *array;
    };
    
    // 初始化信號量
    int init_sem(int sem_id, int value)
    {
        union semun tmp;
        tmp.val = value;
        if(semctl(sem_id, 0, SETVAL, tmp) == -1)
        {
            perror("Init Semaphore Error");
            return -1;
        }
        return 0;
    }
    
    // P操做:
    //  若信號量值爲1,獲取資源並將信號量值-1 
    //  若信號量值爲0,進程掛起等待
    int sem_p(int sem_id)
    {
        struct sembuf sbuf;
        sbuf.sem_num = 0; /*序號*/
        sbuf.sem_op = -1; /*P操做*/
        sbuf.sem_flg = SEM_UNDO;
    
        if(semop(sem_id, &sbuf, 1) == -1)
        {
            perror("P operation Error");
            return -1;
        }
        return 0;
    }
    
    // V操做:
    //  釋放資源並將信號量值+1
    //  若是有進程正在掛起等待,則喚醒它們
    int sem_v(int sem_id)
    {
        struct sembuf sbuf;
        sbuf.sem_num = 0; /*序號*/
        sbuf.sem_op = 1;  /*V操做*/
        sbuf.sem_flg = SEM_UNDO;
    
        if(semop(sem_id, &sbuf, 1) == -1)
        {
            perror("V operation Error");
            return -1;
        }
        return 0;
    }
    
    // 刪除信號量集
    int del_sem(int sem_id)
    {
        union semun tmp;
        if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
        {
            perror("Delete Semaphore Error");
            return -1;
        }
        return 0;
    }
    
    // 建立一個信號量集
    int creat_sem(key_t key)
    {
        int sem_id;
        if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
        {
            perror("semget error");
            exit(-1);
        }
        init_sem(sem_id, 1);  /*初值設爲1資源未佔用*/
        return sem_id;
    }
    
    
    int main()
    {
        key_t key;
        int shmid, semid, msqid;
        char *shm;
        char data[] = "this is server";
        struct shmid_ds buf1;  /*用於刪除共享內存*/
        struct msqid_ds buf2;  /*用於刪除消息隊列*/
        struct msg_form msg;  /*消息隊列用於通知對方更新了共享內存*/
    
        // 獲取key值
        if((key = ftok(".", 'z')) < 0)
        {
            perror("ftok error");
            exit(1);
        }
    
        // 建立共享內存
        if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)
        {
            perror("Create Shared Memory Error");
            exit(1);
        }
    
        // 鏈接共享內存
        shm = (char*)shmat(shmid, 0, 0);
        if((int)shm == -1)
        {
            perror("Attach Shared Memory Error");
            exit(1);
        }
    
    
        // 建立消息隊列
        if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
        {
            perror("msgget error");
            exit(1);
        }
    
        // 建立信號量
        semid = creat_sem(key);
        
        // 讀數據
        while(1)
        {
            msgrcv(msqid, &msg, 1, 888, 0); /*讀取類型爲888的消息*/
            if(msg.mtext == 'q')  /*quit - 跳出循環*/ 
                break;
            if(msg.mtext == 'r')  /*read - 讀共享內存*/
            {
                sem_p(semid);
                printf("%s\n",shm);
                sem_v(semid);
            }
        }
    
        // 斷開鏈接
        shmdt(shm);
    
        /*刪除共享內存、消息隊列、信號量*/
        shmctl(shmid, IPC_RMID, &buf1);
        msgctl(msqid, IPC_RMID, &buf2);
        del_sem(semid);
        return 0;
    }
    //client.c
    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/shm.h>  // shared memory
    #include<sys/sem.h>  // semaphore
    #include<sys/msg.h>  // message queue
    #include<string.h>   // memcpy
    
    // 消息隊列結構
    struct msg_form {
        long mtype;
        char mtext;
    };
    
    // 聯合體,用於semctl初始化
    union semun
    {
        int              val; /*for SETVAL*/
        struct semid_ds *buf;
        unsigned short  *array;
    };
    
    // P操做:
    //  若信號量值爲1,獲取資源並將信號量值-1 
    //  若信號量值爲0,進程掛起等待
    int sem_p(int sem_id)
    {
        struct sembuf sbuf;
        sbuf.sem_num = 0; /*序號*/
        sbuf.sem_op = -1; /*P操做*/
        sbuf.sem_flg = SEM_UNDO;
    
        if(semop(sem_id, &sbuf, 1) == -1)
        {
            perror("P operation Error");
            return -1;
        }
        return 0;
    }
    
    // V操做:
    //  釋放資源並將信號量值+1
    //  若是有進程正在掛起等待,則喚醒它們
    int sem_v(int sem_id)
    {
        struct sembuf sbuf;
        sbuf.sem_num = 0; /*序號*/
        sbuf.sem_op = 1;  /*V操做*/
        sbuf.sem_flg = SEM_UNDO;
    
        if(semop(sem_id, &sbuf, 1) == -1)
        {
            perror("V operation Error");
            return -1;
        }
        return 0;
    }
    
    
    int main()
    {
        key_t key;
        int shmid, semid, msqid;
        char *shm;
        struct msg_form msg;
        int flag = 1; /*while循環條件*/
    
        // 獲取key值
        if((key = ftok(".", 'z')) < 0)
        {
            perror("ftok error");
            exit(1);
        }
    
        // 獲取共享內存
        if((shmid = shmget(key, 1024, 0)) == -1)
        {
            perror("shmget error");
            exit(1);
        }
    
        // 鏈接共享內存
        shm = (char*)shmat(shmid, 0, 0);
        if((int)shm == -1)
        {
            perror("Attach Shared Memory Error");
            exit(1);
        }
    
        // 建立消息隊列
        if ((msqid = msgget(key, 0)) == -1)
        {
            perror("msgget error");
            exit(1);
        }
    
        // 獲取信號量
        if((semid = semget(key, 0, 0)) == -1)
        {
            perror("semget error");
            exit(1);
        }
        
        // 寫數據
        printf("***************************************\n");
        printf("*                 IPC                 *\n");
        printf("*    Input r to send data to server.  *\n");
        printf("*    Input q to quit.                 *\n");
        printf("***************************************\n");
        
        while(flag)
        {
            char c;
            printf("Please input command: ");
            scanf("%c", &c);
            switch(c)
            {
                case 'r':
                    printf("Data to send: ");
                    sem_p(semid);  /*訪問資源*/
                    scanf("%s", shm);
                    sem_v(semid);  /*釋放資源*/
                    /*清空標準輸入緩衝區*/
                    while((c=getchar())!='\n' && c!=EOF);
                    msg.mtype = 888;  
                    msg.mtext = 'r';  /*發送消息通知服務器讀數據*/
                    msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                    break;
                case 'q':
                    msg.mtype = 888;
                    msg.mtext = 'q';
                    msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                    flag = 0;
                    break;
                default:
                    printf("Wrong input!\n");
                    /*清空標準輸入緩衝區*/
                    while((c=getchar())!='\n' && c!=EOF);
            }
        }
    
        // 斷開鏈接
        shmdt(shm);
    
        return 0;
    }

    注意:當scanf()輸入字符或字符串時,緩衝區中遺留下了n,因此每次輸入操做後都須要清空標準輸入的緩衝區。可是因爲 gcc 編譯器不支持fflush(stdin)(它只是標準C的擴展),因此咱們使用了替代方案:

    while((c=getchar())!='\n' && c!=EOF);
    • 例子
    //shm_data.cpp
    #ifndef MUTIPROCESS_SHM_DATA_H__
    #define MUTIPROCESS_SHM_DATA_H__
    
    
    #define MAX_MSG_SIZE  1024
    #define SHARED_BUFFER_KEY 1234
    
    
    struct shared_buffer_t
    {
        int written;  /* 標誌,0 : 可寫, 非0:可讀 */
        char buffer[MAX_MSG_SIZE];
    };
    
    #endif
    //shm_read.cpp
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    #include <string.h>
    
    #include "shm_data.h"
    
    
    int main(int argc, char** argv)
    {
        int shared_buffer_id = shmget(SHARED_BUFFER_KEY, sizeof(shared_buffer_t), 0666 | IPC_CREAT );
        if (shared_buffer_id == -1)
        {
            printf("create shared buffer failed with error = %s.\n", strerror(errno));
            return -1;
        }
    
        /* 將建立的共享內存映射到當前進程的地址空間 */
        void* shared_buffer_ptr = shmat(shared_buffer_id, (void*)0, 0);
        if ((void*)-1 == shared_buffer_ptr)
        {
            printf("attaches shared buffer failed with error=%s.\n", strerror(errno));
            return -1;
        }
    
        shared_buffer_t* shared = (shared_buffer_t*)shared_buffer_ptr;
        /* 設置共享內存爲可寫狀態 */
        shared->written = 0;
        // 若是工程內存可寫,則等待
        while (0 == shared->written)
        {
            sleep(1);
        }
    
        char msg[MAX_MSG_SIZE] = {0};
        strcpy(msg, shared->buffer);
        shared->written = 0;
        printf("recved content is : %s.\n", msg);
    
        //把共享內存從當前進程中分離
        if(shmdt(shared_buffer_ptr) == -1)
        {
        fprintf(stderr, "dettaches shared buffer failed.\n");
        return -1;
        }
    
        //刪除共享內存
        if(shmctl(shared_buffer_id, IPC_RMID, 0) == -1)
        {
        fprintf(stderr, "remove shared buffer failed.\n");
        return -1;
        } 
    
        return 0 ;
    }
    //shm_write.cpp
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include <stdio.h>
    #include <errno.h>
    #include <unistd.h>
    #include <string.h>
    
    #include "shm_data.h"
    
    
    int main(int argc, char** argv)
    {
        int shared_buffer_id = shmget(SHARED_BUFFER_KEY, sizeof(shared_buffer_t), 0666 | IPC_CREAT );
        if (shared_buffer_id == -1)
        {
            printf("create shared buffer failed with error = %s.\n", strerror(errno));
            return -1;
        }
    
        /* 將建立的共享內存映射到當前進程的地址空間 */
        void* shared_buffer_ptr = shmat(shared_buffer_id, (void*)0, 0);
        if ((void*)-1 == shared_buffer_ptr)
        {
            printf("attaches shared buffer failed with error=%s.\n", strerror(errno));
            return -1;
        }
    
        shared_buffer_t* shared = (shared_buffer_t*)shared_buffer_ptr;
        // 若是不能夠寫,則阻塞住
        while (0 != shared->written)
        {
            sleep(1);
        }
    
        const char* msg = "hello,world";
        strcpy(shared->buffer, msg);
        shared->written = 1;
    
        //把共享內存從當前進程中分離
        if(shmdt(shared_buffer_ptr) == -1)
        {
        fprintf(stderr, "dettaches shared buffer failed.\n");
        return -1;
        }
    
        return 0 ;
    }

    共享內存的方式在多線程或者多進程通訊時須要進行同步,這時使用臨界區,信號量進行實現同步。

    五種通信方式總結

    1. 管道:速度慢,容量有限,只有父子進程能通信
    2. FIFO:任何進程間都能通信,但速度慢
    3. 消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完數據的問題
    4. 信號量:不能傳遞複雜消息,只能用來同步
    5. 共享內存區:可以很容易控制容量,速度快,但要保持同步,好比一個進程在寫的時候,另外一個進程要注意讀寫的問題,至關於線程中的線程安全,固然,共享內存區一樣能夠用做線程間通信,不過沒這個必要,線程間原本就已經共享了同一進程內的一塊內存
    相關文章
    相關標籤/搜索