IPC进程间通信
-
全称 Inter Process Communication
-
出现原因
- 不同进程之间的内存空间是隔离的,就连fork创建的子进程也无法和父进程共享内存空间
- 但相关的多个进程有交换信息的需求,所以产生了进程间通信
-
共有6种,下面一一解释
- 管道
- FIFO
- 信号量
- 共享存储
- 消息队列
- socket
管道
-
通常是指无名管道
-
特点
- 只能用于父子或兄弟进程之间
- 看成一种特殊文件,可以读写,但又只存在内存中
- 向管道传递数据时,先读的进程会把数据取走
- 因此,只用1个管道实现双向通信很难
- 因为进程A写完之后,要等进程B读走再写入,A才能继续执行读取,否则将会读到自己写入的数据
- 若想轻松实现双向通信,可以创建2个管道
- 因此,只用1个管道实现双向通信很难
-
原型
#include <unistd.h> int pipe(int fd[2]); -
当一个管道建立时,会创建两个文件描述符
fd[0]为读而打开fd[1]为写而打开- 要关闭管道,只需要关闭这两个文件描述符即可
- 单个进程创建管道没啥用,所以要fork出子进程来使用
-
例子
#include <stdio.h> #include <unistd.h> int main(int argc, char* argv[]){ int fd[2]; pid_t pid; char buff[20]; if (pipe(fd) < 0) { printf("Create Pipe Error!\n"); } pid = fork(); if (pid < 0) { printf("Fork Error!\n"); } else if (pid > 0) { close(fd[0]); //关掉父进程的读端 write(fd[1], "hello\n", 12); } else { close(fd[1]); //关掉子进程的写端 read(fd[0], buff, 20); printf("%s", buff); } return 0; }
FIFO
-
也称为命名管道
-
特点
- 是一种文件类型,有路径名与之关联
- 可在无关的进程之间交换数据
-
原型
#include <sys/stat.h> int mkfifo(const char* pathname, mode_t mode);- pathname —— 真实的文件路径
- mode_t —— 文件的读写权限
- 仅仅是创建了一个FIFO文件
- 在创建之后,要使用open函数打开,选项可用
O_RDONLY读或O_WRONLY写- 注意不能
O_RDWR又读又写,不然会读回自己的输出
- 注意不能
- open函数可以选择追加
| O_NONBLOCK非堵塞选项- 如果没追加,那只有在其他进程以写的方式打开此FIFO,当前进程才会返回
- 如果追加了,那当前进程无论如何都将返回
- 在创建之后,要使用open函数打开,选项可用
-
例子
- 服务端:用文件名创建并打开管道,监听有无内容
#include <stdio.h> #include <unistd.h> #include <sys/stat.h> int main(int argc, char* argv[]){ if (mkfifo("fifo1", 0777) < 0) { printf("Create FIFO Failed"); } int fd; int len; char buf[1024]; fd = open("fifo1", O_RDONLY); while (read(len = read(fd, buf, 1024)) > 0) { printf("read msg: %s", buf); } close(fd); return 0; } - 客户端:往文件里面写东西
#include <stdio.h> #include <unistd.h> #include <sys/stat.h> int main(int argc, char* argv[]){ int fd; char buf[1024]; fd = open("fifo1", O_WRONLY); n = sprintf(buf, "pid is", getpid()); write(fd, buf, n + 1) close(fd) return 0; }
- 服务端:用文件名创建并打开管道,监听有无内容
信号量
-
跟前面的IPC结构不同,它是一个计数器,用于实现进程的互斥与同步
-
特点
- 基于操作系统的PV操作,是原子级别
P是获取资源,信号量-1,如果减了后变负数,则挂起等待V是释放资源,信号量+1,如果发现有进程在等待,则唤醒它们
- 可加减任意数值,不限于1
- 基于操作系统的PV操作,是原子级别
-
分类
- 二值信号量:只能取0或者1
- 通用信号量:任意正整数
-
原型
#include <sys/sem.h> //创建信号量组,num_sems为信号量的个数,返回信号量组ID int semget(key_t key, int num_sems, int sem_flags); //操作信号量组 int semop(int semid, struct sembuf semoparray[], size_t numops); //控制信号量组 int semctl(int semid, int sem_num, int cmd, ...);- 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 < 0,请求 sem_op 绝对值的资源
- 这个操作与sem_flg有关
- sem_flg 指定IPC_NOWAIT,则出错返回
- sem_flg 没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,直到如下情况发生
- 当相应资源数能被满足,就将semncnt值减掉1,让sem_op减掉sem_op 绝对值
- 或者信号量被删除,则出错返回
- 这个操作与sem_flg有关
- 若sem_op = 0,进程阻塞直到信号量相应值为0
- 解释一下结构中的
- sembuf结构
共享存储
-
指两个或多个进程共享一个给定的存储区
-
特点
- 是最快的IPC,因为直接对内存操作
- 因为多个进程可能同时操作,所以要进行同步,这里要结合信号量去使用
-
原型
#include <sys/shm.h> //创建共享内存,要指定大小,返回shm_id int shmget(key_t key, size_t size, int flag); //连接共享内存,成功则返回指向共享内存的指针 void *shmat(int shm_id, const void *addr, int flag); //断开连接 int shmdt(void *addr); //控制共享内存 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);- 解释一下控制函数中的
cmd参数- 最常用的是
IPC_RMID,从系统中删除改共享内存
- 最常用的是
- 解释一下控制函数中的
消息队列
-
是消息的链接表,存放在内核中,具有独一无二的队列ID
-
特点
- 每个消息是一个数据块,允许有不同的类型,但是会有最大的长度限制
- 独立于发送和接收进程,进程终止时,队列内容不会被删除
- 可以随机查询,不一定要先进先出
-
原型
#include <sys/msg.h> //创建消息队列,返回队列ID int msgget(key_t key, int flag); //添加消息 int msgsnd(int msqid, const void *ptr, size_t size, int flag); //读取消息,可指定特定的type类型(数字) int msgrcv(int msqid, void *ptr, size_t size, long type,int flag); //控制消息队列 int msgctl(int msqid, int cmd, struct msqid_ds *buf); -
在下列两种情况下会创建,否则直接返回现有的(这样才能共用而实现通信)
- 1、没有生成过key对应的队列,且flag含IPC_CREAT
- 2、key为IPC_PRIVATE
socket
-
又称套接字,是对TCP/IP、UDP等协议进行封装后形成的API,属于传输层
- 主要用于跨主机的进程间通信
-
类型有两种
- streamSocket,流套接字
- 基于 TCP协议
- 采用
流的方式提供可靠的字节流服务
- datagramSocket,数据报套接字
- 基于 UDP协议
- 采用
数据报文提供数据打包发送服务
- streamSocket,流套接字
-
原型
#include <sys/socket.h> -
创建套接字,返回描述符sockfd
int socket(int domain, int type, int protocol);domain参数——套接字域,常见有两种- AF_INET IPv4
- AF_INET6 IPv6
type参数——套接字类型- SOCK_STREAM 字节流
- SOCK_DGRAM 不可靠的报文传递
- SOCK_SEQPACKET 可靠的报文传递
protocol参数——协议- 通常是0,表示根据上面两个参数选择唯一协议
-
绑定IP地址和端口(多用于服务端)
int bind(int sockfd, const struct sockaddr *addr, socklen_t len); -
开启监听(多用于服务端)
int listen(int sockfd, int backlog); -
建立连接(多用于客户端)
int connect(int sockfd , const struct sockaddr *addr, socklen_t len); -
数据传输
- 发送
ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags); - 接收
ssize_t recv(int sockdfd, void *buf, size_t nbytes, int flags);
- 发送
-
读取选项
int getsockopt(int sockdf, int level, int optname, void* optval, socklen_t* optlen); -
设置选项
int setsockopt(int sockfd, int level, int optname, const void* optval, socklen_t optlen);