管道
匿名管道(Pipe)
特点
- 半双工
- 只能用于具有亲缘关系进程(父子,兄弟)
- 可以视作特殊文件,读写可以使用write,read函数
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
|
#include"stdio.h"
#include"unistd.h"
// https://learnku.com/articles/44477
// **特点**
// 1. 半双工
// 2. 只能用于具有亲缘关系进程(父子,兄弟)
// 3. 可以视作特殊文件,读写可以使用write,read函数
int main() {
int fd[2];
pid_t pid;
char buf[32];
//创建管道
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", 8); //父进程写端写入
}
else {
close(fd[1]); // 关闭子进程写端
read(fd[0], buf, 8); // 子进程读取父进程消息
printf("Child Recv Msg: %s", buf);
}
}
|
命名管道(FIFO)
特点
- 和匿名管道不同,FIFO可以在无关进程间通信
- FIFO有路径名与之关联,以一种特殊文件形式存在于文件系统中
fifo_read.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
|
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"errno.h"
#include"fcntl.h"
#include"sys/stat.h"
int main() {
int fd;
int len;
char buf[1024];
if (mkfifo("/home", 0666) < 0 && errno != EEXIST) {
perror("Create FIFO Failed");
}
if ((fd = open("/home", O_RDONLY)) < 0) {
perror("Open FIFO Failed");
exit(1);
}
while ((len = read(fd, buf, 1024)) > 0) {
printf("Read Message: %s", buf);
}
close(fd);
return 0;
}
|
fifo_write.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
|
#include"stdio.h"
#include"unistd.h"
#include"stdlib.h"
#include"fcntl.h" // O_WRONLY
#include"sys/stat.h"
#include"time.h"
// ## 命名管道(FIFO)
// ** 特点**
// 1. 和匿名管道不同,FIFO可以在无关进程间通信
// 2. FIFO有路径名与之关联,以一种特殊文件形式存在于文件系统中
int main() {
int fd;
int n, i;
char buf[1024];
time_t tp;
printf("Parent Process PID:", getpid());
if ((fd = open("/home", 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);
// 写入数据到FIFO中
if (write(fd, buf, n + 1) < 0) {
perror("Write FIFO Failed");
close(fd);
exit(1);
}
sleep(1);
}
close(fd);
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
52
53
54
55
56
|
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"sys/msg.h"
#define MSG_FILE "/etc/passwd"
// 结构体第一个字段一定为长整型
struct msg_from
{
long mtype;
char mtext[256];
};
int main() {
int msqid;
key_t key;
struct msg_from msg;
// key值不变,要么确保ftok()的文件不被删除,要么不用ftok(),指定一个固定的key值。
// 获取key值
if ((key = ftok(MSG_FILE, 'z')) < 0) {
perror("ftok error");
exit(1);
}
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());
//msgid是由msgget函数返回的消息队列标识符。
// __msgp是一个指向准备发送消息的指针,消息的数据结构却有一定的要求,
// 指针msg_ptr所指向的消息结构一定要是以一个**长整型**成员变量开始的结构体,接收函数将用这个成员来确定消息的类型
msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
// msgrcv函数type参数有以下几种可能:
// type == 0,返回队列中的第一个消息;
// type > 0,返回队列中消息类型为 type 的第一个消息;
// type < 0,返回队列中消息类型值小于或等于 type 绝对值的消息,如果有多个,则取类型值最小的消息。
// 读取类型为999的消息
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);
}
|
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
|
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"sys/msg.h"
#define MSG_FILE "/etc/passwd"
// 结构体第一个字段一定为长整型
struct msg_from
{
long mtype;
char mtext[256];
};
int main() {
int msqid;
key_t key;
struct msg_from 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 | 0700)) == -1) {
perror("msgget error");
exit(1);
}
printf("My msqid is: %d.\n", msqid);
printf("My pid is: %d.\n", getpid());
for (;;) {
// 返回类型为888的第一个消息
msgrcv(msqid, &msg, 256, 888, 0);
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;
}
|
信号量
特点
- 是一个计数器
- 用于实现进程间互斥和同步,不是存储进程间通信数据
示例
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
|
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"sys/sem.h"
union semun
{
int val;
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; // 操作
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;
key_t key;
pid_t pid;
// 获取key值
if ((key = ftok("/home", '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;
}
|
共享内存
特点
- 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
- 因为多个进程可以同时操作,所以需要进行同步。
- 信号量 + 共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
示例
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
|
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"sys/shm.h"
#include"string.h"
int main() {
key_t key;
int shmid;
if ((key = ftok("/etc", 'k')) < 0) {
perror("ftok error");
exit(-1);
}
pid_t pid;
if ((pid = fork()) < 0) {
perror("fork error");
exit(-1);
}
else if (pid == 0) {
// 创建共享内存
shmid = shmget(key, 1024 * 1024 * 10, IPC_CREAT | 0666);
// 连接共享内存到当前进程
// 0 表示可读可写
char *s = (char*)shmat(shmid, NULL, 0);
printf("shm write data\n");
memset(s, 'B', 1024);
// 卸载共享内存
shmdt(s);
}
else {
// 创建共享内存
shmid = shmget(key, 1024 * 1024 * 10, IPC_CREAT | 0666);
// 连接共享内存到当前进程
// SHM_RDONLY表示只读
char *str = (char*)shmat(shmid, NULL, SHM_RDONLY);
printf("read data from shm is:%s\n", str);
// 卸载共享内存
shmdt(str);
}
return 0;
}
|
信号
特点
- 可靠信号与不可靠信号,前32种信号为不可靠信号,后32种为可靠信号。
- 不可靠信号: 也称为非实时信号,不支持排队,信号可能会丢失, 比如发送多次相同的信号, 进程只能收到一次. 信号值取值区间为1~31;
- 可靠信号: 也称为实时信号,支持排队, 信号不会丢失, 发多少次, 就可以收到多少次. 信号值取值区间为32~64
示例
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 <sys/types.h>
#include <signal.h>
#include<stdio.h>
#include <unistd.h>
int main(int argc, char** argv)
{
if (3 != argc)
{
printf("[Arguments ERROR!]\n");
printf("\tUsage:\n");
printf("\t\t%s <Target_PID> <Signal_Number>\n", argv[0]);
return -1;
}
int pid = atoi(argv[1]);
int sig = atoi(argv[2]);
if (pid > 0 && sig > 0)
{
kill(pid, sig);
}
else
{
printf("Target_PID or Signal_Number MUST bigger than 0!\n");
}
return 0;
}
|
套接字
特点
- socket可以在同一个主机也可以在不同主机间进行通信
示例
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
|
#include"stdio.h"
#include"stdlib.h"
#include"unistd.h"
#include"sys/socket.h"
int main() {
int fd[2];
int pair = socketpair(AF_UNIX, SOCK_STREAM, 0, fd);
if (pair < 0) {
perror("socketpair()");
exit(1);
}
pid_t pid = fork();
if (pid < 0) {
perror("fork failure");
exit(1);
}
else if (pid > 0) {
close(fd[1]);
printf("child process pid: %d\n", getpid());
char* send_str = "this is child process msg\0";
char* buf[1024];
while (1)
{
write(fd[0], &send_str, sizeof(send_str));
read(fd[0], &buf, sizeof(buf));
printf("recv msg from parent process is: %s\n", buf);
}
}
else {
sleep(2);
close(fd[0]);
printf("parent process pid: %d\n", getpid());
char* send_str = "this is parent process msg\0";
char* buf[1024];
read(fd[1], &buf, sizeof(buf));
printf("recv msg from child process is: %s\n", buf);
write(fd[1], &send_str, sizeof(send_str));
sleep(5);
}
}
|
参考链接
进程间的五种通信方式介绍-详解 | Laravel China 社区 (learnku.com)