进程控制搞完了,再来record一下,进程的通信,总的来说说。
我觉得管道 套接字 or 消息队列的底层实现还是通过文件来实现
的只是各自功能不同,所以就适合不同的情况。但是本质上还是文件
我们收发信息就是io的过程。把io的思想套入进去理解进程间的通信
这样就会容易理解了。个人认为,进程间的通信亦是io的变种吧。


管道

概念

管道 就是管道,我们平常在shell里面经常用的”|“ 就是管道了
”|“的功能是把上一个程序的输出链接到下个程序的输入,并且不可逆。
就跟听录音机一样,播音主播只负责说,我们只负责听,管道就是这样
这就是所谓的半双工。

1
2
3
4
5
6
7
#include <unistd.h>
int pipe(int fd[2]);
返回值:若成功,返回0,若出错,返回-1
参数fd返回俩个文件描述符:fd[0]为了读而打开,fd[1]为了写而打开
数据经由 fd[1] 到 fd[0] 。
shell命令: cat file1 | grep "test"
相当于shell命令: cat file1 "fd[1]-> fd[0]"grep "test"

pipe的属于匿名管道,只能在子进程和父进程之间进程通信,通信的
前提是父进程先使用pipe生成管道文件,然后fork 子进程,因为子进程
复制父进程,所以这个时候子进程就可以使用fd[1]给父进程写数据,或者
使用fd[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
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define MAXLEN 100
int main()
{
int n;
int fd[2];
pid_t pid;
char line[MAXLEN];
if (pipe(fd) < 0) {
printf("pipe error\n");
return -1;
}
if ((pid = fork()) < 0) {
printf("fork error");
return -2;
} else if (pid > 0) { /* parent */
close(fd[0]); /*close read end */
write(fd[1], "hello world\n", 12);
} else { /* child */
close(fd[1]); /* close wirte end */
n = read(fd[0], line, MAXLEN);
line[n-1] = '\n';
line[n] = '\0';
write(1, line, n);
}
return 0;
}

FIFO

上面说道的管道是匿名管道只能在一个进程组(具有共同祖先进程)中使用

不能在俩个没有关系的进程之间传递。FIFO是一个命名管道,意思是给管道命名
然后就可以传递管道了因为fifo是文件类型。并且fifo还是全双工的。

1
2
3
4
5
6
7
8
9
#include <sys/stat.h>
int mkfifo(const char* path, mode_t mode);
int mkfifoat(int fd, const char* path, mode_t mode);
俩个函数的返回值:若成功,返回0;若出错,返回-1.
俩个函数的区别在于第二个函数使用文件描述符fd, 如果path是绝对路径
则俩个函数一样,path是相对路径那就基于fd的。
mode 则是和创建文件的时候使用的一样,新文件的权限。
俩个函数都是创建一个fifo文件,使用的时候就是正常文件的
流程 open -> write -> read ->close 就可以了。

消息队列

消息队列是消息的链接表,存储在内核中,由消息队列标识符标识。

创建消息队列

1
2
3
4
5
6
7
#include <sys/msg.h>
/* 打开一个or 创建一个消息队列
* key 是 已经创建的标识符则,打开已有的消息队列
* flag 跟open 文件的flag一样,权限
*/
int msgget(key_t key, int flag);
若成功:返回消息队列id;若失败,返回-1

修改消息队列属性

1
2
3
4
5
6
7
8
#include <sys/msg.h>
/*
* cmd 参数1 : IPC_STAT 取msgid msgid_ds 结构存储到buf中
* cmd 参数2 : IPC_SET 设置 msgid_ds 结构使用buf指向的内容
* cmd 参数3 : IPC_RMID 从系统中删除该消息队列
*/
int msgctl(int msgid, int cmd, struct msgid_ds *buf);
返回值:若成功,返回0;若出错,返回-1

发送消息

1
2
3
4
5
6
7
8
9
10
11
12
#include <sys/msg.h>
/*
* ptr 指向一个mymesh结构的指针
* struct mymesg {
* long mtype;
* char mtext[512];
* };
* nbytes 标示信息实际的长度
* flag IPC_NOWAIT 指定为非阻塞,类似于文件的 非阻塞标志
*/
int msgsnd(int msgid, const void *ptr,size_t nbytes, int flag);
返回值:若成功,返回指向消息队列段的指针;若失败,返回-1

接收消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <sys/msg.h>
/*
* ptr 指向一个mymesg结构的指针
* struct mymesg {
* long mtype;
* char mtext[512];
* };
* nbytes 标示信息实际的长度
* type == 0 返回队列第一条信息,type > 0 ,返回类型为type的第一个信息
* type < 0 返回队列消息类型值小于等于type绝对值的消息,多有多个返回类型值最小的
* flag IPC_NOWAIT 指定为非阻塞,类似于文件的 非阻塞标志
*/
int msgrcv(int msgid, const void *ptr,size_t nbytes, long type, int flag);
返回值:若成功,返回指向消息队列段的指针;若失败,返回-1

共享存储

共享存储允许俩个或者多个进程共享一个给定的存储区。因为数据不需要

在进程之间传输,所以是效率最高的IPC.But 同时我们需要考虑到多个进程访问
同一个存储区,就存在和多线程一样问题。那就是多进程同步问题。这个就需要
使用进程间的通信或者直接使用信号机制通过收发信号来通知相关进程。

创建共享存储

1
2
3
4
5
6
7
8
#include <sys/shm.h>
/* 打开一个or 创建一个共享内存
* key 是IPC_PRIVATE 新建一个共享内存,并且返回唯一标识
* key 是 已经创建的标识符则,打开已有的共享内存
* size 缓存大小
* flag 跟open 文件的flag一样,标示 write read 等
*/
int shmget(key_t key, size_t size, int flag);

修改共享内存属性

1
2
3
4
5
6
7
8
9
10
#include <sys/shm.h>
/*
* cmd 参数1 : IPC_STAT 取shmid shmid_ds 结构存储到buf中
* cmd 参数2 : IPC_SET 设置 shmid_ds 结构使用buf指向的内容
* cmd 参数3 : IPC_RMID 从系统中删除该共享内存
* cmd 参数4 : SHM_LOCL 给内存加锁,只有root用户有此权限
* cmd 参数5 : SHM_INLOCK 给内存解锁,只有root用户有此权限
*/
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
返回值:若成功,返回0;若出错,返回-1

获取共享内存地址

1
2
3
4
5
6
7
#include <sys/shm.h>
/*
* addr 为0, 则由内核分配一个可用的地址,一般都用这个
* flag SHM_RDONLY ,标示以只读方式打开共享内内,否则是以读写方式
*/
void *shmat(int shmid, const void *addr, int flag);
返回值:若成功,返回指向共享存储段的指针;若失败,返回-1