Linux进程间的通信(二)管道通信及其实际应用(主要是实际编程应用,底层涉及不太多,想了解底层参考《UNIX环境高级编程》)
目录
简单介绍一下管道的概念及其特性
命名管道
命名管道例程
匿名管道
匿名管道例程
Linux管道通信实战演示
1、利用管道建立聊天室,实现两个用户间的发送和接受消息
2、利用管道进行文件的传输
简单介绍一下管道的概念及其特性
管道是一种进程间通信(IPC)机制,它允许一个进程将数据传递给另一个进程。管道文件可以看作是一个临时的、基于内存的数据通道,数据在其中以先进先出(FIFO)的方式流动。
例如,假设有两个程序 A 和 B,程序 A 产生一些数据,而程序 B 需要使用这些数据。通过创建一个管道,程序 A 可以将数据写入管道,而程序 B 可以从管道中读取这些数据,从而实现了两个程序之间的数据传递。
如果read读取完管道里的数据,管道为空,就会读阻塞
如果write写入的数据大于管道的容量,管道容量满了,就会写阻塞
如果所有指向管道写端的文件描述符都关闭了,而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样
如果所有指向管道读端的文件描述符都关闭了,这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。
参考文献:Linux 的进程间通信:管道 - 知乎 (zhihu.com)
参考文献:linux之《管道》_管道文件-CSDN博客
命名管道
命名管道是真实存在在磁盘中的!
命名管道文件的创建
Linux命令行创建管道文件
mkfifo <pipename>
函数原型:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
pathname:要创建的管道文件名字。
mode:用来规定FIFO的读写权限。
命名管道例程
创建一个命名管道实现两个进程间的通信:
write.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
// 1.打开管道文件
int fd = open("/home/loading/pipetest", O_RDWR);
if (fd < 0)
{
perror("打开管道失败\n");
return -1;
}
// 2.写入数据到管道中
while (1)
{
printf("请输入写入管道的数据\n");
char buf[1024] = {0};
scanf("%s", buf);
write(fd, buf, strlen(buf));
}
}
read.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
// 1.打开管道文件
int fd = open("/home/loading/pipetest", O_RDWR);
if (fd < 0)
{
perror("打开管道失败\n");
return -1;
}
// 2.读取管道数据
while (1)
{
char buf[1024] = {0};
read(fd, buf, 1024);
printf("读取到的管道数据 %s\n", buf);
}
}
在运行前需要现在open的路径下mkfifo一个pipe文件,我这里的路径是/home/loading/,根据实际创建
运行时需要打开两个Linux终端,好验证进程间的通信。
匿名管道
没有文件名,在内存中以特殊的文件描述符对的形式存在。
当一个进程调用 pipe 系统调用时,内核会创建一个匿名管道,并返回两个文件描述符,一个用于读操作,一个用于写操作。
通常在父进程中创建管道后,再通过 fork 创建子进程。此时,子进程会继承父进程的文件描述符表,从而父子进程都可以访问这个管道。
匿名管道本身并不占用磁盘或者其他外部存储的空间,在Linux的实现上,它占用的是内存空间。所以,Linux上的匿名管道就是一个操作方式为文件的内存缓冲区。
函数原型
#include <unistd.h>
int pipe(int filedes[2]);
pipefd[0]:管道数据读取端,读取时必须关闭写入端,即close(pipefd[1])
pipefd[1]:管道数据写入端,写入时必须关闭读取端,即close(pipefd[0])
返回值:成功 0
失败 -1
由于基于fork机制,所以匿名管道只能用于父进程和子进程之间,或者拥有相同祖先的两个子进程之间 (有亲缘关系的进程之间)!
参考文献:Linux进程间通信之管道(pipe)、命名管道(FIFO)与信号(Signal) - as_ - 博客园 (cnblogs.com)
匿名管道例程
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
int main()
{
// 1.创建一个匿名管道
int pipefd[2] = {0};
int ret = pipe(pipefd);
if (ret < 0)
{
perror("创建匿名管道失败\n");
return -1;
}
// 2.创建父子进程
pid_t pid = fork();
if (pid == 0) // 子进程
{
while (1) // 子进程写入管道
{
char buf[1024] = {0};
printf("请输入想要写入管道的数据\n");
scanf("%s", buf);
write(pipefd[1], buf, strlen(buf));
usleep(50000); //延时半秒
}
}
if (pid > 0) // 父进程
{
while (1) // 父进程读管道
{
char buf[1024] = {0};
read(pipefd[0], buf, 1024);
printf("读取到的管道数据 %s\n", buf);
}
}
}
Linux管道通信实战演示
1、利用管道建立聊天室,实现两个用户间的发送和接受消息
原理:需要建立两个命名管道实现收发
client1.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
// 1.打开一个管道文件
int fd1 = open("/home/loading/pipe1", O_RDWR);
int fd2 = open("/home/loading/pipe2", O_RDWR);
if (fd1 < 0)
{
perror("打开管道失败\n");
return -1;
}
if (fd2 < 0)
{
perror("打开管道失败\n");
return -1;
}
pid_t pid1 = fork();
// 写数据
if (pid1 == 0)
{
while (1)
{
char buf1[1024] = {0};
printf("请输入要发送的数据\n");
scanf("%s", buf1);
// lseek(fd,0,SEEK_SET);
write(fd2, buf1, strlen(buf1));
}
}
// 读数据
if (pid1 > 0)
{
while (1)
{
char buf[1024] = {0};
// lseek(fd,0,SEEK_SET);无论命名还是匿名管道,它的文件描述都没有偏移量的概念,所以不能用lseek进行偏移量调整
read(fd1, buf, 1024); // 管道没有数据,则会阻塞等待
printf("管道1读取数据 %s\n", buf);
}
}
}
client2.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
// 1.打开一个管道文件
int fd1 = open("/home/loading/pipe1", O_RDWR);
// 1.打开一个管道文件
int fd2 = open("/home/loading/pipe2", O_RDWR);
if (fd1 < 0)
{
perror("打开管道失败\n");
return -1;
}
if (fd2 < 0)
{
perror("打开管道失败\n");
return -1;
}
pid_t pid =fork();
//写数据
if(pid==0)
{
while (1)
{
printf("请输入要发送的数据\n");
char buf[1024]={0};
scanf("%s", buf);
// lseek(fd,0,SEEK_SET);
write(fd1, buf, strlen(buf));
}
}
//读数据
if(pid>0)
{
while (1)
{
char buf1[1024]={0};
// lseek(fd1,0,SEEK_SET);
read(fd2, buf1, 1024);// 管道没有数据,则会阻塞等待
printf("管道2读取数据 %s\n", buf1);
}
}
}
2、利用管道进行文件的传输
send.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
// 获取文件的大小
int get_file_size(char *file)
{
// 1.打开文件
int fd = open(file, O_RDWR);
if (fd < 0)
{
perror("");
return -1;
}
// 2.偏移光标到文件末尾
int file_size = lseek(fd, 0, SEEK_END);
close(fd);
return file_size;
}
// 发送进程
int main(int argc, char *argv[])
{
if (argc != 2)
{
printf("请输入需要发送的文件名\n");
return 0;
}
// 1.打开发送的文件
int fd = open(argv[1], O_RDWR);
if (fd < 0)
{
perror("");
return -1;
}
// 2.打开管道文件
int pipe_fd = open("/home/loading/pipe", O_RDWR);
if (pipe_fd < 0)
{
perror("");
return -1;
}
// 3.发送文件大小给接收端
int file_size = get_file_size(argv[1]);
char head[1024] = {0};
sprintf(head, "%d", file_size);
write(pipe_fd, head, strlen(head));
// 休眠等待客户端应答
sleep(2);
char ack[50] = {0};
read(pipe_fd, ack, 50);
if (strcmp(ack, "no") == 0)
{
printf("客户端拒绝接收\n");
return -1;
}
while (1)
{
char buf[1024 * 10] = {0};
// 读取文件的数据
int size = read(fd, buf, 1024 * 10);
if (size <= 0)
{
break;
}
// 写入到管道文件中
write(pipe_fd, buf, size); // 读取到多少就写入多少
}
printf("发送完毕\n");
close(fd);
close(pipe_fd);
}
recv.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
// 接收进程
int main(int argc, char *argv[])
{
if (argc != 2)
{
printf("请输入需要接收的文件名\n");
return 0;
}
// 1.打开发送的文件
int fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, 0777);
if (fd < 0)
{
perror("");
return -1;
}
// 2.打开管道文件
int pipe_fd = open("/home/loading/pipe", O_RDWR);
if (pipe_fd < 0)
{
perror("");
return -1;
}
// 3.接收文件的大小
char head[1024] = {0};
read(pipe_fd, head, 1024);
int filesize = atoi(head);
printf("客户端发送文件,大小为%d,1.yes or 2.no\n", filesize);
int n = 0;
scanf("%d", &n);
if (n == 1)
{
write(pipe_fd, "yes", 3);
}
else
{
write(pipe_fd, "no", 2);
return -1; // 退出接收
}
// 休眠等待
sleep(1);
int down_size = 0; // 下载大小
while (1)
{
char buf[1024 * 10] = {0};
// 读取管道数据
int size = read(pipe_fd, buf, 1024 * 10);
if (size <= 0)
{
break;
}
// 写入到本地文件中
write(fd, buf, size); // 读取到多少就写入多少
down_size += size;
printf("当前接收文件的大小:%d ,进度%.2f %%\r", down_size, (float)down_size * 100 / filesize);
if (down_size >= filesize)
{
printf("接收完毕\n");
break;
}
}
printf("接收完毕\n");
close(fd);
close(pipe_fd);
}