进程间通信方法:管道(命名管道),共享内存区,信号量,消息队列,套接字
管道
管道在内存中分配空间,如果断电则其中的数据就不在了。
必须有至少一个读和一个写
对管道的操作只有只读或只写两种方式,没有读写——这种形式是未定义的
如果程序中只有读或只有写则程序会被阻塞住。
有名管道(fifo)
有名管道可以在任意两个进程间通信,有名字。
fifo文件的大小永远为0
A程序:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int fdw = open("fifo",O_WRONLY);//打开fifo管道,只写,fdw为文件描述符
if(fdw == -1)exit(1);//打开失败
printf("fdw=%d\n",fdw);
while(1)//输入数据
{
printf("input:\n");
char buff[128]={0};//创建一个空间
fgets(buff,128);//存放输入的东西
if(strncmp(buff,"end",3)==0)break;//如果前三个字符为end则退出输入
write(fdw,buff,strlen(buff));//写
}
close(fdw);
exit(0);
}
B程序:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int fdr = open("fifo",O_RDONLY);//打开fifo管道,只读形式
if(fdr == -1)exit(1);//打开失败,退出
printf("fdr==%d\n",fdr);
while(1)//一直读
{
char buff[128]={0};//创建一个空间
int n = read(fdr,buff,127);//读文件描述符fdr指向的文件的内容到buff中
if(n==0)break;//当读不到时退出程序
printf("read(%d):%s",n,buff);
}
close(fdr);
exit(0);
}
写数据的一端一旦关闭,则读数据的一端也会被关闭,读的返回值为0。
读关闭,写产生异常,产生信号(SIGPIPE)13
要一起运行ab程序管道才能用,否则会被阻塞,可以开两个终端使用。
无名管道(pipe)
无名管道只能在父子进程间通信
管道的通信方式:半双工,允许二台设备之间的双向数据传输,但不能同时进行
其他的通信方式:
单工:不能同时收发,数据传输只支持数据在一个方向上传输
全双工:收发可以同时进行
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int fd[2];//fd[0] r,fd[1] w
if(pipe(fd) == -1)//创建管道失败
{
printf("pipe err\n");
exit(1);
}
pif_t pid = fork();//复制进程
if(pid == -1)//复制失败
{
printf("fprk err\n");
exit(1);
}
if(pid == 0)//子
{
close(fd[1]);//半双工,关闭写,下面要读
char buff[128] = {0};
read(fd[0],buff,127);
printf("child buff=%s\n",buff);
}
else//父
{
close(fd[0]);//关闭读
write(fd[1],"hello",5);//将hello写入fd[1]
close(fd[1]);
}
exit(0);
}
管道的实现和顺序队列类似
信号量(sem)
信号量表示可用资源的数目,主要用来同步进程。
信号量的值如果只取 0,1,将其称为二值信号量。如果信号量的值大于 1,则称之为计数信号量;
pv操作:
可以进行原子操作,+1(v操作)释放资源,-1 (p操作)获取资源(用一个资源,资源减少)
一般取正数值,0代表资源没有了,p操作会被阻塞;
临界资源:同一时刻,只允许被一个进程或线程访问的资源(一个用完另外一个才能用)
临界区:访问临界资源的代码段
a程序:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
for(int i=0;i<5;i++)
{
printf("A");
fflush(stdout);//刷屏
int n = rand()%3;//模拟做了一会其他的东西再继续
sleep(n);
printf("A");
fflush(stdout);
n = rand()%3;
sleep(n);
}
}
b程序:把a程序的输出A改成B
如果没有pv操作,会变成这样AB交叉输出,ab程序同时使用资源:
封装信号量
创建、获取已存在的信号量:semget()
对信号量的值加减一semop()
赋初始值、销毁semctl()
失败返回负一
首先:sem.h头文件声明一下
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/sem.h>
//需要用这个联合体内的val初始化信号量的值
union semun
{
int val;
struct semid_ds*buf;
unsigned short *array;
struct seminfo *__buf;
};
void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
实现pv操作的封装
不同的进程使用相同的key值可以获取同一个信号量
#include"sem.h"
static int semid = -1;//信号量
void sem_init()
{
semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//全新创建一个信号量
if(semid==-1)//全新创建失败
{//获取以前的
semid = semget((key_t)1234,1,0600);
//如果还是获取失败就是失败了
if(semid==-1)
{
printf("semget err\n");
}
else//初始化
{
union semun a;
a.val = 1;
if(semctl(semid,0,SETVAL,a)==-1)
{
printf("semctl setval err\n");
}
}
}
}
void sem_p()
{
struct sembuf buf;
buf.sem_num = 0;//下标
buf.sem_op = -1;//p操作
buf.sem_flg = SEM_UNDO;//防止程序崩溃,崩溃自动执行v操作
if(semop(semid,&buf,1)==-1)//找semid第0个元素-1
{
printf("op p err\n");
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_num = 0;//下标
buf.sem_op = 1;//v操作
if(semop(semid,&buf,1)==-1)
{
printf("op v err\n");
}
}
void sem_destroy()
{
if(semctl(semid,0,IPC_RMID)==-1)
{
printf("semctl rm err");
}
}
将封装实现好的pv操作加到程序中
int main()
{
sem_init();//创建或获取信号量id
for(int i=0;i<5;i++)
{
sem_p();//p
printf("A");
fflush(stdout);
int n = rand()%3;
sleep(n);
printf("A");
fflush(stdout);
sem_v();//v
n = rand()%3;
sleep(n);
}
sleep(10);
sem_destroy();//销毁
}
a已经销毁了信号量,b最后就不用销毁了
最后同时运行ab程序,不会再出现两个程序同时使用资源的行为了,如下AA完一次再BB,然后AA……
ipcs可以查看消息队列、共享内存、信号量的使用情况,ipcrm可以对它们进行删除操作
-s信号量,-m共享内存,-q消息队列
销毁是把一组信号量都销毁
共享内存(shm)
a、b的逻辑空间中的某段物理内存用的同一块,这样a、b进程可以利用这块共享内存通信,不用像管道一样还需要拷贝。
shmget()创建或获取共享内存,返回共享内存的id,失败返回-1;
int shmget(key_t key, size_t size, int shmflg);
shmget(key,size,shmfkg);
key: 不同的进程使用相同的 key 值可以获取到同一个共享内存
size: 创建共享内存时,指定要申请的共享内存空间大小
shmflg: IPC_CREAT IPC_EXCL
shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
void* shmat(int shmid, const void *shmaddr, int shmflg);
shmat()成功返回返回共享内存的首地址,失败返回 NULL
shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写
shmdt()断开当前进程的 shmaddr 指向的共享内存映射
shmdt()成功返回 0, 失败返回-1
int shmdt(const void *shmaddr);
test.c输出共享内存中的东西
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#include<string.h>
int main()
{
//获取共享内存
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
if(shmid==-1)
{
printf("shmid err");
exit(1);
}
//链接
char*p = (char*)shmat(shmid,NULL,0);
if(p==(char*)-1)
{
printf("shmat err");
exit(1);
}
//打印
while(1)
{
if(strncmp(p,"end",3)==0)break;
printf("%s\n",p);
sleep(1);
}
//断开
shmdt(p);
//销毁共享内存
if(shmctl(shmid,IPC_RMID,0)==-1)
printf("shmctl err\n");
}
main.c,往共享内存中写入东西
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#include<string.h>
int main()
{
//创建共享内存
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
if(shmid==-1)
{
printf("shmid err\n");
exit(1);
}
//把共享内存映射过来
char *s = (char*)shmat(shmid,NULL,0);//s指向共享内存
if(s == (char*)-1)
{
printf("shmat err");
exit(1);
}
while(1)
{
printf("input:\n");
char buff[128] = {0};
fgets(buff,128,stdin);
strcpy(s,buff);
if(strncmp(buff,"end",3)==0)
{
break;
}
}
//断开映射
shmdt(s);
exit(0);
}
为了让它写修改一次输出一次,可以使用信号量:
使用两个信号量s1==1,s2==0
更改后
main.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#include<string.h>
#include"sem.h"
int main()
{
//创建共享内存
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
if(shmid==-1)
{
printf("shmid err\n");
exit(1);
}
//把共享内存映射过来
char *s = (char*)shmat(shmid,NULL,0);//s指向共享内存
if(s == (char*)-1)
{
printf("shmat err");
exit(1);
}
sem_init();
while(1)
{
printf("input:\n");
char buff[128] = {0};
fgets(buff,128,stdin);
sem_p(SEM_W);
strcpy(s,buff);
sem_v(SEM_R);
if(strncmp(buff,"end",3)==0)
{
break;
}
}
//断开映射
shmdt(s);
exit(0);
}
test.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/shm.h>
#include<string.h>
#include"sem.h"
int main()
{
//获取共享内存
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
if(shmid==-1)
{
printf("shmid err");
exit(1);
}
//链接
char*p = (char*)shmat(shmid,NULL,0);
if(p==(char*)-1)
{
printf("shmat err");
exit(1);
}
//打印
sem_init();
while(1)
{
sem_p(SEM_R);
if(strncmp(p,"end",3)==0)break;
printf("%s\n",p);
sem_v(SEM_W);
}
//断开
shmdt(p);
//销毁共享内存
if(shmctl(shmid,IPC_RMID,0)==-1)
printf("shmctl err\n");
sem_destroy();
}
消息队列
ipcrm -q 0删消息队列
-s信号量,-m共享内存
在内存中创建
msgsnd()发送
msgrcv()接收
msgctl()移除
msggst()创建
a.c程序
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/msg.h>
struct mess//自己定义消息结构体
{
long type;
char buff[32];
};
int main()
{
int msgid = msgget((key_t)1234,IPC_CREAT|0600);
if(msgid==-1)
{
printf(“msggeterr”);
exit(1);
}
//写入数据,程序运行一次写入一次
struct mess dt;
dt.type = 1;//1号消息
strcpy(dt.buff,”hello1”);
//添加到消息队列
msgsnd(msgid,&dt,32,0);
}
再运行一遍a.c又加入一个消息,已用字节数:32->64
b.c程序
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/msg.h>
struct mess
{
long type;
char buff[32];
};
int main()
{
int msgid =msgget((key_t)1234,IPC_CREAT|0600);
if(msgid ==-1)
{
printf(“msgget err\n”);
exit(1);
}
struct mess dt;
//读取消息
msgrcv(msgid,&dt,32,1,0);//倒数第二个参数为指定读取几号类型消息,若为0,所有消息都可以读(不区分消息类型)。
printf(“dt.buff=%s\n”,dt.buff);
}
读消息会取出消息队列中的消息