管道
匿名管道(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 ( " \t Usage: \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)