1. 程式人生 > 其它 >程序通訊簡單程式碼示例-(無名管道,有名管道,共享記憶體,訊息佇列,訊號量)

程序通訊簡單程式碼示例-(無名管道,有名管道,共享記憶體,訊息佇列,訊號量)

技術標籤:C語言學習LinuxlinuxIPCFIFOC

目錄

無名管道

有名管道

訊息佇列

訊號量

共享記憶體


程序間通訊技術包括訊息傳遞、同步、共享記憶體和遠端過程呼叫。IPC是一種標準的Unix通訊機制。IPC的方式通常有管道(包括無名管道和有名管道)、訊息佇列,訊號量、共享儲存、Socket、Streams等,其中Socket和Streams支援在不同主機上的兩個程序IPC

無名管道

特點:

1) 半雙工的(即資料只能在一個方向上流動),具有固定的讀端和寫端;

2) 只能用於具有親緣關係的程序之間的通訊(也是父子程序或者兄弟程序之間);

3) 它可以看成是一種特殊的檔案,對於它的讀寫也可以使用普通的read、write 等函式。但是它不是普通的檔案,並不屬於其他任何檔案系統,並且只存在於記憶體中。

4)資料儲存屬於佇列的性質,資料讀完之後就沒有了。管道有快取大小,當寫入大於快取會發生寫阻塞,當資料被讀取後才能繼續寫入。

原型:

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

程式碼:

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

int main()
{
    int fd[2];  // 兩個檔案描述符
    pid_t pid;
    char buff[20];
    int ret=pipe(fd); // 建立管道
    if(ret < 0) 
        printf("Create Pipe Error!\n");

    pid=fork(); // 建立子程序
    if( pid  < 0)  
        printf("Fork Error!\n");
    if(pid > 0)  // 父程序
    {
        close(fd[0]); // 關閉讀端
        write(fd[1], "hello world\n", 12);
        printf("this is parent process ,and pipe write is finished.\n");
    }
    if(pid==0)
    {
        close(fd[1]); // 關閉寫端
        read(fd[0], buff, 20);
        printf("%s", buff);
        printf("this is child process ,and pipe read is %s.\n",buff);
    }

    return 0;
}

有名管道

有名管道(FIFO)是一種檔案型別,可以在無關的程序之間交換資料,通過一種特殊裝置檔案形式存在於檔案系統中。

原型:

#include<stdio.h>

int mkfifo(const char *pathname, mode_t_Mode);

mode_t_Mode和open函式的mode一樣,見下文。其中O_NONBLOCK是指非阻塞模式。

以只讀和只寫方式 open 管道,不指定非阻塞模式。特點:

  • open() 以只讀方式開啟 FIFO 時,要阻塞到另一個程序為寫而開啟此 FIFO;
  • open() 以只寫方式開啟 FIFO 時,要阻塞到另一個程序為讀而開啟此 FIFO。
  • 通訊過程中若寫程序先退出了,就算命名管道里沒有資料,呼叫 read() 函式從 FIFO 裡讀資料時不阻塞;若寫程序又重新執行,則呼叫 read() 函式從 FIFO 裡讀資料時又恢復阻塞。

以只讀和只寫方式 open 管道,指定非阻塞模式。特點:

  • 先以只讀方式開啟,如果沒有程序已經為寫而開啟一個 FIFO, 只讀 open() 成功,並且 open() 不阻塞。
  • 先以只寫方式開啟,如果沒有程序已經為讀而開啟一個 FIFO,只寫 open() 將出錯返回 -1 。
  • read()、write() 讀寫命名管道中讀資料時不阻塞。

以可讀可寫方式 open 管道,則 open() 函式不會阻塞。特點:

  • read() 仍會阻塞,緩衝區滿時,write() 也會阻塞;
  • 通訊過程中,讀程序退出後,寫程序向命名管道內寫資料時,寫程序不會退出
  • 通訊過程中若寫程序先退出了,如果名管道里沒有資料,呼叫 read() 函式從 FIFO 裡讀資料時會阻塞,與第一種情況不同。

mode_t_Mode引數所能使用的標記:
O_RDONLY 以只讀方式開啟檔案
O_WRONLY 以只寫方式開啟檔案
O_RDWR 以可讀寫方式開啟檔案. 上述三種旗標是互斥的, 也就是不可同時使用, 但可與下列的旗標利用OR(|)運算子組合.
O_CREAT 若欲開啟的檔案不存在則自動建立該檔案.
O_EXCL 如果O_CREAT 也被設定, 此指令會去檢查檔案是否存在. 檔案若不存在則建立該檔案, 否則將導致開啟檔案錯誤. 此外, 若O_CREAT 與O_EXCL 同時設定, 並且欲開啟的檔案為符號連線, 則會開啟檔案失敗.
O_NOCTTY 如果欲開啟的檔案為終端機裝置時, 則不會將該終端機當成程序控制終端機.
O_TRUNC 若檔案存在並且以可寫的方式開啟時, 此旗標會令檔案長度清為0, 而原來存於該檔案的資料也會消失.
O_APPEND 當讀寫檔案時會從檔案尾開始移動, 也就是所寫入的資料會以附加的方式加入到檔案後面.
O_NONBLOCK 以不可阻斷的方式開啟檔案, 也就是無論有無資料讀取或等待, 都會立即返回程序之中.
O_NDELAY 同O_NONBLOCK.
O_SYNC 以同步的方式開啟檔案.
O_NOFOLLOW 如果引數pathname 所指的檔案為一符號連線, 則會令開啟檔案失敗.
O_DIRECTORY 如果引數pathname 所指的檔案並非為一目錄, 則會令開啟檔案失敗。注:此為Linux2. 2 以後特有的旗標, 以避免一些系統安全問題.

程式碼:

server_write.c:從管道寫入資料

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <fcntl.h>
#include <signal.h>

//訊號處理函式。
void fun(int sig)
{
    printf("sig == %d\n",sig);
}

int main()
{
    signal(SIGPIPE,fun);
    int fd = open("/tmp/fifo.tmp",O_WRONLY);

    assert(fd != -1);//fd==-1則建立失敗

    char buff[128] = {0};
    while(1)
    {
        printf("input:\n");
        fgets(buff,128,stdin);//服務端寫入內容
        write(fd,buff,strlen(buff));

        if(strncmp(buff,"end",3)==0)  //當輸入end時候,退出程式
        {
            break;
        }
    }
    close(fd);
    exit(0);
}

client_read.c:從管道讀取資料

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

int main()
{
    int fd = open("fifo",O_RDONLY);
    assert(fd != -1);

    char buff[128] = {0};

    int n = 0;
    while((n = read(fd,buff,127))>0)//n是讀入內容的size,當n==0時候表示無讀入內容
    {
        printf("read:(n = %d)%s\n",n,buff);       
        memset(buff,0,128);//將buff中的資料清空
    }
    close(fd);
    exit(0);
}

訊息佇列

訊息佇列指存放訊息的佇列。是訊息的連結表,存放在核心中。一個訊息佇列由一個識別符號(即佇列ID)來標識。

特點:

  1. 訊息佇列是面向記錄的,其中的訊息具有特定的格式以及特定的優先順序。

  2. 訊息佇列獨立於傳送與接收程序。程序終止時,訊息佇列及其內容並不會被刪除。

  3. 訊息佇列可以實現訊息的隨機查詢,訊息不一定要以先進先出的次序讀取,也可以按訊息的型別讀取。

原型:

#include <sys/msg.h>

int msgget(key_t key, int flag);// 建立或開啟訊息佇列:成功返回佇列ID,失敗返回-1

int msgsnd(int msqid, const void *ptr, size_t size, int flag);// 新增訊息:成功返回0,失敗返回-1

int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);// 讀取訊息:成功返回訊息資料的長度,失敗返回-1

int msgctl(int msqid, int cmd, struct msqid_ds *buf);// 控制訊息佇列:成功返回0,失敗返回-1

在以下兩種情況下,msgget將建立一個新的訊息佇列:

  • 如果沒有與鍵值key相對應的訊息佇列,並且flag中包含了IPC_CREAT標誌位。
  • key引數為IPC_PRIVATE

函式msgrcv在讀取訊息佇列時,type引數有下面幾種情況:

  • type == 0,返回佇列中的第一個訊息;
  • type > 0,返回佇列中訊息型別為 type 的第一個訊息;
  • type < 0,返回佇列中訊息型別值小於或等於 type 絕對值的訊息,如果有多個,則取型別值最小的訊息。

可以看出,type值非 0 時用於以非先進先出次序讀訊息。也可以把 type 看做優先順序的權值。

程式碼:

服務端程式一直在等待特定型別的訊息,當收到該型別的訊息以後,傳送另一種特定型別的訊息作為反饋,客戶端讀取該反饋並打印出來。

msg_server.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>


#define MSG_FILE "/etc/passwd"// 用於建立一個唯一的key


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>


#define MSG_FILE "/etc/passwd"// 用於建立一個唯一的key


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

訊號量

訊號量:是一個含有整數值的資源,程序通過檢測該整數值,來保證其他程序在某個時間不會進行類似的操作。訊號量用於實現程序間的互斥與同步,而不是用於儲存程序間通訊資料。

特點:

  1. 訊號量用於程序間同步,若要在程序間傳遞資料需要結合共享記憶體。

  2. 訊號量基於作業系統的 PV 操作,程式對訊號量的操作都是原子操作。

  3. 每次對訊號量的 PV 操作不僅限於對訊號量值加 1 或減 1,而且可以加減任意正整數。

  4. 支援訊號量組。

原型:

#include <sys/sem.h>

int semget(key_t key, int num_sems, int sem_flags);// 建立或獲取一個訊號量組:若成功返回訊號量集ID,失敗返回-1

int semop(int semid, struct sembuf semoparray[], size_t numops);// 對訊號量組進行操作,改變訊號量的值:成功返回0,失敗返回-1

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,然後程序掛起直到下述情況發生:
        1. 當相應的資源數可以滿足請求,此訊號量的semncnt值減1,該訊號量的值減去sem_op的絕對值。成功返回;
        2. 此訊號量被刪除,函式smeop出錯返回EIDRM;
        3. 程序捕捉到訊號,並從訊號處理函式返回,此情況下將此訊號量的semncnt值減1,函式semop出錯返回EINTR
  • sem_op == 0,程序阻塞直到訊號量的相應值為0:

    • 當訊號量已經為0,函式立即返回。
    • 如果訊號量的值不為0,則依據sem_flg決定函式動作:
      • sem_flg指定IPC_NOWAIT,則出錯返回EAGAIN
      • sem_flg沒有指定IPC_NOWAIT,則將該訊號量的semncnt值加1,然後程序掛起直到下述情況發生:
        1. 訊號量值為0,將訊號量的semzcnt的值減1,函式semop成功返回;
        2. 此訊號量被刪除,函式smeop出錯返回EIDRM;
        3. 程序捕捉到訊號,並從訊號處理函式返回,在此情況將此訊號量的semncnt值減1,函式semop出錯返回EINTR

semctl函式中的命令有多種,這裡就說兩個常用的:

  • SETVAL:用於初始化訊號量為一個已知的值。所需要的值作為聯合semun的val成員來傳遞。在訊號量第一次使用之前需要設定訊號量。
  • IPC_RMID:刪除一個訊號量集合。如果不刪除訊號量,它將繼續在系統中存在,即使程式已經退出,它可能在你下次執行此程式時引發問題,而且訊號量是一種有限的資源。

程式碼:

該例子如果不加訊號量,則父程序會先執行完畢。這裡加了訊號量讓父程序等待子程序執行完以後再執行。

#include<stdio.h>
#include<stdlib.h>
#include<sys/sem.h>

// 聯合體,用於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 main()
{
    int sem_id;  // 訊號量集ID
    key_t key;
    pid_t pid;

    // 獲取key值
    if((key = ftok(".", 'z')) < 0)
    {
        perror("ftok error");
        exit(1);
    }

    // 建立訊號量集,其中只有一個訊號量
    if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
    {
        perror("semget error");
        exit(1);
    }

    // 初始化:初值設為0資源被佔用
    init_sem(sem_id, 0);

    if((pid = fork()) == -1)
        perror("Fork Error");
    else if(pid == 0) /*子程序*/
    {
        sleep(2);
        printf("Process child: pid=%d\n", getpid());
        sem_v(sem_id);  /*釋放資源*/
    }
    else  /*父程序*/
    {
        sem_p(sem_id);   /*等待資源*/
        printf("Process father: pid=%d\n", getpid());
        sem_v(sem_id);   /*釋放資源*/
        del_sem(sem_id); /*刪除訊號量集*/
    }
    return 0;
}

共享記憶體

共享記憶體:在Linux中,每個程序使用獨立的程序地址空間。程序間是不能訪問其他程序的地址空間。共享記憶體間通過建立一段允許其他程序使用過的記憶體段,實現資源和資料的共享。

特點:

  1. 共享記憶體是最快的一種 IPC,因為程序是直接對記憶體進行存取。

  2. 因為多個程序可以同時操作,所以需要進行同步。

  3. 訊號量+共享記憶體通常結合在一起使用,訊號量用來同步對共享記憶體的訪問。

原型:

#include <sys/shm.h>

int shmget(key_t key, size_t size, int flag);// 建立或獲取一個共享記憶體:成功返回共享記憶體ID,失敗返回-1

void *shmat(int shm_id, const void *addr, int flag);// 連線共享記憶體到當前程序的地址空間:成功返回指向共享記憶體的指標,失敗返回-1

int shmdt(void *addr);// 斷開與共享記憶體的連線:成功返回0,失敗返回-1

int shmctl(int shm_id, int cmd, struct shmid_ds *buf);// 控制共享記憶體的相關資訊:成功返回0,失敗返回-1

當key引數為IPC_PRIVATE時候,shmid=0,每次都會重新建立一個共享記憶體。當用ftok建立k時候,則shmid不等於0。這兩種方式的區別是IPC_PRIVATE類似有親緣關係的程序共享記憶體,fotk類似無親緣關係的程序的共享記憶體。

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

參考https://www.cnblogs.com/zgq0/p/8780893.html