目录
1. 进程间通信(IPC)
进程间通信的“进程间”指在两个进程间!
同一台主机之间进程通信方式:管道、信号量、共享内存、消息队列
两台主机之间:套接字(qq)
2. 进程间通信之管道
(1)管道的含义 单工 半双工 全双工
管道分为有名管道和无名管道,打开管道是在内存中分配一块空间。
例如:ps -ef | grep "bash",就是在进程ps -ef和进程grep "bash"之间,将ps -ef执行的结果写入管道,grep "bash"从管道中过滤提取符合要求的。
单工:通讯方向永远固定(数据发送方向固定)
半双工:一方发送,另一方接收(对讲机)
全双工:双方可同时发送(打电话)
管道的通讯方式为半双工(一端写入,另一端读出)!!
(2)创建管道
用mkfifo创建管道文件(有名管道),文件大小永远为0,因为管道是在内存分配空间。
用open()打开管道(打开管道分配的空间在内存里),read()从管道中读数据,write()向管道中写数据,close()关闭管道。
(3)管道读写数据原理
对于内核为管道分配的空间按照一字节一字节划分,定义头指针控制向管道写入数据,尾指针控制从管道读数据。
头指针随着向管道写入数据而向后移动同时尾指针随着从管道读出数据向后移动,边写边读可以循环起来,直至头指针循环到尾指针所指处代表管道已写满(尾指针循环到头指针所指处代表管道被读空)。
任意时刻头指针与尾指针的距离为当前时刻管道中的数据长度。
2.有名管道 任意进程通信
(1)有名管道的代码实现
p1.c:只写方式打开管道,向管道写入数据,关闭管道
p2.c:只读方式打开管道,从管道中读数据,关闭管道
read()的返回值为读入数据的长度。
p1.c:
#include<stdio.h>
#include<fcntl.h>//open read write close
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<signal.h>
void fun(int sig)
{
printf("sig=%d\n",sig);
exit(0);//读端关闭,写端收到信号,exit(0)终止进程
}
//p1.c向管道写入数据(读端关闭,写端继续写入会触发异常,系统发送SIGPIPE信号中止程序)
int main()
{
signal(SIGPIPE,fun);
int fdw=open("fifo",O_WRONLY);
printf("fdw=%d\n",fdw);
if(fdw==-1)
{
exit(1);
}
while(1)
{
printf("input:\n");
char buff[128]={0};
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
write(fdw,buff,strlen(buff)-1);
}
close(fdw);
exit(0);
}
p2.c:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>//open read write close
//p2.c从管道中读出数据
int main()
{
int fdr = open("fifo", O_RDONLY);
if(fdr==-1)
{
exit(1);
}
printf("fdr=%d\n", fdr);
while(1)
{
char buff[128] = {0};
int num = read(fdr, buff, 128);
if(num<=0)
{
break;
}
printf("read:%s num=%d\n",buff,num);
}
close(fdr);
exit(0);
}
(2)管道重点知识点总结
- 当写端关闭时,读端解除阻塞直接返回(返回值为0)。
- 当读端关闭时,写端无法再向管道写入数据,会触发异常(系统会发送信号13 SIGPIPE中止程序)。因此读端关闭时写端坚决无法向管道再写入数据。
- 向管道写入的数据在内存中,从管道读数据是从内存中读!
- 管道的通讯方式为半双工!
- 有名管道和无名管道的区别:有名管道可以在任意两个进程间通信!!无名管道只能在父子进程间通信!!
- 管道写满(指内核为管道开辟的空间写满)时写操作会阻塞;管道为空时读操作会阻塞。
3. 无名管道 父子进程间通讯
(1)创建无名管道+无名管道通信原理
使用pipe()方法创建管道,参数为有两个元素的数组pipefd,通过pipefd[0]从管道读数据,通过pipefd[1]向管道写数据。
父子进程一个向管道写数据,一个从管道读数据。
(2)父子进程无名管道通信代码
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
int main()
{
int fd[2];
int res=pipe(fd);//创建无名管道 fd[0]:读,fd[1]:写
if(res==-1)
{
exit(1);
}
pid_t pid=fork();
if(pid==-1)
{
exit(1);
}
else if(pid==0) //子进程读(关闭写端)
{
close(fd[1]);
char buff[128]={0};
read(fd[0],buff,127);
printf("buff=%s\n",buff);
close(fd[0]);
}
else
{
close(fd[0]); //父进程写(关闭读端)
write(fd[1],"hello",5);
close(fd[1]);
}
exit(0);
}
4. 文件描述符的复制 dup2
用新的描述符(参数2)覆盖旧的描述符(参数1)。这样映射到文件表中即:使用描述符oldfd或newfd访问的为同一文件(均为旧描述符对应的文件)。
如下示例为:用新的描述符标准输出1和标准错误输出2取代文件描述符fd,这样printf函数调用write(1,...)本来向屏幕输出变为向文件a.txt输出。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
int main()
{
int fd = open("a.txt", O_WRONLY | O_CREAT, 0600);
if(fd==-1)
{
perror("open error");
}
//用新的描述符标准输出1和标准错误输出2取代文件描述符fd,这样printf函数调用write(1,...)本来向屏幕输出变为向文件a.txt输出
dup2(fd, 1);
dup2(fd, 2);
printf("Hello,my dup2!\n"); // 等价于write(1,"Hello,my dup2!\n",15);
exit(0);
}
dup2实质:重定向!例如ls > a.txt, 将ls原本输出到屏幕的内容重定向输出到文件a.txt中(用标准输出1和标准错误输出2替代a.txt的文件描述符)。