【1】进程间的通信方式
1.传统的进程间通信方式
1)无名管道 pipe
2)有名管道(命名管道) fifo
3)信号 signal
2.system V操作系统的IPC对象
1)消息队列 message queue
2)共享内存 shared memory
3)信号灯集 semaphore
3.可用于跨主机的通信
1)套接字 socket
【2】管道
1. 管道的原理
在进程的3~4G内核空间中,创建一个管道(特殊的文件),管道中的数据直接保存在内存中
2. 管道的特性
-
管道可以看成是一个特殊的文件,一般文件存储在硬盘上,而管道文件存储在内存中;
-
管道遵循先进先出的原则;
-
管道是一种半双工的通信方式。
单工:只能A发消息给B,B不能发消息给A
半双工:同一时间只能A发给B,B不能发给A;
全双工:同一时间,AB能互相发消息;
-
管道的大小:64K = 64*1024 = 65536byte;
-
对于管道的读写操作是一次性的,如果对管道进行读操作,那么被读取的数据会从管道中删除。
-
从管道中读取数据;
当写端没有关闭
1)当管道中没有数据,read函数会阻塞。
2)当请求的数据a(10)大于管道中存储的数据b(5),则实际读取的数据个数为:b
3)当请求的读取的数据a(5)小于管道中存储的数据b(12),则实际读取的数据个数为:a
当所有写端关闭(父子进程都有写端)
1)从管道中读取数据,当管道中的数据读取完毕后,read函数不阻塞,返回0;
-
向管道中写入数据
当读端没有关闭
1)管道中,如果有剩余空间,则写进程会向管道中写数据;
2)如果管道满了,则写函数会阻塞;
当所有读端关闭(父子进程都有读端)
1)当读端关闭,尝试向管道中写入数据,会造成管道破裂,退出进程,只要调用write函数就会造成管道破裂;
2)写进程会收到管道破裂信号(SIGPIPE);
3. 无名管道(pipe)
1)无名管道的特点
-
无名管道只能用于具有 亲缘关系 进程间通信。
-
对于无名管道的读写,只能使用文件IO,如read write,但是不能使用lseek;
-
当管道的读写端均关闭的时候,释放管道空间
为什么无名管道只能用于具有亲缘关系的进程通信
用管道的方式进行进程通信,必须要操作同一个管道。即两个进程必须有同一个管道的读写端。
无名管道在文件系统上没有名字,没有关系的进程无法打开同一个管道;
而子进程克隆父进程的所有资源,所以在父进程中获取到管道的读写端会被拷贝到子进程中,
所以父子进程能能拿到同一管道的读写端。
2)pipe
功能:创建一个管道文件,并打开文件的读写端; 头文件: #include <unistd.h> 原型: int pipe(int pipefd[2]); 参数: int pipefd[2]:传入一个数组,且容量为2;存储打开的两个文件描述符; pipefd[0]:读端; pipefd[1]:写端; 返回值: 成功,返回0; 失败,返回-1,更新errno;
练习
使用无名管道实现父子进程对话,顺序如下:
1)父进程发送一句话,子进程接收数据后,打印
2)子进程发送一句话,父进程接收数据后,打印。
3)重复1)2)步骤
4)当父进程或子进程发送 quit Quit,,结束父子进程。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>int main(int argc, const char *argv[])
{
//父进程向子进程发送数据,父进程写,子进程读
int ptoc[2] = {0};
if(pipe(ptoc) < 0)
{
perror("pipe");
return -1;
}//子进程向父进程发送数据,子进程写,父进程读
int ctop[2] = {0};
if(pipe(ctop) < 0)
{
perror("pipe");
return -1;
}
char str[128] = "";
ssize_t res = 0;//创建子进程
pid_t pid = fork();
if(pid > 0)
{
//父进程运行
close(ptoc[0]); //由于ptoc的读端,父进程不需要,所以关闭
close(ctop[1]); //由于ctop的写端,父进程不需要,所以关闭
while(1)
{
printf("父进程说>>>");
fgets(str, sizeof(str), stdin);
str[strlen(str)-1] = 0;res = write(ptoc[1], str, sizeof(str));
if(res < 0)
{
perror("write");
return -1;
}
// printf("父进程成功发送%ld个字节\n\n", res);
if(strncasecmp(str, "quit", 4) == 0)
{
break;
}
res = read(ctop[0], str, sizeof(str));
if(res < 0)
{
perror("read");
return -1;
}
else if(0 == res)
{
printf("所有写端关闭,并且管道中没有数据了\n");
return -1;
}printf("父进程接收到子进程:%s\n", str);
if(strncasecmp(str, "quit", 4) == 0)
{
break;
}
}
wait(NULL);
close(ptoc[1]);
close(ctop[0]);
}
else if(0 == pid)
{
//子进程运行
close(ptoc[1]);
close(ctop[0]);while(1)
{
res = read(ptoc[0], str, sizeof(str));
if(res < 0)
{
perror("read");
return -1;
}
else if(0 == res)
{
printf("所有写端关闭,并且管道中没有数据了\n");
return -1;
}printf("子进程接收到父进程:%s\n", str);
if(strncasecmp(str, "quit", 4) == 0)
{
break;
}
printf("子进程说>>>");
fgets(str, sizeof(str), stdin);
str[strlen(str)-1] = 0;res = write(ctop[1], str, sizeof(str));
if(res < 0)
{
perror("write");
return -1;
}
// printf("子进程成功发送%ld个字节\n\n", res);
if(strncasecmp(str, "quit", 4) == 0)
{
break;
}
}close(ptoc[0]);
close(ctop[1]);
}
else
{
perror("fork");
return -1;
}return 0;
}
4. 有名管道(fifo)
又称为命名管道,顾名思义,就是名字可以被看见的管道文件,但是数据依然存在于内存中,是不可见的
1)有名管道的特点
-
可以用在没有亲缘关系的进程间通信
-
有名管道的读写可以使用文件IO函数,read,write,但是不能使用lseek;
-
有名管道文件需要手动open、close管道文件
-
当读写端都关闭后,管道内存被释放;
2)创建有名管道文件
-
用shell命令创建
$ mkfifo 有名管道名 $ mkfifo fifo
-
用mkfifo函数
功能:创建有名管道文件; 头文件: #include <sys/types.h> #include <sys/stat.h> 原型: int mkfifo(const char *pathname, mode_t mode); 参数: char *pathname:指定要创建的有名管道的路径及管道名; mode_t mode:管道的8进制权限; the permissions of the created file are (mode & ~umask). 返回值: 成功,返回0; 失败,返回-1,更新errno; 需要排除fifo文件已经存在的情况,errno == EEXIST if(mkfifo("./myfifo", 0777) < 0) { printf("errno = %d\n", errno); if(errno != 17) //EEXIST { perror("mkfifo"); return -1; } }
3)有名管道的使用
-
通过open函数打开有名管道文件
-
通过文件IO函数读写有名管道
与读写普通文件的时候一致;
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> 原型: int open(const char *pathname, int flags);
-
有名管道的open规则
O_RDONLY 只读 O_WRONLY 只写 O_RDWR 读写
---以上三种必须包含一种---
1)flags == O_WRONLY
open函数会阻塞,直到有另外一个进程以读的方式打开同一个FIFO文件;
2)flags == O_RDONLY
open函数会阻塞,直到有另外一个进程以写的方式打开同一个FIFO文件;
3)flags == O_RDWR
open函数不阻塞,立即返回;
4)代码范例
i. 写
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <string.h> #include <errno.h> int main(int argc, const char *argv[]) { //创建有名管道 if(mkfifo("./myfifo", 0777) < 0) { if(EEXIST !=errno ) { perror("mkfifo"); return -1; } } printf("create FIFO success\n"); //打开有名管道文件 int fd = open("./myfifo", O_WRONLY); if(fd < 0) { perror("open"); return -1; } printf("写端打开成功:%d\n", fd); ssize_t res = 0; char str[20] = ""; while(1) { printf("请输入>>>"); fgets(str, sizeof(str), stdin); res = write(fd, str, sizeof(str)); if(res < 0) { perror("write"); return -1; } printf("writ