进程间通信 IPC
1.无名管道
2.有名管道
3.信号
4.共享内存
5.消息队列
6.信号量
7.SOCKET
一.无名管道
创建无名管道:
int pipe(int pipefd[2]);
参数 pipefd 读写端
返回值 成功与否 0:成功
注:fd[0]表示read端,fd[1]表示写端;
注:子进程继承父进程的fd,读端和写端
注:这是父进程
注:这是子进程
父进程向子进程发送消息,关闭父进程read端和子进程write端,这样有两个好处,使用fd时不会出错保证半双工通信。
二.有名管道
创建有名管道
int mkfifo(const char *pathname, mode_t mode);
参数:pathname 文件名
mode 文件权限
返回值:成功返回0
先创建一个有名管道,p为管道文件
先从一端写入管道
再从管道中读取
注:read端和write端必须同时开启,否则不能执行任意一个。
三.信号:由一个进程向另一个进程发送中断消息,可通过kill –l查看,共64个信号。
1)unsigned int alarm(unsigned int seconds); 自己给自己发信号
参数 : 定时器倒计时时间,精度:秒
返回值:1 之前有定时器计时,返回剩余秒数,否则返回0,出错返回-1
2)int kill(pid_t pid, int sig);
参数:pid 目标进程ID号
sig 信号的值
注:这个kill为系统调用,与命令kill用法相似
3)信号注册函数
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数:signum 指定接受信号的种类
handler 相关的信号处理函数
定义新的数据类型:
信号函数 :
注:如果碰到14号信号,则用handle函数处理。
四.本次套接字:Socket
本地地址结构(bind时用):
<sys/un.h>
struct sockaddr_un
{
sa_family_t sun_family; //AF_UNIX
char sun_path[108]; // 套接字文件的路径
};
1 tcp方式
1 设置socket套接口协议为PF_UNIX
2 服务端注意绑定地址不是sockaddr_in,而是sockaddr_un
3 客户端需要connect时设置的结构体是sockaddr_un,对应文件地址应与服务端设置一致
2 udp方式
1 设置socket套接口协议为PF_UNIX
2 服务端注意绑定地址不是sockaddr_in,而是sockaddr_un
3 如果需要交互的话,客户端也需要bind一个客户端的套接口文件,不能与服务端的socket文件同名
本地套接字udp互相通信,为什么客户端需要绑定一个与服务端不相同的socket文件?
在发送数据过程中,客户端知道服务端绑定的socket文件,所以,客户端能够发送数据给服务端。
但是如果客户端没有绑定socket文件,相当与只发送数据,但服务端不知道谁发过来的。
所以,互相通信时,客户端需要绑定一个socket文件,以便服务端向客户端发送数据。
系统IPC
一.共享内存
1.创建共享内存:int shmget(key_t key, size_t size, int shmflg);
参数: key 键值:通信双方约定的共享内存的代号
size 共享内存片段大小:以字节为单位
shmflg 标志位:类比于open
返回值:共享内存的id
注: ICP_CREAT表示创建一个共享内存,0666为其权限。
2 将共享内存映射进当前进程中:void *shmat(int shmid, const void *shmaddr, int shmflg);
参数 shmid:共享内存的id
shmaddr:要被映射的真实的物理地址 一般设置为NULL
shmflg 标志位 0使用默认标志
返回值:映射之后的私有地址
3 释放映射进来的地址:int shmdt(const void *shmaddr);
二.信号量:与sem_init创建的信号量类似;本质为个计数器
1.创建: int semget(key_t key, int nsems, int semflg);
返回值:信号量集id 失败 -1
参数:key 键值:IPC_PRIVATE
nsems:信号量集中信号量个数
Semflg:IPC_CREAT
三.消息队列
1.创建:int msgget(key_t key, int msgflg);
返回值:消息队列id 失败 -1
参数:key:键值 msgflg:标志位 0使用默认标志
2.获取和设置消息队列属性
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
有点复杂!!!
3.新消息加入队尾
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
返回值:成功 0 失败 -1
参数:msgp:消息类型和内容,可为任意类型结构体,但第一字节必为long类型且必大于0;
Msgsz:消息大小,字节
Msgflg:标志位,IPC_NOWAIT:当消息队列满时,立即返回错误;
4.从消息队列读取消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
说明:从msqid消息队列中读取消息并存于msgp中,并删除此消息;
返回值:成功:读到的数据长度 失败 -1
参数:msgp:存放消息的结构体,类型要与msgsnd函数发送的结构体一致
Msgsz:要接受消息的大小
Msgtyp:0 接收第一个消息
>0 接收类型等于Msgtyp的第一个消息
<0 接收类型小于等于Msgtyp绝对值的第 一个消息
Msgflg: 0 阻塞接收消息
几种进程间通信方式比较
从同步、数据传输、传输方式、进程数量的角度分析
1.无名管道:同步,只能用于亲缘进程通信,半双工,通讯时读写时机较难控制,且不适合大量进程相互通信;
2.有名管道:同步,用于非亲缘进程通信,半双工,大量进程互相通信时,创建的管道文件较多,不易维护,所以不适合大量进程互相通信;
3.信号:异步通信,不能携带数据,只起到通知作用;
4.共享内存:同步,不宜过大,因为在内核中分配内存,相当于多进程的全局变量,访问共享内存时需要控制访问顺序(同步互斥);也不适用于多进程通信;
5.消息队列:数据量不宜过大,因为在内核中分配内存,指定读取数据进程比较复杂且不宜大量进程间通信;
6.信号量,同步和互斥作用不能携带数据;
7.本地进程间通信socket:
专门用于本地通信的套接字,网络套接字绑定时是IP和端口,本地套接字是套接字文件;本地有点像网络套接字和管道的结合,跟其他进程间通信比较效率更高、因为只是在应用层之间,不经过协议栈,不用打包拆包;
可以进行多进程间互相通信,使用IO多路复用方式,服务端对通信描述符处理为串行处理,所以不需要进程同步互斥控制,支持tcp和udp方式;
tcp方式,服务端与客户端只需要一个套接字文件进行数据交换,比较容易实现,代码简单,如果使用udp方式,客户端与服务端需要单独绑定各自的套接字文件,当多个客户端时,套接字文件较多,所以,本地套接字通信使用tcp;