管道是一种在Unix和类Unix系统中用于进程间通信的机制。管道可以分为匿名管道和命名管道两种类型。
匿名管道是一种单向通信机制,只能在具有共同祖先的进程之间使用。它通过pipe系统调用创建,其中一个进程作为读端,另一个进程作为写端。
优点:
缺点:
命名管道是一种特殊类型的文件,允许无关的进程之间进行通信。它通过mkfifo系统调用创建,可以在文件系统中看到。
优点:
缺点:
匿名管道适用于具有共同祖先的两个进程之间的单向通信,比如父子进程之间的通信。命名管道适用于无关的进程之间的通信,可以实现双向通信,适用于需要在不同进程之间进行数据交换的场景,比如进程间的协作或数据传输。
管道通信是基于字节流的,不支持传递复杂的数据结构,因此在需要传递结构化数据或大量数据的情况下,可能需要考虑其他进程间通信机制,比如消息队列、共享内存或套接字等。
匿名管道只能进行单向通信,因此创建两个管道实现父子间进程的双向通信:
创建完描述符后关闭自己不需要的功能,让管道1负责父进程写子进程读,管道2负责子进程写父进程读,实现效果如下:
编写如下测试代码:
#include
#include
#include
#include
#include
#include
// 打印时分秒的宏
#define PRINT_MIN_SEC() do { \
time_t t = time(NULL); \
struct tm *tm_ptr = localtime(&t); \
printf("%02d:%02d:%02d:", tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec); \
} while (0)
int main()
{
int pipefd1[2]; // 管道1 父进程写,子进程读 [0]为读端 [1]为写端
int pipefd2[2]; // 管道2 子进程写,父进程读
char buffer1[100] = {0}; // 父进程接收缓存
char buffer2[100] = {0}; // 子进程接收缓存
pid_t pid;
if (pipe(pipefd1) == -1 || pipe(pipefd2) == -1)
{
perror("pipe");
return 0;
}
pid = fork();
if (pid == -1)
{
perror("fork");
return 0;
}
if (pid > 0) // 父进程
{
close(pipefd1[0]); // 关闭管道1父进程读取端
close(pipefd2[1]); // 关闭管道2父进程写入端
// 向管道1写入数据
write(pipefd1[1], "I am parent send msg", sizeof("I am parent send msg"));
while(1)
{
// 从管道2读取数据
bzero(buffer1, sizeof(buffer1));
read(pipefd2[0], buffer1, sizeof(buffer1));
PRINT_MIN_SEC();
printf("Read from child: %s\n", buffer1);
sleep(3);
write(pipefd1[1], "I am parent send msg", sizeof("I am parent send msg"));
}
}
else // 子进程
{
close(pipefd1[1]); // 关闭管道1子进程写入端
close(pipefd2[0]); // 关闭管道1子进程读取端
// 向管道2写入数据
write(pipefd2[1], "I am child send msg", sizeof("I am child send msg"));
while(1)
{
bzero(buffer2, sizeof(buffer2));
read(pipefd1[0], buffer2, sizeof(buffer2));
PRINT_MIN_SEC();
printf("Read from parent: %s\n", buffer2);
sleep(3);
write(pipefd2[1], "I am child send msg", sizeof("I am child send msg"));
}
}
return 0;
}
每隔3秒,父进程和子进程都会读取对方发送的消息,并重新发送给对方一次消息,测试结果如下:
对于创建的读写描述符有以下属性:
可以通过fcntl来设置相关属性值,例如设置管道1读取非阻塞:
fcntl(pipefd1[0], F_SETFL, O_NONBLOCK)
使用两个FIFO可以实现无血缘关系的两个进程之间的通信,编写测试用例,进程1代码如下,实现向fifo1写入数据,并且堵塞读取fifo2中的数据:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PROCESS_1_SEND "/home/fifo1"
#define PROCESS_2_SEND "/home/fifo2"
// 打印时分秒的宏
#define PRINT_MIN_SEC() do { \
time_t t = time(NULL); \
struct tm *tm_ptr = localtime(&t); \
printf("%02d:%02d:%02d:", tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec); \
} while (0)
// 进程1
int main()
{
int fd1, fd2;
int ret = 0;
char buffer[100];
// 创建FIFO
if (-1 == access(PROCESS_1_SEND, F_OK))
{
if (0 != mkfifo(PROCESS_1_SEND, 0666))
{
perror("mkfifo PROCESS_1_SEND err");
return 0;
}
}
if (-1 == access(PROCESS_2_SEND, F_OK))
{
if (0 != mkfifo(PROCESS_2_SEND, 0666))
{
perror("mkfifo PROCESS_2_SEND err");
return 0;
}
}
fd1 = open(PROCESS_1_SEND, O_RDWR);
fd2 = open(PROCESS_2_SEND, O_RDWR);
write(fd1, "Process 1 Start", sizeof("Process 1 Start"));
while(1)
{
read(fd2, buffer, sizeof(buffer));
PRINT_MIN_SEC();
printf("Read from process 2: %s\n", buffer);
sleep(5);
write(fd1, "Process 1 Msg", sizeof("Process 1 Msg"));
}
close(fd1);
close(fd2);
return 0;
}
进程2代码如下,实现向fifo2写入数据,并且堵塞读取fifo1中的数据:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define PROCESS_1_SEND "/home/fifo1"
#define PROCESS_2_SEND "/home/fifo2"
// 打印时分秒的宏
#define PRINT_MIN_SEC() do { \
time_t t = time(NULL); \
struct tm *tm_ptr = localtime(&t); \
printf("%02d:%02d:%02d:", tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec); \
} while (0)
// 进程2
int main()
{
int fd1, fd2;
int ret = 0;
char buffer[100];
fd1 = open(PROCESS_1_SEND, O_RDWR);
fd2 = open(PROCESS_2_SEND, O_RDWR);
write(fd2, "Process 2 Start", sizeof("Process 2 Start"));
while(1)
{
read(fd1, buffer, sizeof(buffer));
PRINT_MIN_SEC();
printf("Read from process 1: %s\n", buffer);
sleep(5);
write(fd2, "Process 2 Msg", sizeof("Process 2 Msg"));
}
close(fd1);
close(fd2);
return 0;
}
每隔5秒,进程1和进程2都会读取对方发送的消息,并重新发送给对方一次消息,测试结果如下:
管道默认是有最大限值的,如果一直写入没有读取的话会造成堵塞,Linux下通过以下命令可以查看:
sysctl fs.pipe-max-size
或者
cat /proc/sys/fs/pipe-max-size
本文阐述了管道通信的特点和优缺点,通过测试用例示例了管道在编程中的具体使用。