Linux IPC 管道:标准流管道,无名管道(PIPE),命名管道(FIFO)

标准流管道

像文件操作有标准 io 流一样,管道也支持文件流模式。用来创建连接到另一进程的管道,是通过函数 popen 和 pclose。

函数原型:

#include <stdio.h>
FILE* popen(const char* command, const char* open_mode);
int pclose(FILE* fp);

函数popen()

FILE* popen(const char* command, const char* open_mode);

popen允许一个程序将另一个程序作为新进程来启动,并可以传递数据给它或者通过它接收数据。这里“另一个进程”是可以执行一定操作的可执行文件,例如用户执行“ls -l”或者./pipe。由于这类操作很常见,所以将一系列创建过程合并到一个函数popen()中完成,这个函数会完成以下步骤:

https://blog.csdn.net/zzyoucan/article/details/9226599

创建一个管道
fork()一个子进程
在父子进程中关闭不需要的文件描述符
执行exec()函数族调用
执行函数中指定的命令

参数command 字符串是要运行的程序名

参数open_mode 必须是“r”或“w”。

如果 open_mode 是“r”,被调用程序的输出就可以被调用程序(popen)使用,调用程序利用popen 函数返回的 FILE*文件流指针,就可以通过常用的 stdio 库函数(如 fread)来读取被调用程序的输出。

如果 open_mode 是“w”,调用程序(popen)就可以用 fwrite 向被调用程序发送数据,而被调用程序可以在自己的标准输入上读取这些数据。

该函数调用成功返回文件流指针,调用失败返回-1。

函数pclose()

函数 pclose():用 popen 启动的进程结束时,我们可以用 pclose 函数关闭与之关联的文件流。

popen_ls.c

#include <func.h>

int main(int argc, char* argv[])
{
    FILE *fp;
    fp=popen("ls","r");
    ERROR_CHECK(fp,NULL,"popen");
    char buf[128];
    fread(buf,sizeof(char),sizeof(buf)-1,fp);
    printf("buf=%s\n",buf);
    pclose(fp);
    return 0;
}
buf=add
add.c
a.out
popen_ls.c
popen_r
popen_r.c
popen_w.c
print
print.c

popen_read_print.c

#include <func.h>

int main(int argc, char* argv[])
{
    FILE *fp;
    fp=popen("./print","r");
    ERROR_CHECK(fp,NULL,"popen");
    char buf[128];
    fgets(buf,sizeof(buf),fp);
    printf("buf=%s\n",buf);
    pclose(fp);
    return 0;
}
print.c
#include <func.h>

int main(int argc, char* argv[])
{
    printf("I am print\n");
    return 0;
}

popen_write_add.c

#include <func.h>

int main(int argc, char* argv[])
{
    FILE *fp;
    fp=popen("./add","w");
    ERROR_CHECK(fp,NULL,"popen");
    char buf[128]="3 4";
    fputs(buf,fp);
    pclose(fp);
    return 0;
}
add.c
#include <func.h>

int main(int argc, char* argv[])
{
    int i,j;
    scanf("%d %d",&i,&j);
    printf("sum=%d\n",i+j);
    return 0;
}

无名管道PIPE

管道通讯原理

两个进程之间进行通信,因为它们拥有各自独有的进程地址空间,所以必须使两个进程指向一块公共内存,而这块内存就是在内核中开辟出来的缓冲区。

管道的两端,一端负责输入,一端负责输出,管道的两端分别连接两个进程。进程1负责将数据输入到缓冲区,进程2将缓冲区的数据读出,这样就实现了两个进程的通信。

无名管道的特点

1、只能在亲缘关系进程间通信(父子或兄弟),无名管道只能由创建进程所访问。通常情况下,父进程创建一个管道,并使用它来与其子进程进行通信(该子进程由 fork() 来创建)。子进程继承了父进程的打开文件。由于管道是一种特殊类型的文件,因此子进程也继承了父进程的管道。

2、半双工(固定的读端和固定的写端),数据只能单向流动。无名管道是单向的,只允许单向通信。如果需要双向通信,那么就要采用两个管道

3、无名管道是一种特殊的文件,不属于某种文件系统,自己单独构成,可以用 read、write 等系统调用,并且无名管道只能存在于内存中。

4、管道数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

函数原型:

#include <unistd.h>
int pipe(int fds[2]);

管道在程序中用一对文件描述符表示, 其中一个文件描述符有可读属性, 一个有可写属性。fds[0]是读属性,fds[1]是写属性。

函数 pipe()

用于创建一个无名管道,如果成功,fds[0]存放可读的文件描述符,fds[1]存放可写文件描述符,并且函数返回 0,否则返回-1。

通过调用 pipe 获取这对打开的文件描述符后,一个进程就可以从 fds[0]中读数据,而另一个进程就可以往 fds[1]中写数据。当然两进程间必须有继承关系,才能继承这对打开的文件描述符。

管道不是真正的物理文件,不是持久的,即两进程终止后,管道也自动消失了。
在这里插入图片描述
管道两端的关闭是有先后顺序的, 如果先关闭写端则从另一端读数据时, read 函数将返
回 0,表示管道已经关闭。

但是如果先关闭读端,则从另一端写数据时,将会使写数据的进程接收到 SIGPIPE 信号,如果写进程不对该信号进行处理,将导致写进程终止,如果写进程处理了该信号,则写数据的 write 函数返回一个负值,表示管道已经关闭。

SIGPIPE信号

什么时候会产生在这个信号?

写管道时,如果管道的读端被close了话,向管道“写”数据的进程会被内核发送一个SIGPIPE信号,发这个信号的目的就是想通知你,管道所有的“读”都被关闭了。

这就好比别人把水管的出口(读)给堵住了,结果你还一直往里面灌水(写),别人跟定会警告你,因为你这样可能会对水管造成损害,道理其实是类似的。

由于这个信号的默认动作是终止,所以收到这个信号的进程会被终止,如果你不想被终止的话,你可以忽略、捕获、或者屏蔽这个信号。

pipe_sigpipe.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>

void print_err(char *estr)
{
        perror(estr);
        exit(-1);
}
int main(void)
{
       int ret = 0;
        int pipefd[2] = {0};

        ret = pipe(pipefd);

        if(ret == -1) print_err("pipe fail");
        ret = fork();
        if(ret > 0)
        {
                signal(SIGPIPE, SIG_IGN);
                close(pipefd[0]);
                while(1)
                {
                        write(pipefd[1], "hello", 5);
                        sleep(1);
                }
        }
        else if(ret == 0)
        {
                close(pipefd[1]);
                close(pipefd[0]);
                while(1)
                {
                        char buf[30] = {0};
                        bzero(buf, sizeof(buf));
                        read(pipefd[0], buf, sizeof(buf));
                        printf("child, recv data:%s\n", buf);
                }
        }
        return 0;
}

父进程尝试在向管道写入数据的时候,被内核发送了SIGPIPE信号,父进程收到SIGPIPE信号之后就会被终止。如果不想要终止我们就可以进行忽略或者捕获操作。使用signal函数:
signal(SIGPIPE,SIG_IGN); 就可以把信号设置为忽略。

只有当管道所有的读端都被关闭时,才会产生这个信号,只有还有一个读端开着,就不会产生。

SIGPIPE信号引用自:
https://blog.csdn.net/qq_43648751/article/details/104680142

pipe_father_write_son_read.c

创建父子进程,创建无名管道,父写子读

#include <func.h>

int main(int argc, char* argv[])
{
    int fds[2];
    //int fds1[2];
    pipe(fds);  //管道的读端就存在fds[0],写端存在fds[1]
    if(!fork())
    {
        close(fds[1]);  //关闭写端,因为子进程要读数据
        char buf[128]={0};
        read(fds[0],buf,sizeof(buf));
        printf("I am child gets = %s\n",buf);
        exit(0);
    }
    else
    {
        close(fds[0]);  //关闭读端,因为父进程要写数据
        write(fds[1],"I hen niu",9);
        wait(NULL);
    }
    return 0;
}

命名管道FIFO

无名管道只能在亲缘关系的进程间通信大大限制了管道的使用,有名管道突破了这个限制,通过指定路径名的范式实现不相关进程间的通信。

因为有文件名,所以进程可以直接调用open函数打开文件,从而得到文件描述符,不需要像无名管道一样,必须在通过继承的方式才能获取到文件描述符。

所以任何两个进程之间,如果想要通过“有名管道”来通信的话,不管它们是亲缘的还是非亲缘的,只要调用open函数打开同一个“有名管道”文件,然后对同一个“有名管道文件”进行读写操作,即可实现通信。

总之,不管是亲缘进程还是非亲缘进程,都可以使用有名管道来通信。

创建FIFO文件 mkfifo

创建 FIFO 文件与创建普通文件很类似,只是创建后的文件用于 FIFO。

创建FIFO文件函数原型:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

参数 pathname 为要创建的 FIFO 文件的全路径名;
参数 mode 为文件访问权限
如果创建成功,则返回 0,否则-1。

create_fifo.c

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[])   //演示通过命令行传递参数
{
	if(argc != 2){
		puts("Usage: MkFifo.exe {filename}");
		return -1;
	}
	if(mkfifo(argv[1], 0666) == -1){
		perror("mkfifo fail");
		return -2;
	}
	return 0;
}

删除FIFO文件 unlink

删除FIFO文件的函数原型为:

#include <unistd.h>
int unlink(const char *pathname);

delete_fifo.c

#include <func.h>

int main(int argc, char* argv[])
{
    ARGS_CHECK(argc,2);
    int ret;
    ret=unlink(argv[1]);
    ERROR_CHECK(ret,-1,"unlink");
    return 0;
}

创建和删除FIFO文件

用命令mkfifo创建 不能重复创建,用命令unlink删除
创建完毕之后,就可以访问FIFO文件了:一个终端:cat < myfifo;
另一个终端:echo “hello” > myfifo;

打开、关闭 FIFO 文件 open close

对 FIFO 类型的文件的打开/关闭跟普通文件一样, 都是使用 open 和 close 函数。

如果打开时使用 O_WRONLY 选项,则打开 FIFO 的写入端,如果使用 O_RDONLY 选项,则打开
FIFO 的读取端,写入端和读取端都可以被几个进程同时打开。

如果以读取方式打开 FIFO, 并且还没有其它进程以写入方式打开 FIFO, open 函数将被阻塞。同样,如果以写入方式打开 FIFO,并且还没其它进程以读取方式 FIFO,open 函数也将被阻塞。

与无名管道PIPE 相同,关闭 FIFO 时,如果先关读端,将导致继续往 FIFO 中写数据的进程接收 SIGPIPE 的信号。

读写FIFO文件 read write

可以采用与普通文件相同的读写方式读写 FIFO
先用管道创建命令创建管道MyFifo.pip

fifo_write.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
	int fdFifo = open("MyFifo.pip",O_WRONLY); //1. 打开(判断是否成功打开略)
	write(fdFifo, "hello", 6); //2. 写
	close(fdFifo); //3. 关闭
	return 0;
}

fifo_read.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
	char szBuf[128];
	int fdFifo = open("MyFifo.pip",O_RDONLY); //1. 打开
	if(read(fdFifo,szBuf,sizeof(szBuf)) > 0) //2. 读
		puts(szBuf);
	close(fdFifo); //3. 关闭
	return 0;
}
gcc –o write write.c
gcc –o read read.c
./write //发现阻塞,要等待执行./read
./read
在屏幕上输出 hello

有名管道双向通信

同样的,使用一个“有名管道”是无法实现双向通信的,因为也涉及到抢数据的问题。所以双向通信时需要两个管道。

图解:
在这里插入图片描述
关闭端口之后的图解:
在这里插入图片描述
当我们在一个进程里面既要进行读,也要进行写的时候,如果如果没有数据,就会阻塞在read函数,那么就会导致write函数不能执行,所以我们给出下面图解,让两个进程的读写都不会互相干扰到。

让程序1和程序2分别创建一个子进程,在第一个程序里面父进程读管道2,子进程写管道1,在另一个程序里面父进程写管道2,子进程读管道1。那么程序就会并发运行互不干扰,并且在捕获SININT信号在删除文件的时候只需要一个进程进行删除即可。我们把删除文件的操作放在父进程,子进程在收到SIGINT信号之后只是正常的终止进程。

图解:
在这里插入图片描述
代码演示:我们对于函数进行封装,以便于我们创建多个管道文件

文件 name_pipe.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"

void print_err(char *estr)
{
        perror(estr);
        exit(-1);
}

int creat_open_fifo(char *fifoname, int open_mode)
{
        int ret = -1;
        int fd = -1;

        ret = mkfifo(fifoname, 0664);
        if(ret == -1 && errno!=EEXIST) print_err("mkfifo fail");

        fd = open(fifoname, open_mode);
        if(fd == -1) print_err("open fail");

        return fd;
}

void signal_fun(int signo)
{
        //unlink();
        remove(FIFONAME1);
        remove(FIFONAME2);
        exit(-1);
}

int main(void)
{
        char buf[100] = {0};
        int ret = -1;
        int fd1 = -1;
        int fd2 = -1;


        fd1 = creat_open_fifo(FIFONAME1, O_WRONLY);
        fd2 = creat_open_fifo(FIFONAME2, O_RDONLY);

        ret = fork();
        if(ret > 0)
        {
                signal(SIGINT, signal_fun);
                while(1)
                {
                        bzero(buf, sizeof(buf));
                        scanf("%s", buf);
                        write(fd1, buf, sizeof(buf));
                }
        }
        else if(ret == 0)
        {
                while(1)
                {
                        bzero(buf, sizeof(buf));
                        read(fd2, buf, sizeof(buf));
                        printf("%s\n", buf);
                }
        }

        return 0;
}

文件:name_pipe_read.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>


#define FIFONAME1 "./fifo1"
#define FIFONAME2 "./fifo2"

void print_err(char *estr)
{
        perror(estr);
        exit(-1);
}

int creat_open_fifo(char *fifoname, int open_mode)
{
        int ret = -1;
        int fd = -1;

        ret = mkfifo(fifoname, 0664);

        fd = open(fifoname, open_mode);
        if(fd == -1) print_err("open fail");

        return fd;
}

void signal_fun(int signo)
{
        //unlink();
        remove(FIFONAME1);
        remove(FIFONAME2);
        exit(-1);
}

int main(void)
{
        char buf[100] = {0};
        int ret = -1;
        int fd1 = -1;
        int fd2 = -1;


        fd1 = creat_open_fifo(FIFONAME1, O_RDONLY);
        fd2 = creat_open_fifo(FIFONAME2, O_WRONLY);


        ret = fork();
        if(ret > 0)
        {
                signal(SIGINT, signal_fun);
                while(1)
                {
                        bzero(buf, sizeof(buf));
                        read(fd1, buf, sizeof(buf));
                        printf("recv:%s\n", buf);
                }
        }
        else if(ret == 0)
        {
                while(1)
                {
                        bzero(buf, sizeof(buf));
                        scanf("%s", buf);
                        write(fd2, buf, sizeof(buf));

                }
        }

        return 0;
}

有名管道双向通信内容引用自:
https://blog.csdn.net/qq_43648751/article/details/104724453

管道示例:基于管道的客户端服务器程序

程序说明:
1、服务器端:
维护服务器管道,接受来自客户端的请求并处理(本程序为接受客户端发来的字符串,将小写字母转换为大写字母。)然后通过每个客户端维护的管道发给客户端。
2. 客户端
向服务端管道发送数据,然后从自己的客户端管道中接受服务器返回的数据。

服务器端 server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
typedef struct tagmag
{
	int client_pid ;
	char my_data[512] ;
}MSG;
int main()
{
	int server_fifo_fd , client_fifo_fd;
	char client_fifo[256];
	MSG my_msg ;
	char * pstr ;
	memset(&my_msg , 0 , sizeof(MSG));
	mkfifo("SERVER_FIFO_NAME",0777);
	server_fifo_fd = open("./SERVER_FIFO_NAME",O_RDONLY);
	if(server_fifo_fd == -1)
	{
		perror("server_fifo_fd");
		exit(-1);
	}
	int iret ;
	while( (iret = read(server_fifo_fd , &my_msg ,sizeof(MSG))>0))
	{
		pstr = my_msg.my_data ;
		printf("%s\n",my_msg.my_data);
		while(*pstr != '\0')
		{
			*pstr = toupper(*pstr);
			pstr ++ ;
		}
		memset(client_fifo , 0 , 256);
		sprintf(client_fifo , "CLIENT_FIFO_%d" , my_msg.client_pid);
		client_fifo_fd = open(client_fifo , O_WRONLY);
		if(client_fifo_fd == -1)
		{
			perror("client_fifo_fd");
			exit(-1);
		}
		write(client_fifo_fd , &my_msg, sizeof(MSG));
		printf("%s\n", my_msg.my_data);
		printf("OVER!\n");
		close(client_fifo_fd);
	}
	return 0 ;
}

客户端代码:client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
typedef struct tagmag
{
	int client_pid ;
	char my_data[512] ;
}MSG;
int main()
{
	int server_fifo_fd,client_fifo_fd ;
	char client_fifo[256]={0};
	sprintf(client_fifo,"CLIENT_FIFO_%d",getpid());
	MSG my_msg ;
	memset(&my_msg , 0 , sizeof(MSG));
	my_msg.client_pid = getpid();
	server_fifo_fd = open("./SERVER_FIFO_NAME",O_WRONLY);
	mkfifo(client_fifo , 0777);
	while(1)
	{
		int n = read(STDIN_FILENO, my_msg.my_data, 512);
		my_msg.my_data[n] = '\0' ;
		write(server_fifo_fd , &my_msg , sizeof(MSG));
		client_fifo_fd = open(client_fifo , O_RDONLY);
		//memset(&my_msg , 0 , sizeof(MSG));
		n = read(client_fifo_fd, &my_msg , sizeof(MSG));
		my_msg.my_data[n]= 0 ;
		write(STDOUT_FILENO, my_msg.my_data ,strlen(my_msg.my_data) );
		close(client_fifo_fd);
	}
	unlink(client_fifo);
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章