進程間通信(IPC)[轉載]

進程間通信(IPC,InterProcess Communication)是指在不同進程之間傳播或交換信息。IPC的方式通常有管道(包括無名管道和命名管道)、消息隊列、信號量、共享存儲、Socket、Streams等。其中 Socket和Streams支持不同主機上的兩個進程IPC。

一、管道

管道,通常指無名管道,是 UNIX 系統IPC最古老的形式。

1、特點:

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

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

  3. 它可以看成是一種特殊的文件,對於它的讀寫也可以使用普通的read、write 等函數。但是它不是普通的文件,並不屬於其他任何文件系統,並且只存在於內存中。

2、原型:

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

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

要關閉管道只需將這兩個文件描述符關閉即可。

3、例子

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
#include<unistd.h>

int main()
{

int fd[2]; // 兩個文件描述符
pid_t pid;
char buff[20];

if(pipe(fd) < 0) // 創建管道
printf("Create Pipe Error!\n");

if((pid = fork()) < 0) // 創建子進程
printf("Fork Error!\n");
else if(pid > 0) // 父進程
{
close(fd[0]); // 關閉讀端
write(fd[1], "hello world\n", 12);
}
else
{
close(fd[1]); // 關閉寫端
read(fd[0], buff, 20);
printf("%s", buff);
}

return 0;
}


二、FIFO

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

1、特點

  1. FIFO可以在無關的進程之間交換數據,與無名管道不同。

  2. FIFO有路徑名與之相關聯,它以一種特殊設備文件形式存在於文件系統中。

2、原型

1
2
3
#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。

3、例子

FIFO的通信方式類似於在進程中使用文件來傳輸數據,只不過FIFO類型文件同時具有管道的特性。在數據讀出時,FIFO管道中同時清除數據,並且“先進先出”。下面的例子演示了使用 FIFO 進行 IPC 的過程:

  • write_fifo.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    #include<stdio.h>
    #include<stdlib.h> // exit
    #include<fcntl.h> // O_WRONLY
    #include<sys/stat.h>
    #include<time.h> // time

    int main()
    {

    int fd;
    int n, i;
    char buf[1024];
    time_t tp;

    printf("I am %d process.\n", getpid()); // 說明進程ID

    if((fd = open("fifo1", O_WRONLY)) < 0) // 以寫打開一個FIFO
    {
    perror("Open FIFO Failed");
    exit(1);
    }

    for(i=0; i<10; ++i)
    {
    time(&tp); // 取系統當前時間
    n=sprintf(buf,"Process %d's time is %s",getpid(),ctime(&tp));
    printf("Send message: %s", buf); // 打印
    if(write(fd, buf, n+1) < 0) // 寫入到FIFO中
    {
    perror("Write FIFO Failed");
    close(fd);
    exit(1);
    }
    sleep(1); // 休眠1秒
    }

    close(fd); // 關閉FIFO文件
    return 0;
    }
  • read_fifo.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    #include<stdio.h>
    #include<stdlib.h>
    #include<errno.h>
    #include<fcntl.h>
    #include<sys/stat.h>

    int main()
    {

    int fd;
    int len;
    char buf[1024];

    if(mkfifo("fifo1", 0666) < 0 && errno!=EEXIST) // 創建FIFO管道
    perror("Create FIFO Failed");

    if((fd = open("fifo1", O_RDONLY)) < 0) // 以讀打開FIFO
    {
    perror("Open FIFO Failed");
    exit(1);
    }

    while((len = read(fd, buf, 1024)) > 0) // 讀取FIFO管道
    printf("Read message: %s", buf);

    close(fd); // 關閉FIFO文件
    return 0;
    }



在兩個終端裏用 gcc 分別編譯運行上面兩個文件,可以看到輸出結果如下:

1
2
3
4
5
6
7
8
9
10
11
12
[songlee@localhost]$ ./write_fifo 
I am 5954 process.
Send message: Process 5954's time is Mon Apr 20 12:37:28 2015
Send message: Process 5954's time is Mon Apr 20 12:37:29 2015
Send message: Process 5954's time is Mon Apr 20 12:37:30 2015
Send message: Process 5954's time is Mon Apr 20 12:37:31 2015
Send message: Process 5954's time is Mon Apr 20 12:37:32 2015
Send message: Process 5954's time is Mon Apr 20 12:37:33 2015
Send message: Process 5954's time is Mon Apr 20 12:37:34 2015
Send message: Process 5954's time is Mon Apr 20 12:37:35 2015
Send message: Process 5954's time is Mon Apr 20 12:37:36 2015
Send message: Process 5954's time is Mon Apr 20 12:37:37 2015

1
2
3
4
5
6
7
8
9
10
11
[songlee@localhost]$ ./read_fifo 
Read message: Process 5954's time is Mon Apr 20 12:37:28 2015
Read message: Process 5954's time is Mon Apr 20 12:37:29 2015
Read message: Process 5954's time is Mon Apr 20 12:37:30 2015
Read message: Process 5954's time is Mon Apr 20 12:37:31 2015
Read message: Process 5954's time is Mon Apr 20 12:37:32 2015
Read message: Process 5954's time is Mon Apr 20 12:37:33 2015
Read message: Process 5954's time is Mon Apr 20 12:37:34 2015
Read message: Process 5954's time is Mon Apr 20 12:37:35 2015
Read message: Process 5954's time is Mon Apr 20 12:37:36 2015
Read message: Process 5954's time is Mon Apr 20 12:37:37 2015




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


三、消息隊列

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

1、特點

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

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

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

2、原型

1
2
3
4
5
6
7
8
9
#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 看做優先級的權值。(其他的參數解釋,請自行Google之)

3、例子

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

  • msg_server.c

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    #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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    #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、特點

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

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

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

  4. 支持信號量組。

2、原型

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

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

1
2
3
4
5
6
7
#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結構的定義如下:

1
2
3
4
5
6
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:刪除一個信號量集合。如果不刪除信號量,它將繼續在系統中存在,即使程序已經退出,它可能在你下次運行此程序時引發問題,而且信號量是一種有限的資源。

3、例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#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;
}

上面的例子如果不加信號量,則父進程會先執行完畢。這裏加了信號量讓父進程等待子進程執行完以後再執行。


五、共享內存

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

1、特點

  1. 共享內存是最快的一種 IPC,因爲進程是直接對內存進行存取。

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

  3. 信號量+共享內存通常結合在一起使用,信號量用來同步對共享內存的訪問。

2、原型

1
2
3
4
5
6
7
8
9
#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(從系統中刪除該共享內存)。

3、例子

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

  • 共享內存用來傳遞數據;
  • 信號量用來同步;
  • 消息隊列用來 在客戶端修改了共享內存後 通知服務器讀取。

Server.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
#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的擴展),所以我們使用了替代方案:

1
while((c=getchar())!='\n' && c!=EOF);

註釋已經很詳細了,所以代碼的其他部分我就不解釋了,下面是運行結果截圖:







轉自: https://songlee24.github.io/2015/04/21/linux-IPC/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章