进程通信方式
当今比较常用的有:
- 管道(使用简单)
- 信号(开销最小)
- 共享映射区(无血缘关系)
- 本地套接字(最稳定)
管道
管道又分为:无名管道和有名管道FIFO
无名管道:只能用于有亲属关系的进程间
有名管道:可以用在有亲属关系的进程间也可用在无亲属关系的进程间
无名管道
管道是基于文件描述符的通信方式,当一个管道建立时,它会创建两个文件描述符 fds[0]和 fds[1],其中 fds[0]固定用于读管道,而 fd[1]固定用于写管道,这样就构成了一个半双工的通道。
管道关闭时只需将这两个文件描述符关闭即可,可使用普通的 close 函数逐个关闭各个文件描述符。
管道其本质是一个伪文件(实为内核缓冲区)
管道的原理:管道实为内核使用环形队列的机制,借助内核缓冲区(4k)实现。
管道的局限性:
- 数据自己读不能自己写
- 数据一旦被读走,便不在管道中存在,不可反复读取
- 由于管道采用半双工通信方式,因此,数据只能在一个方向上流动
- 只能在有亲属关系的进程间使用管道
7种文件类型:
- — 文件
- d 目录
- l 符号链接
- s 套接字
- b 块设备
- c 字符设备
- p 管道
前三个是占用内存空间的;后四个是不占用内存空间的,并且都是伪文件
管道创建 pipe( )
管道读写注意点
- 只有在管道的读端存在时向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的 SIFPIPE 信号(通常 Broken pipe 错误)。
- 向管道中写入数据时,linux 将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作将会一直阻塞。
- 父子进程在运行时,它们的先后次序并不能保证,因此,在这里为了保证父进程已经关闭了读描述符,可在子进程中调用 sleep 函数。
代码示例:
/* pipe.c */
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include<sys/wait.h>
#define MAX_DATA_LEN 256
#define DELAY_TIME 1
int main()
{
pid_t pid;
int pipe_fd[2];
char buf[MAX_DATA_LEN];
const char data[] = "Pipe Test Program";
int real_read, real_write;
memset((void*)buf, 0, sizeof(buf));
/* 创建管道 */
if (pipe(pipe_fd) < 0)
{
printf("pipe create error\n");
exit(1);
}
/* 创建一子进程 */
if ((pid = fork()) == 0)
{
/* 子进程关闭写描述符,并通过使子进程暂停1秒等待父进程已关闭相应的读描述符 */
close(pipe_fd[1]);
sleep(DELAY_TIME * 3);
/* 子进程读取管道内容 */
if ((real_read = read(pipe_fd[0], buf, MAX_DATA_LEN)) > 0)
{
printf("%d bytes read from the pipe is '%s'\n", real_read, buf);
}
/* 关闭子进程读描述符 */
close(pipe_fd[0]);
exit(0);
}
else if (pid > 0)
{
/* 父进程关闭读描述符,并通过使父进程暂停1秒等待子进程已关闭相应的写描述符 */
close(pipe_fd[0]);
sleep(DELAY_TIME);
/* 父进程向管道中写入字符串 */
if((real_write = write(pipe_fd[1], data, strlen((const char*)data))) != -1)
{
printf("Parent wrote %d bytes : '%s'\n", real_write, data);
}
/*关闭父进程写描述符*/
close(pipe_fd[1]);
/*收集子进程退出信息*/
waitpid(pid, NULL, 0);
exit(0);
}
}
结果:
有名管道FIFO
有名管道FIFO,它可以使互不相关的两个进程实现彼此通信。
对于读进程
- 若该管道是阻塞打开,且当前 FIFO 内没有数据,则对读进程而言将一直阻塞直到有
数据写入。 - 若该管道是非阻塞打开,则不论 FIFO 内是否有数据,读进程都会立即执行读操作。
对于写进程
- 若该管道是阻塞打开,则写进程而言将一直阻塞直到有读进程读出数据。
- 若该管道是非阻塞打开,则当前 FIFO 内没有读操作,写进程都会立即执行读操作。
代码演示:
读端:
/* fifo_read.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#define MYFIFO "/tmp/myfifo"
#define MAX_BUFFER_SIZE PIPE_BUF /*定义在于limits.h中*/
int main()
{
char buff[MAX_BUFFER_SIZE];
int fd;
int nread;
/* 判断有名管道是否已存在,若尚未创建,则以相应的权限创建*/
if (access(MYFIFO, F_OK) == -1)
{
if ((mkfifo(MYFIFO, 0666) < 0) && (errno != EEXIST))
{
printf("Cannot create fifo file\n");
exit(1);
}
}
/* 以只读阻塞方式打开有名管道 */
fd = open(MYFIFO, O_RDONLY);
if (fd == -1)
{
printf("Open fifo file error\n");
exit(1);
}
while (1)
{
memset(buff, 0, sizeof(buff));
if ((nread = read(fd, buff, MAX_BUFFER_SIZE)) > 0)
{
printf("Read '%s' from FIFO\n", buff);
}
}
close(fd);
exit(0);
}
写端:
/* fifo_write.c */
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#define MYFIFO "/tmp/myfifo" /* 有名管道文件名*/
#define MAX_BUFFER_SIZE PIPE_BUF /*定义在于limits.h中*/
int main(int argc, char * argv[]) /*参数为即将写入的字符串*/
{
int fd;
char buff[MAX_BUFFER_SIZE];
int nwrite;
if(argc <= 1)
{
printf("Usage: ./fifo_write string\n");
exit(1);
}
sscanf(argv[1], "%s", buff);
/* 以只写阻塞方式打开FIFO管道 */
fd = open(MYFIFO, O_WRONLY);
if (fd == -1)
{
printf("Open fifo file error\n");
exit(1);
}
/*向管道中写入字符串*/
if ((nwrite = write(fd, buff, MAX_BUFFER_SIZE)) > 0)
{
printf("Write '%s' to FIFO\n", buff);
}
close(fd);
exit(0);
}
结果:
要先执行读端,因为管道是在读端简历的;如果先执行写端的话,会报错:管道文件打开失败
写端:
读端: