1. 管道简单理解
- 管道实质上是在内存或文件系统中创建一个共享文件,通讯方共享这个文件,每一方只允许对文件进行读或者写,数据流是单向的。
- 管道内是字节流传递,没有分界线,不能随机读取,只能顺序读写。
- 管道分为匿名管道和命名管道
2. 匿名管道
- 使用同一管道的进程间必须有亲属关系,对文件系统不可见,内核在内存中模拟管道
管道应用:ls -al | grep xxx
: ls -al 输出为管道的输入,管道的输出为grep的输入 - 数据拷贝:
写入:用户空间buf到内核,内核到内存
读取:内存到内核,内核到用户空间buf - api
#include <unistd.h>
// fd为两个文件描述符,fd[0]为读,fd[1]为写
int pipe(int fd[2]);
// 向管道中写数据
ssize_t write(int fd, const void *buf, size_t count);
// 从管道中读数据,返回的是读取的数据
ssize_t read(int fd, void *buf, size_t count);
// 关闭管道某一端
int close(int fd);
// 管道和shell command的交互
// type为r时,command的输出为管道的输入,type为w时,command的输入为shell的输出。
// popen返回的文件流中保存的是管道的输出。可以通过fgets获取,
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
3. 命名管道
-
FIFO ,进程间可以没有亲属关系,文件系统可见
-
命名管道提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,在文件系统中产生一个物理文件,其他进程只要访问该文件路径,就能彼此通过管道通信。
-
FIFO文件与普通文件的区别:
普通文件无法实现字节流方式管理,而且多进程之间访问共享资源会造成意想不到的问题;
FIFO文件采用字节流方式管理,遵循先入先出原则,不涉及共享资源访问。 -
api
#include <sys/types.h>
#include <sys/stat.h>
// 创建管道,pathname为文件系统中的路径名
int mkfifo(const char *pathname, mode_t mode);
// #define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
// 打开管道
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode)
// 从文件系统中删除这个命名管道
int unlink(const char *pathname);
// 关闭管道,参数为open的返回值,为文件描述符
int close(int fd);
4. 管道和FIFO的额外参数 和 阻塞规则
- 设置non_block标志
1)匿名管道只能通过fcntl设置, FIFO可以通过open指定
int flags;
if ((flags = fcntl(fd, F_GETFL, 0)) < 0) {
printf("fcntl get error \n");
}
flags |= O_NONBLOCK;
if (fcntl(fd, F_SETFL, flags) < 0) {
printf("fcntl set error\n");
}
int fd = open(FIFO_NAME, O_WRONLY|O_NONBLOCK, 0);
2)阻塞规则
对于open,有读无写,读阻塞直到有open写,设置O_NONBLOCK后成功返回
对于open,有写无读,阻塞直到有open读,设置O_NONBLOCK后返回ENXIO
对于read,有读无写,返回0,
对于read, 有读有写,如果写不及,阻塞知道有数据或者FIFO不在有写者,设置O_NONBLOCK后返回EAGAIN
对于write,有写无读,产生SIGPIPE信号,SIGPIPE信号默认终止程序,如果捕获或者显示忽略,则会返回错误EPIPE
5. 匿名管道和命名管道的区别:
1. 共享管道的进程间,匿名管道需要亲属关系,命名管道不需要
2. 匿名管道是通过内存文件实现,命名管道是通过文件系统中的文件实现。
6. 代码demo
6.1 pipe:创建管道后fork产生子进程,父子进程共享管道,子关闭写,父关闭读,实现父写子读。
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define BUF_SIZE 100
int main(int argc, char *argv[])
{
int fd[2];
char buf[BUF_SIZE];
if (argc != 2) {
printf("input fail\n");
return -1;
}
if (pipe(fd) == -1) {
printf("pipe error\n");
return -1;
}
switch(fork())
{
case 0:
// son
if (close(fd[1]) == -1) {
printf("son close wirte fail\n");
return -1;
}
while(1) {
int num = read(fd[0], buf, BUF_SIZE);
if (num == -1) {
printf("read fail \n");
return -1;
}
if (num == 0) {
printf("end of file \n");
break;
}
printf("son get msg: %s\n", buf);
}
printf("son read over\n");
if (close(fd[0]) == -1) {
printf("son close read fail\n");
return -1;
}
return 0;
break;
case -1:
printf("fork error \n");
break;
default:
// father
if (close(fd[0]) == -1) {
printf("father close read fail\n");
return -1;
}
if (write(fd[1], argv[1], strlen(argv[1])) != strlen(argv[1])) {
printf("father write fail\n");
return -1;
}
if (close(fd[1]) == -1) {
printf("father close fail\n");
return -1;
}
wait(NULL);
printf("father gone\n");
return 0;
}
}
---
./app xxxxxxxxxd
son get msg: xxxxxxxxxdZ
end of file
son read over
father gone
6.2 popen,与shell的交互
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define MAXLINE 4096
int main(int argc, char **argv)
{
size_t n;
char buff[MAXLINE], command[MAXLINE];
char *rptr;
if ( (rptr = fgets(buff, MAXLINE, stdin)) == NULL && ferror(stdin))
{
printf("fgets error\n"); return -1;
}
n = strlen(buff);
if (buff[n-1] == '\n') {
n--;
}
snprintf(command, sizeof(command), "cat %s", buff);
FILE *fp = popen(command, "r"); // command执行后的结果作为管道的输入
while (fgets(buff, MAXLINE, fp) != NULL) { // 获取管道内的数据,通过文件流fp
fputs(buff, stdout);
}
pclose(fp);
exit(0);
}
--
./app
输入某个文件
输出为cat 文件后的内容
6.3 mkfifo: 客户端传递一个文件名,服务器获取文件内容返回后退出
testFifoServer.cpp
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#define FIFO "/tmp/testFifo1"
#define FIFO2 "/tmp/testFifo12"
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
#define MAXLINE 4096
int main()
{
if (mkfifo(FIFO, FILE_MODE) < 0 && errno != EEXIST) {
printf("mkfifo error1 \n"); return -1;
}
if (mkfifo(FIFO2, FILE_MODE) < 0 && errno != EEXIST) {
unlink(FIFO);
printf("mkfifo error2 \n"); return -1;
}
int readFd = open(FIFO, O_RDONLY, 0); // 打开管道1的读,阻塞
int writeFd = open(FIFO2, O_WRONLY, 0);
int n;
char buff[MAXLINE+1];
if((n = read(readFd, buff, MAXLINE)) == 0) { // 此时客户端open了写,服务端阻塞在此处等待客户端写数据
printf("read size0 \n");
}
buff[n] = '\0';
int fd;
if ((fd = open(buff, O_RDONLY)) < 0) { // 打开client传过来的管道名
snprintf(buff + n, sizeof(buff) - n, ": can't open, %s\n", strerror(errno));
n = strlen(buff);
write(writeFd, buff, n);
} else {
while ((n = read(fd, buff, MAXLINE)) > 0)
write(writeFd, buff, n);
close(fd); // 关闭文件
}
close(writeFd);
printf(" server out\n");
exit(0);
}
testFifoClient.cpp
// 头文件同server
int main()
{
if (mkfifo(FIFO1, FILE_MODE) < 0 && errno != EEXIST) {
printf("mkfifo error1 \n"); return -1;
}
if (mkfifo(FIFO2, FILE_MODE) < 0 && errno != EEXIST) {
unlink(FIFO1);
printf("mkfifo error2 \n"); return -1;
}
int writeFd = open(FIFO1, O_WRONLY, 0); // 客户端打开写,服务端的open读阻塞返回
int readFd = open(FIFO2, O_RDONLY, 0);
char buff[MAXLINE];
char *rptr;
if ((rptr = fgets(buff, MAXLINE, stdin)) == NULL && ferror(stdin)) {
printf("client get stdin data error \n"); return -1;
}
size_t len = strlen(buff);
if (buff[len-1] == '\n') {
len--;
}
if (write(writeFd, buff, len) != len) {
printf("client write error\n"); return -1;
}
int n;
while ((n = read(readFd, buff, MAXLINE)) > 0) { // 客户端读阻塞知道服务端写
//printf("client read get n: %d\n", n);
write(STDOUT_FILENO, buff, n);
if (write(STDOUT_FILENO, buff, n) != n) {
printf("write error \n"); return -1;
}
}
close(readFd);
close(writeFd);
unlink(FIFO1);
unlink(FIFO2);
printf("client end\n");
return 0;
}
linux api查询 : https://www.die.net/
网络编程 卷2 进程间通信
linux编程手册