产生背景:
fork 和 exec实现的通信很低效 -- 残疾通信
IPC包括:
管道:(无名和命名管道) 全双工和班双工
消息队列:
信号量;
共享存储:
Scoket,Streams -- 支持不同主机的IPC
管道:
特点:
1.默认无名管道-- 半双工(数据只在一个方向上流动)
2.只能在亲戚进程之间通信,父子,兄弟
3.特殊文件,可以用write , read 来写入,读取。不是普通文件,并不属于其他任何文件系统,只存在于内存#半双工:
1.同一时间只能单向通信,一读一写,不能同时写,同时读 -- 算两个
2.管道不存储数据,读完就没了
pipe 函数建立(无名)管道:
头文件:<unistd.h>
函数原型:
int pipe(int fd[2]);
参数-- 2个文件描述符 fd[0]--read fd[1]--write
返回值: 成功返回0,失败返回-1
怎么关闭管道 -- close关闭两个文件描述符 fd[0] fd[1]
=====================================
case -- 实现无名管道的使用:
#include<unistd.h>
#include <sys/types.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <sys/wait.h>int main()
{
int fd[2];
char readBuf[128]={0};
if(pipe(fd)==-1){
puts("open pipe fail!!!");
}
int pid=fork();
if(pid<0){
puts("open fork fail!!!");
}
else if(pid>0){
sleep(3); // 先让子进程执行,父进程先睡觉,发现子进程read一直阻塞程序,直到父进程睡完写入数据
puts("this is father to write.");
close(fd[0]); //关闭读,只写 -->半双工
write(fd[1],"Hello from father",strlen("Hello from father"));
wait(NULL);
}
else{
puts("this is son to read.");
close(fd[1]);//关闭写,只读
read(fd[0],readBuf,128);
printf("this is from father:%s\n",readBuf);
exit(0);
}return 0;
}
=============================
命名管道mkfifo
存在形式: 以 文件 的形式存放在磁盘中
open 一个 mkfifo时,是否设置非阻塞标志的区别(O_NONBLOCK)
默认没有指定 O_NONBLOCK (是否设置非阻塞标志)
只读open要阻塞到某个其他进程为写而打开此FIFO
只写open要阻塞到某个其他进程为读而打开此FIFO
if 指定 O_NONBLOCK : 一般不用
只读open立即返回,只写open出错返回-1,if没有进程已经为读打开FIFO,则errno设置为ENXIO
case1:实现命名管道:
打开基本读写,防止阻塞
//读程序
#include<unistd.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<errno.h>int main()
{
if(mkfifo("./file",0600)==-1 && errno!=EEXIST){//判断错误原因是否是已经存在
printf("mkfifo failuer\n");
perror("why");
}
int fd=open("./file",O_RDONLY);
printf("open read success\n");
return 0;
}// 写程序
#include<unistd.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<errno.h>int main()
{int fd=open("./file",O_WRONLY);
printf("open write success\n");
return 0;
}
==================================
case2--加入数据一致读写:
读文件
#include<unistd.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<errno.h>int main()
{
char readBuf[1024]={0};
int nread=0;
if(mkfifo("./file",0600)==-1 && errno!=EEXIST){
printf("mkfifo failuer\n");
perror("why");
}
int fd=open("./file",O_RDONLY); //只读打开
printf("open read success\n");
while(1){ //时刻等待接收
nread=read(fd,readBuf,30);
printf("read num:%d\nmessage:%s\n",nread,readBuf);
}
close(fd);//用完关掉
return 0;
}
写文件:
#include<unistd.h>
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<errno.h>
#include<string.h>int main()
{
int cnt=0;
char * str="The message from fifo.";
int fd=open("./file",O_WRONLY); // 只写打开
printf("open write success\n");
while(1){
sleep(1); //每秒写入一次
int nwrite=write(fd,str,strlen(str));
}
close(fd);
return 0;
}
======================================
消息队列:
linux内核中管理消息队列的链表
原理:
算是一个中间桥梁 -- B把数据写到内核中的消息队列,A读取队列中B的数据,当A,B使用同一个队列通讯的时候,实现A,B的通信
特点:
1.消息队列是面向记录的,其中的消息、具有特定的格式和特定的优先级
2.消息队列独立与发送和接收进程,进程终止,消息队列也不会清除
3.消息队列可以实现信息的随机查询,按消息类型读取(链表的数据结构特性)
实现A,B通信:
A:1.获取队列 2读
B:1.获取队列 3.写
消息队列API
头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
-- 创建/打开消息队列,成功返回队列ID,失败返回-1:
函数原型
int msgget(key_t key, int msgflg);
//创建新的消息队列:
1.if 没有键值与key相同的消息队列,并且flag 包含 IPC_CREAT标志位
2.key的参数为 IPC_PRIVATE
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
-- 添加消息,成功返回0,失败-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
-- 读取消息,成功返回队列数据长度,失败返回-
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgtyp ==0,返回消息队列中的第一个消息
msgtyp >0 , 返回消息队列中类型为msgtyp 的第一个消息
msgtyp <0,返回队列中消息类型绝对值小于等于msgtyp 绝对值的消息,if存在多个,则去类型在,制最小的消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
-- 消息队列的的控制(删除):成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//可以看到四个API的头文件都一样
======================================
case1:实现消息共享:
//获取队列 写入队列
#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<string.h>
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf
{
long mtype;
char mtext[128];
};
int main()
{
struct msgbuf sendBuf={888,"this is message from que."};
int msgId=msgget(0x1234,IPC_CREAT|0777);//创建消息队列
if(msgId == -1){
puts("get message que failed\n");
}
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);// 向队列中添加消息return 0;
}// 获取队列,读取队列消息
#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf
{
long mtype;
char mtext[128];
};
int main()
{
struct msgbuf readBuf;
int msgId=msgget(0x1234,IPC_CREAT|0777);// 获取消息队列
if(msgId == -1){
puts("get message que failed\n");
}
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0); //读取队列中的消息
printf("read from message que :%s\n",readBuf.mtext);return 0;
}
=================================
case2:消息队列实现双全工 同时收发
#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<string.h>
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf
{
long mtype;
char mtext[128];
};
int main()
{
struct msgbuf readBuf;
int msgId=msgget(0x1234,IPC_CREAT|0777);
if(msgId == -1){
puts("get message que failed\n");
}
//先接收
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
printf("read 1 from message que :%s\n",readBuf.mtext);
//再发送
struct msgbuf sendBuf={988,"this is return message from que."};//换一个接口988
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
return 0;
}
#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<string.h>
// int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
struct msgbuf
{
long mtype;
char mtext[128];
};
int main()
{
struct msgbuf sendBuf={888,"this is send message from que."};
struct msgbuf readBuf;
int msgId=msgget(0x1234,IPC_CREAT|0777);
if(msgId == -1){
puts("get message que failed\n");
}
//先发送
msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);//再接收
msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);//换一个接口988
printf("read 2 from message que :%s\n",readBuf.mtext);
return 0;
}
==================================
共享内存:
描述:
A,B之间创建一块共享内存,让他们的信息交流无障碍(使用频率高 -- 先进好用)
相关API:
头文件: <sys.shm.h>
// 创建/获取一块共享内存,成功返回内存ID,失败-1
int shmget(key_t key, size_t size, int shmflg);// 注意 共享内存大小必须以M对齐
// 连接共享内存到当前进程地址空间 , 成功返回指向共享内存的指针,失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflg);
//第二 第三个参数一般都是0,第二个0 -- linux 内核自动安排共享内存,第三个0 -- 共享内存权限可读可写
//断开与共享内存的连接,成功返回0,失败-1
int shmdt(const void *shmaddr);
#include <sys/ipc.h>
#include <sys/shm.h>
//控制共享内存的相关信息,成功0,失败-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf); //第二第三参数: IPC_RMID 0--不关心详细信息
步骤:
1. 先创建/打开共享内存 -- shmget
2.映射 --*shmat
3.数据 -- strcpy 写入数据(操作自己的额内存怎么写就怎么写)
4.释放内存空间 -- shmdt
5.删除共享内存 -- shmctl
sleep在 unistd.h中
linux 指令查看系统中的共享内存: ipcs -m
ipcrm -m 共享内存id -- 删除指定共享内存
==============================
case:
-- 写
#include <sys/types.h>
#include <sys/shm.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>int main()
{// int shmget(key_t key, size_t size, int shmflg);
// 注意 共享内存大小必须以M对齐
key_t key;
int shmId;
char *shmaddr;
key=ftok("./",1);
shmId=shmget(key,1024*2,IPC_CREAT|0666);//创建共享内存
if(shmId == -1){
puts("shmget fail!");
exit(-1);
}
//void *shmat(int shmid, const void *shmaddr, int shmflg); //连接当前进程地址到共享内存
//第二 第三个参数一般都是0,第二个0 -- linux 内核自动安排共享内存,第三个0 -- 共享内存权限可读可写shmaddr=shmat(shmId,0,0);
puts("shmat ok.");
strcpy(shmaddr,"mxjun"); // 在这段地址中写入
//int shmdt(const void *shmaddr);
sleep(5);
shmdt(shmaddr);//释放内存空间
// int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//第二第三参数: IPC_RMID 0--不关心详细信息
shmctl(shmId,IPC_RMID,0); //删除(杀死)共享内存
puts("quit");return 0;
}
---读#include <sys/types.h>
#include <sys/shm.h>
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>int main()
{// int shmget(key_t key, size_t size, int shmflg);
// 注意 共享内存大小必须以M对齐
key_t key;
int shmId;
char *shmaddr;
key=ftok("./",1);
shmId=shmget(key,1024*2,0);
if(shmId == -1){
puts("shmget fail!");
exit(-1);
}
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//第二 第三个参数一般都是0,第二个0 -- linux 内核自动安排共享内存,第三个0 -- 共享内存权限可读可写shmaddr=shmat(shmId,0,0);
puts("shmat ok.");
printf("data= %s\n",shmaddr);
//int shmdt(const void *shmaddr);
sleep(5);
shmdt(shmaddr);
// int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//第二第三参数: IPC_RMID 0--不关心详细信息
puts("quit");
return 0;
}
=================================
Linux 信号(Signal):
对于linux来说,信号是软中断,许多主要重新都需要中断。ctrl + c退出 也是中断程序。
但A,B同时写共享内存的一块地方的时候就会出现信息杂糅 -- 信号来处理
信号定义在"signal.h"头文件里
指令kill -l: 查看系统中的所有信号;
信号处理方式:
忽略 (SIGNKILL SIGNSTOP不能忽略)
捕捉: 类似槽函数,告诉内核希望如何处理某信号 -- 信号的最大意义 -- 实现异步通信
默认: 系统对每个信号都有其默认处理动作kill -9 pid -- 杀死进程 9--对应信号SIGNKILL
=====================================
signal:
信号处理函数;
signal
高级:
sigaction
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函数原型:
signal(SIGINT,handler); -- 收到SINGINT 信号的时候调用handler函数
case1:不给ctrl + c退出的程序(ctrl +z -- signstop) -- 检测你发送的信号
#include<stdio.h>
#include<signal.h>
void handler(int signum){
printf("get signum=%d\n",signum);
switch (signum)
{
case 2:
printf("SIGINT\n");
break;
case 9:
printf("SIGKILL\n");
break;
case 10:
printf("SIGUSR1\n");
break;default:
break;
}printf("never be killer\n");
}int main()
{
signal(SIGINT,handler);
signal(SIGKILL,handler);
signal(SIGUSR1,handler);
while(1);return 0;
}
kill函数
头文件:
#include <sys/types.h>
#include <signal.h>
函数原型:
int kill(pid_t pid, int sig); // 使用kill发信号
//ASCLL码值转整形数
#include <stdlib.h>
int atoi(const char *nptr);
long atol(const char *nptr);
long long atoll(const char *nptr);
==================
case: kill函数发信号
#include<stdio.h>
#include<signal.h>
#include<sys/types.h>
#include<stdlib.h>
int main(int argc,char**argv)
{
int signum;
int pid;
char cmd[128]={0};
signum=atoi(argv[1]); // 注意转换为字符串
pid=atoi(argv[2]);
printf("num=%d\t pid=%d\n",signum,pid);
// int kill(pid_t pid, int sig);
// kill(pid,signum);sprintf(cmd,"kill -%d %d",signum,pid);
system(cmd);
puts("send signal ok");
return 0;
}
=============================
sigaction
头文件
#include <signal.h>
函数原型
int sigaction(int signum, const struct sigaction *act,
struct sigaction *oldact);
发信号:sigqueue
头文件
#include <signal.h>
函数原型
int sigqueue(pid_t pid, int sig, const union sigval value);
case:sigaction实现 数据接收
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>// int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
void handler(int signum, siginfo_t *info, void *context)
{
printf("signum=%d\n", signum);
if (context != NULL)
{
printf("get data = %d\n", info->si_int);
printf("get data = %d\n", info->si_value.sival_int);
printf("from:%d\n",info->si_pid);
}
}int main()
{
struct sigaction act;
printf("pid = %d\n",getpid());
act.sa_sigaction = handler;
act.sa_flags = SA_SIGINFO;
sigaction(SIGUSR1, &act, NULL);
while (1);
return 0;
}---------------
发数据#include<signal.h>
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include<stdlib.h>
// int sigqueue(pid_t pid, int sig, const union sigval value);
int main(int argc,char**argv)
{
int signum;
int pid;signum=atoi(argv[1]);
pid=atoi(argv[2]);
//printf("num=%d\t pid=%d\n",signum,pid);
union sigval value;
value.sival_int=100;//待发送 的数据
sigqueue(pid,signum,value);//发数据
printf("pid = %d\n",getpid());
puts("done");
return 0;
}
=====================================
信号量的三个API:
头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
// 创建/获取一个信号量组:if成功放回信号量集ID,失败放回-1
int semget(key_t key, int nsems, int semflg);
//对信号量组进行操作,改变信号量额值,成功0,失败-1
int semop(int semid, struct sembuf *sops, size_t nsops);
//int semtimedop(int semid, struct sembuf *sops, size_t nsops,
const struct timespec *timeout);
// 控制信号量相关信息
int semctl(int semid, int semnum, int cmd, ...);
case1:初始化信号量:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>union semun
{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};int main(int argc, char **argv)
{
int semid;
key_t key;
key = ftok(".", 2);
// int semget(key_t key, int nsems, int semflg);
// 获取创建信号量
semid = semget(key, 1, IPC_CREAT | 0666); // 1 --希望只有一个信号量
union semun initsem;
initsem.val = 1;
// 初始化信号量
semctl(semid, 0, SETVAL, initsem); // 0--操作第0个信号量
// SETVAL -- 设置信号量的值
int pid = fork();
if(pid>0){
puts("this is father");
//去拿🔓//🔓放回去
}
else if(!pid){
puts("this is son");
}
else{
puts("fork error!!!");
}
return 0;
}
============================
case2:控制 信号量控制子进程先进行:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>union semun
{
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
void pGetKey(int id)
{
//int semop(int semid, struct sembuf *sops, size_t nsops);
struct sembuf set;
set.sem_num=0;
set.sem_op=-1;
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("get key\n");}
void vPutBackKey(int id)
{
//int semop(int semid, struct sembuf *sops, size_t nsops);
struct sembuf set;
set.sem_num=0;
set.sem_op=1;
set.sem_flg=SEM_UNDO;
semop(id,&set,1);
printf("put back key\n");}
int main(int argc, char **argv)
{
int semid;
key_t key;
key = ftok(".", 2);
// int semget(key_t key, int nsems, int semflg);
// 获取创建信号量
semid = semget(key, 1, IPC_CREAT | 0666); // 1 --希望只有一个信号量
union semun initsem;
initsem.val = 0; // 房间钥匙被拿
// 初始化信号量
semctl(semid, 0, SETVAL, initsem); // 0--操作第0个信号量
// SETVAL -- 设置信号量的值
int pid = fork();
if(pid>0){
//去拿🔓
pGetKey(semid);
puts("this is father");
//🔓放回去
vPutBackKey(semid);
}
else if(!pid){
puts("this is son");
//🔓放回去
vPutBackKey(semid);
}
else{
puts("fork error!!!");
}
return 0;
}
// 初始化,把锁拿走,父亲开不了门,不能执行,儿子先放钥匙进去才能让父亲开门,so每次都是子进程先运行
====================================
进程间的通信-IPC:
1.无名管道
2.命名管道
3.消息队列
4. 共享内存
5信号
6.信号量