区别:
有名管道在任意两个进程间通信
无名管道在父子进程间通信
mkfifo
open()
close()
read()
write()
我们创建一个管道
我们写一个程序,给管道写入数据,再从另一个程序读取数据
这样看好像用文件操作也可以实现:通过一个程序写入数据,另一个文件读取数据。但是这样做有一些问题:比如我们并不知道什么时候给文件写入了数据,不知道什么时候去从文件读取数据,再一个这样使用的磁盘储存的速度非常的慢,与管道相比慢很多倍
写入管道代码
#include
#include
#include
#include
#include
#include
int main()
{
int fd = open("fifo",O_WRONLY);//打开管道文件 (默认打开当前位置的fifo)
assert(fd!=-1);
printf("fd=%d\n",fd);
while(1)
{
printf("input:\n");
char buff[128] = {0};
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
write(fd,buff,strlen(buff));
}
close(fd);
}
读取管道代码
#include
#include
#include
#include
#include
#include
int main()
{
int fd = open("fifo",O_RDONLY);//打开管道文件
assert(fd!=-1);
printf("fd=%d\n",fd);
while(1)
{
char buff[128] = {0};
if(read(fd,buff,127)==0)
{
break;
}
printf("read:%s\n",buff);
}
close(fd);
}
编译运行
当我们只运行a
,但是并没有输出我们想要的内容(fd),只有一个程序打开管道是阻塞住的,因为需要两个程序同时打开管道才有用(就好像整个世界只有你一个有手机,等于你也没有,你并没有可以打电话的对象)
我们再执行b
这下才会打印出我们想要的结果
无名管道
pipe
创建无名管道使用这个方法可以直接创建得到在管道读数据与写数据的描述符,相对简单方便,父子进程通信有限考虑无名管道
我们写一段代码来看一下
通过帮助手册查看,可以看到需要传入两个元素的整型数组,数组一个代表读文件描述符,另一个代表写文件描述符
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{
int fd[2];
assert( pipe(fd)!=-1 ); //fd[0] read 读文件描述符, fd[1] write 写文件描述符
pid_t pid = fork();
assert(pid!= -1);
if(pid == 0)
{
close(fd[1]);//进行读操作,我们直接关闭写文件描述符
char buff[128] = {0};
read(fd[0],buff,127);
printf("child read:%s\n",buff);
close(fd[0]);
}
else
{
close(fd[0]);//read 关闭读,与上面同理
write(fd[1],"hello",5);
close(fd[1]);
}
wait(0);
}
编译运行查看
就像我们的红绿灯,如果不进行管理,两个方向的成车辆会发生拥挤堵车,所以我们通过红绿灯控制,当一方通行,另一方进行等待来做到流畅运行,再举一个例子,比如我们有一个饮水机,有一个人再用另外其他的人就需要进行等待,我们程序也需要进行资源的控制,通过信号量来控制资源的访问
PV操作是原子操作,进行加一减一,P操作进行减一代表获取资源,V操作进行加一代表释放资源。例如我们商场的试衣间有三个位置,当有一个人去使用,我们进行P操作将信号量减一,再有人去使用的时候就要去观察信号量是不是0,如果是0,就代表三个试衣间都被占用,这样这个人就要等待信号量加一,便是阻塞住
控制程序对资源的使用的调控,大部分时候我们的信号量不是0就是1
代表计算机的软硬件资源(打印机),他们有一个特性就是某一时刻只允许一个进程或者线程进行使用,我们将这类资源成为临界组员
临界区就是控制访问临界资源的代码段
我们写两端程序来模拟打印机的使用
#include
#include
#include
#include
#include
int main()
{
int i = 0;
sem_init();
for(;i<5;i++)
{
printf("A");
fflush(stdout);
int n = rand()%3;//随机睡眠
sleep(n);
printf("A");
fflush(stdout);
n = rand()%3;//随机睡眠
sleep(n);
}
sleep(10);
}
#include
#include
#include
#include
#include
int main()
{
int i = 0;
sem_init();
for(;i<5;i++)
{
printf("B");
fflush(stdout);
int n = rand()%3;//随机睡眠
sleep(n);
printf("B");
fflush(stdout);
n = rand()%3;//随机睡眠
sleep(n);
}
sleep(10);
}
我们运行这两个程序
得到的就是乱掉的打印结果:ABABBBABAB
通过帮助手册看一下信号量的API接口
semget
:创建一个信号量,或者获取一个已经创建好的信号量,参数:整型key值;创建信号量的数量;标志位
int semget(key_t key, int nsems, int semflg );
semop
:参数:semget的返回值semid;结构体指针,成员有编号,进行PV哪个操作,标志位
int semop(int semid, struct sembuf *sops, size_t nsops);
semctl
:对信号量进行控制(初始化、移除等);参数:semid,对哪个信号量进行操作,命令
int semctl(int semid, int semnum, int cmd, ...);
我们写一段代码来看一看
//写一个.h文件定义结构体,和方法声明
#include
#include
#include
#include
//初始化需要的联合体,省略了一部分内容
union semun{
int val;//信号量大小
};
void sem_init();//初始化信号量
void sem_p();//P操作
void sem_v();//v操作
void sem_destroy();//销毁信号量
#include"sem.h"
static int semid = -1;//定义一个静态全局变量
void sem_init()
{
//首先需要进行判断是不是全新创建,是就创建不是就获得已有的信号量
//第一个参数key,任意数字就可以
//第二个参数,信号量大小
//第三个参数,标志位,代表全新创建,以及加上权限
semid = semget( (key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//标志位(全新创建)
if(semid == -1)
{
//不创建不需要写标志位,写上权限
semid = semget( (key_t)1234,1,0600);//不全新创建
if(semid == -1)
{
perror("semget error");//创建失败
}
}
else
{
//初始化操作
union semun a;
a.val = 1;
//第一个参数,id
//第二个参数,下标,只有一个信号量下标为0
//第三个参数,命令,设置值
//第四个参数,a
if( semctl(semid,0,SETVAL,a) == -1 )
{
perror("semctl init error");
}
}
}
void sem_p()
{
//结构体,sem_num下标;sem_op=-1 减一进行P操作;sem_flg标志位 代表若没有将信号量进行V操作,则异常终止则操作系统进行V操作
struct sembuf buf;
buf.sem_num = 0;//下标 第几个
buf.sem_op = -1;//p操作
buf.sem_flg = SEM_UNDO;
//参数:id;结构体指针;操作个数
if(semop(semid,&buf,1)==-1)
{
perror("p error");
}
}
void sem_v()
{
//与上同理
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if(semop(semid,&buf,1)==-1)
{
perror("v error");
}
}
void sem_destroy()
{
//参数:id;第二个参数无意义,会将整个信号量移除;命令 删除;最后一个参数可选,不给
if(semctl(semid,0,IPC_RMID) == -1)
{
perror("destory sem error");
}
}
我们对之前的两个模拟打印操作的代码进行修改
#include
#include
#include
#include
#include
#include"sem.h"
int main()
{
int i = 0;
//初始化
sem_init();
for(;i<5;i++)
{
sem_p();//若信号量为0 就会阻塞
printf("A");
fflush(stdout);
int n = rand()%3;//随机睡眠
sleep(n);
printf("A");
fflush(stdout);
sem_v();
n = rand()%3;
sleep(n);
}
sleep(10);//人为让a最后结束,b就不进行销毁
sem_destroy();
}
#include
#include
#include
#include
#include
#include"sem.h"
int main()
{
int i = 0;
sem_init();
for(;i<5;i++)
{
sem_p();
printf("B");
fflush(stdout);
int n = rand()%3;//随机睡眠
sleep(n);
printf("B");
fflush(stdout);
sem_v();
n = rand()%3;
sleep(n);
}
}
运行看到打印是有序的正确的
通过ipcs
可以查看信号量
前面的键转换为十进制就是我们之前设置的1234,程序结束在输入ipcs
查看
消息队列销毁
将一块物理内存映射到两个不同的进程空间中,当启用一个进程读取或者写入这块内存,对于另一个进程下这块空间也会发生改变
原本操作系统会对进程进行保护,不允许两个进程使用同样一块物理空间,共享内存通过接口打破了这种约定,我们来查看一下这些API接口
int shmget(key_t key,size_t size,int shmflg);
void* shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_da *buf);
我们写一段代码来看一下
#include
#include
#include
#include
#include
#include
int main()
{
//创建共享内存
//参数:
//给定的key值
//内存大小
//标志位,IPC_CREATE创建 以及权限
int shmid = shmget((key_t)1234,256,IPC_CREAT|0600);限
assert(shmid != -1);
//创建成功 可以通过ipcs命令查看
//映射内存
//参数:
//shmid 共享内存的id
//物理内存位置 一般给NULL
//权限标志 一般给0
char *s = (char *)shmat(shmid,NULL,0);
assert(s != (char*)-1);
//在共享内存写入数据
strcpy(s,"hello");
shmdt(s);//断开链接
}
编译运行后使用ipcs
查看
发现有一个编号0x4d2(1234),大小256的共享内存
我们再写一个读共享内存的代码
#include
#include
#include
#include
#include
#include
int main()
{
int shmid = shmget((key_t)1234,256,IPC_CREAT|0600);
assert(shmid != -1);
char *s = (char *)shmat(shmid,NULL,0);
assert(s != (char*)-1);
printf("%s",s);
shmdt(s);
}
消息队列的原理是这样的,我们往这个队列中添加消息并且消息有一个类型值,如上图我们有两个类型的消息一个是1,一个是2;进程会根据需要获取相应类型的消息,若没有相应类型消息则获取不到消息,当消息队列已满(达到最大值)则在添加消息会被阻塞
int msgget(key_t ket, int msqflg);
int msgsed(int msqid, const void *msqp, size_t msqsz,int msqflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msqsz, long msqtyp, int msqflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
我们写一段代码来看一下
#include
#include
#include
#include
#include
#include
//消息队列需要的结构体
struct mess
{
long type;
char buff[128];
};
int main()
{
//创建消息队列
int msgid = msgget((key_t)1234,IPC_CREAT|0600);
assert(msgid != -1);
struct mess dt;
dt.type = 1;//发生消息不能为0 接收可以是0代表接收任何类型消息
strcpy(dt.buff,"hello 1");//写入数据
//发送消息到消息队列
msgsnd(msgid,&dt,128,0);
}
我们编译运行这段代码,使用ipcs
查看
消息队列产生,当我们再次执行程序,消息会变成2
再写一段接收消息的消息队列代码
#include
#include
#include
#include
#include
#include
struct mess
{
long type;
char buff[128];
};
int main()
{
//IPC_CREAT,冷知识为什么是CREAT而不是CREATE,这是因为作者在编写的时候疏忽导致少写了一个E字母
int msgid = msgget((key_t)1234,IPC_CREAT|0600);
assert(msgid != -1);
struct mess dt;
//id,结构体指针(接收消息存放进去),buff数据大小,type类型(0类型消息 意味着全部接收),标志位0
msgrcv(msgid,&dt,128,1,0);
printf("read message:%s\n",dt.buff);
}
编译执行,并再次使用ipcs
查看,消息队列消息变成 0
删除消息队列
#include
#include
#include
#include
#include
#include
int main()
{
int msgid = msgget((key_t)1234,IPC_CREAT|0600);
assert(msgid != -1);
//创建消息队列 程序结束不会消失
msgctl(msgid,IPC_RMID,NULL);//id,命令,获取信息,删除为null
//控制消息队列
}
套接字涉及内容较多,我们在这里先不进行讲解,将会在后面单独拿出来进行详细完整的介绍。