Linux 进程间通信

目录

进程间通信介绍

进程间通信的概念

进程间通信的目的

进程间通信的本质

进程间通信分类

管道

什么是管道

匿名管道

匿名管道的原理

pipe函数

匿名管道的使用步骤

管道的大小

命名管道

使用命令创建命名管道

创建一个命名管道

用命名管道实现serve&client通信

用命名管道实现派发计算任务

用命名管道实现文件拷贝

匿名管道和命名管道的区别


进程间通信介绍

进程间通信的概念

进程间通信简称IPC(Interprocess communication),进程间通信就是在不同的进程之间的传播或交换信息

进程间通信的目的

**数据传输:**一个进程需要将它的数据发送给另一个进程

**资源共享:**多个进程之间共享同样的资源。

**通知事件:**一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止 时要通知父进程)。

**进程控制:**有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另 一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间通信的本质

让不同的进程看到同一份资源

由于各个运行进程之间具有独立性,这个独立性主要体现在数据层面,而代码逻辑层面可以公有也可以私有(例如父进程),因此各个进程之间要实现通信是非常困难的

各个进程之间若想实现通信,一定要借助第三方资源,这些进程就可以通过向这个第三方资源,写入或者读取数据,进而实现进程之间的通信,这个第三方资源实际上就是操作系统提供的一段内存区域

因此,进程间通信的本质就是,让不同的进程看到同一份资源(内存,文件也缓冲区等)。由于这份资源可以由操作系统中不同模块提供,因此出现了不同的进程间通信方式

进程间通信分类

管道

  • 匿名管道
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

管道

什么是管道

管道是Unix中最古老的进程间通信的形式。

我们把从一个进程连接到另一个进程的一个数据流称为一个"管道"

例如:统计我们当前使用云服务器上的登录用户个数

其中who命令和wc命令都是两个程序,当它们运行起来后就变成了两个进程,who进程通过标准输出将数据打到 "管道" 当中,wc进程再通过标准输入从 "管道" 当中读取数据,至此便完成了数据的传输,进而完成数据的进一步加工处理

who命令用于查看当前云服务器的登陆用户(一行显示一个用户),wc -l用于统计当前行数

匿名管道

匿名管道的原理

匿名管道用于进程间通信,且仅限于本地父子之间的通信

进程间通信的本质就是,让不同的进程看到同一份资源

原理:

让父子进程看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或是读取操作,进而实现进程间通信

注意:

这里父子进程看到的同一份文件资源(内存级文件)是由操作系统来维护的,所以当父子进程进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝

管道虽然用的是文件的方案,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有IO参与会降低效率,而且也没有必要。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在。

pipe函数

int pipe(int pipefd[2]);

pipe的参数是一个输出型参数,主要用来返回指向管道读端和写段的两个文件描述符

pipefd[0]: 指向管道读端的文件描述符

pipefd[1]: 指向管道写端的文件描述符

pipe函数调用成功返回0,调用失败返回-1

匿名管道的使用步骤

1.父进程调用pipe函数创建管道

2.父进程创建子进程

3.然后关闭父进程的读端/写端或者子进程的读端/写端,只要最后父子进程读写端配对即可

以下代码是父进程向管道当中写入十行代码,子进程去匿名管道中将数据读取出来

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <string>
#include <cstring>

#define N 2
#define SIZE 1024

using namespace std;


void writer(int wfd)
{
    char buffer[SIZE]; //缓冲区

    string str = "Hello World!!!";
    pid_t id = getpid();
    int number = 0;

    int count = 10;
    while(count--)
    {
        sleep(1);
        buffer[0] = '\0';
        snprintf(buffer, sizeof(buffer), "%s-%d-%d", str.c_str(), ++number, id);
        // cout << buffer << endl;
        write(wfd, buffer, sizeof(buffer));
    }
}

void reader(int rfd)
{
    char buffer[SIZE];

    int count = 10;
    while(count--)
    {
        buffer[0] = '\0';
        ssize_t n = read(rfd, buffer, sizeof(buffer));
        if(n > 0) 
        {
            buffer[n] = '\0'; //使用的数组,需要结束标记
            cout << "father get a message [" << getpid() << "]: " << buffer << endl;
        }
        else if(n == 0)
        {
            cout << "father read fail!!!" << endl;
            break;
        }
    }
}

int main()
{
    int pipefd[N];
    if(pipe(pipefd))
    {
        perror("main::pipe");
        return 1;
    }

    pid_t id = fork();
    if(id < 0) return 2;

    if(id == 0)
    {
        //子进程读,关闭写端
        close(pipefd[1]);
        reader(pipefd[0]);
        close(pipefd[0]);

        exit(0);
    }

    //父进程写,关闭读端
    close(pipefd[0]);
    writer(pipefd[1]);
    
    //父进程等待子进程退出
    waitpid(id, nullptr, 0);
    
    close(pipefd[1]);
    return 0;
}

管道的五大特点

1.只有具有血缘关系的进程才能进行通信

例如:

可以观察到,我们使用匿名管道创建的3个进程有共同的父亲

2.管道通信是单向的

3.管道是面向字节流的

例如:向管道中输入大量数据,至于你怎么读取数据那是你的事,你可以一次拿5个字节或10个字节的数据,我们将这种方式称为约定,约定好一次拿多少就是多少,不能拿多也不能拿少

4.父子进程再进行通信的时候会进行协同------同步与互斥机制,保护管道文件的数据安全

我们将一次只允许一个进程使用的资源,称为临界资源。管道在同一时刻只允许一个进程对其进行写入或是读取操作,因此管道也就是一种临界资源。

临界资源是需要被保护的,若是我们不对管道这种临界资源进行任何保护机制,那么就可能出现同一时刻有多个进程对同一管道进行操作的情况,进而导致同时读写、交叉读写以及读取到的数据不一致等问题。

为了避免这些问题,内核会对管道操作进行同步与互斥:

  • 同步: 两个或两个以上的进程在运行过程中协同步调,按预定的先后次序运行。比如,A任务的运行依赖于B任务产生的数据。例如:父进程向管道文件中写入数据时,子进程会进入阻塞状态等待父进程写入完成
  • 互斥: 一个公共资源在任一时刻只能被一个进程使用,多个进程不能同时使用公共资源。

实际上,同步是一种更为复杂的互斥,而互斥是一种特殊的同步。对于管道的场景来说,互斥就是两个进程不可以同时对管道进行操作,它们会相互排斥,必须等一个进程操作完毕,另一个才能操作,而同步也是指这两个不能同时对管道进行操作,但这两个进程必须要按照某种次序来对管道进行操作。

也就是说,互斥具有唯一性和排它性,但互斥并不限制任务的运行顺序,而同步的任务之间则有明确的顺序关系。

5.管道是基于文件的,而文件的生命周期是基于进程的

进程结束时,会关闭已有的文件资源,管道的底层就是使用内存文件让两个进程之间进行通信,即进程结束,文件结束,管道也就结束了

管道的四种特殊情况

1.读写端正常,写端进行写入,管道为空,则读端会等待写端写入数据,在此期间读端处于阻塞状态

2.读写端正常,写端向管道中写入数据,如果管道被写满,写端在此期间会处于阻塞状态

3.读端关闭,写端正常写入,操作系统会直接杀死该进程

4.写端关闭,读端进行读取,读端就会读到0,表明读到了文件pipe的结尾,不会被阻塞

其中前面两种情况就能够很好的说明,管道是自带同步与互斥机制的,读端进程和写端进程是有一个步调协调的过程的,不会说当管道没有数据了读端还在读取,而当管道已经满了写端还在写入。读端进程读取数据的条件是管道里面有数据,写端进程写入数据的条件是管道当中还有空间,若是条件不满足,则相应的进程处于阻塞状态,直到条件满足后才会被再次唤醒。

第三种情况,读端进程已经将管道当中的所有数据都读取出来了,而且此后也不会有写端再进行写入了,那么此时读端进程也就可以执行该进程的其他逻辑了,而不会被挂起。

第四种情况,既然管道当中的数据已经没有进程会读取了,那么写端进程的写入将没有意义,因此操作系统不会做这种低效,无意义的事情,它会直接将写端进程杀掉。而此时子进程代码都还没跑完就被终止了,属于异常退出,那么子进程必然收到了某种信号。

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>

#define N 2
#define SIZE 1024

using namespace std;

int main()
{
    int pipefd[N] = {0};
    if(pipe(pipefd) < 0)
    {
        return 1;
    }

    pid_t id = fork();
    if(id < 0)
    {
        return 2;
    }

    //child
    if(id == 0)
    {
        //char buffer[SIZE] = {0};
        const char* s = "I am child process!";

        close(pipefd[0]);
        int count = 10;
        while(count--)
        {
            write(pipefd[1], s, sizeof(s));
        }
                
        close(pipefd[1]);
    }

    //father
    close(pipefd[1]);
    close(pipefd[0]);

    int status = 0;
    pid_t rid = waitpid(id, &status, 0);

    if(rid > 0)
    {
        printf("signal kill: %d\n", status & 0x7F);
    }

    return 0;
}

可以看到杀死该进程的是13号信号

我们可以使用 kill -l 命令来查看13号信号的详细指令

由此可知,第四种情况 是由 SIGPIPE 指令杀死的

管道的大小

管道的容量是有限的,如果管道已满,那么写端将阻塞或失败,那么管道的最大容量是多少呢?

方法一:使用 ulimit -a 命令

根据显示,管道的最大容量是 512 × 8 = 4096 字节。

方法二:自行测试

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <sys/wait.h>

#define N 2

using namespace std;

int main()
{
    int pipefd[N] = {0};
    if(pipe(pipefd) < 0)
    {
        return 1;
    }

    pid_t id = fork();
    if(id < 0)
    {
        return 2;
    }

    //child
    if(id == 0)
    {
        char c = 'c';
        close(pipefd[0]);
        
        int cnt = 0;
        while(true)
        {
            write(pipefd[1], &c, 1);
            printf("cnt: %d\n", cnt++);
        }    
        close(pipefd[1]);

    }

    //father
    char ch;
    close(pipefd[1]);

    int status = 0;
    waitpid(id, &status, 0);

    close(pipefd[0]);

    return 0;
    
}

可以看到,在读端进程不进行读取的情况下,写端进程最多写65536字节的数据就被操作系统挂起了,也就是说,我当前Linux版本中管道的最大容量是65536字节

命名管道

如果要实现两个毫不相关进程之间的通信,可以使用命名管道来做到。命名管道就是一种特殊类型的文件,两个进程通过命名管道的文件名打开同一个管道文件,此时这两个进程也就看到了同一份资源,进而就可以进行通信了。

注意:

  1. 普通文件是很难做到通信的,即便做到通信也无法解决一些安全问题。

  2. 命名管道和匿名管道一样,都是内存文件,只不过命名管道在磁盘有一个简单的映像,但这个映像的大小永远为0,因为命名管道和匿名管道都不会将通信数据刷新到磁盘当中。

使用命令创建命名管道

我们可以使用mkfifo命令创建一个命名管道。

可以看到,创建出来的文件的类型是p,代表该文件是命名管道文件。

使用这个命名管道文件,就能实现两个进程之间的通信了。我们在一个进程(进程A)中用shell脚本每秒向命名管道写入一个字符串,在另一个进程(进程B)当中用cat命令从命名管道当中进行读取。

现象就是当进程A启动后,进程B会每秒从命名管道中读取一个字符串打印到显示器上。这就证明了这两个毫不相关的进程可以通过命名管道进行数据传输,即通信。

之前我们说过,当管道的读端进程退出后,写端进程再向管道写入数据就没有意义了,此时写端进程会被操作系统杀掉,在这里就可以很好的得到验证:当我们终止掉读端进程后,因为写端执行的循环脚本是由命令行解释器bash执行的,所以此时bash就会被操作系统杀掉,我们的云服务器也就退出了。

创建一个命名管道

cpp 复制代码
int mkfifo(const char *pathname, mode_t mode);

mkfifo函数的第一个参数是pathname,表示要创建的命名管道文件。

  • 若pathname以路径的方式给出,则将命名管道文件创建在pathname路径下。
  • 若pathname以文件名的方式给出,则将命名管道文件默认创建在当前路径下。(注意当前路径的含义)

mkfifo函数的第二个参数是mode,表示创建命名管道文件的默认权限。

例如,将mode设置为0666,则命名管道文件创建出来的权限如下:

但实际上创建出来文件的权限值还会受到umask(文件默认掩码)的影响,实际创建出来文件的权限为:mode&(~umask)。umask的默认值一般为0002,当我们设置mode值为0666时实际创建出来文件的权限为0664

若想创建出来命名管道文件的权限值不受umask的影响,则需要在创建文件前使用umask函数将文件默认掩码设置为0

mkfifo的返回值

  • 命名管道创建成功,返回0。
  • 命名管道创建失败,返回-1。

创建命名管道示例:

使用以下代码即可在当前路径下,创建出一个名为myfifo的命名管道

用命名管道实现serve&client通信

实现服务端(server)和客户端(client)之间的通信之前,我们需要先让服务端运行起来,我们需要让服务端运行后创建一个命名管道文件,然后再以读的方式打开该命名管道文件,之后服务端就可以从该命名管道当中读取客户端发来的通信信息了。

cpp 复制代码
#include "comm.h"

int main()
{
	umask(0);

	if(mkfifo(FILE_NAME, 0666) < 0) //服务端创建命名管道文件,失败则退出
	{
		perror("mkfifo");
		return 1;
	}

	int fd = open(FILE_NAME, O_RDONLY); //已读的方式打开管道文件
	if(fd < 0) //失败直接返回1
	{
		perror("open");
		return 1;
	}	

	char msg[SIZE]; //存放从客户端传来的消息

	while(1)
	{
		msg[0] = '\0';//每次读之前将msg清空

		ssize_t s = read(fd, msg, sizeof(msg)-1);
		if(s > 0)
		{
			msg[s] = '\0';
			printf("client: %s\n", msg);
		}	
		else if(s==0)
		{
			printf("client quit!\n"); //管道中无数据,客户端正常退出
			break;
		}
		else
		{
			printf("client error!\n"); //s小于0,客户端异常退出
			break;	
		}
	}
	
	close(fd); //传数写入完毕,关闭管道文件

	return 0;
}

而对于客户端来说,因为服务端运行起来后命名管道文件就已经被创建了,所以客户端只需以写的方式打开该命名管道文件,之后客户端就可以将通信信息写入到命名管道文件当中,进而实现和服务端的通信。

cpp 复制代码
#include "comm.h"

int main()
{
	int fd = open(FILE_NAME, O_WRONLY); //以写的方式打开管道文件
	if(fd < 0) 
	{
		perror("open");
		return 1;
	}

	string line;		
	while(true)
	{
		cout << "client Enter@ ";		
		getline(cin, line);

		ssize_t s = write(fd, line.c_str(), line.size());
	}

	close(fd);
	return 0;
}

对于如何让客户端和服务端使用同一个命名管道文件,这里我们可以让客户端和服务端包含同一个头文件,该头文件当中提供这个共用的命名管道文件的文件名,这样客户端和服务端就可以通过这个文件名,打开同一个命名管道文件,进而进行通信了。

共用comm.h头文件的代码如下:

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define SIZE 1024
#define FILE_NAME "myfifo"

using namespace std;

将服务端运行起来后,就可以在客户端看到命名管道了

接着再将客户端也运行起来,此时我们从客户端写入的信息被客户端写入到命名管道当中,服务端再从命名管道当中将信息读取出来打印在服务端的显示器上,该现象说明服务端是能够通过命名管道获取到客户端发来的信息的,换句话说,此时这两个不相关进程之间是能够通信的。

服务端和客户端的退出关系

当客户端退出后,服务端将管道当中的数据读完后就再也读不到数据了,那么此时服务端也就会去执行它的其他代码了(在当前代码中是直接退出了)。

当服务端退出后,客户端写入管道的数据就不会被读取了,也就没有意义了,那么当客户端下一次再向管道写入数据时,就会收到操作系统发来的13号信号(SIGPIPE),此时客户端就被操作系统强制杀掉了。

管道通信是在内存中进行的

如果我们让服务端不去读取客户端往管道中写入的数据,那么该管道的大小是否会发生变化

cpp 复制代码
#include "comm.h"

int main()
{
	umask(0);

	if(mkfifo(FILE_NAME, 0666) < 0)
	{
		perror("mkfifo");
		return 1;
	}

	int fd = open(FILE_NAME, O_RDONLY);
	if(fd < 0)
	{
		perror("open");
		return 1;
	}	

	while(1)
	{
		//服务端什么都不做

	}
	
	close(fd);

	return 0;
}

可以看到,尽管服务端不读取管道当中的数据,但是管道当中的数据并没有被刷新到磁盘,使用ll命令看到命名管道文件的大小依旧为0,也就说明了双方进程之间的通信依旧是在内存当中进行的,和匿名管道通信是一样的。

用命名管道实现派发计算任务

两个进程的通信不仅可以接收字符串, 还可以实现复杂一点的计算让任务,这里我们以客户端向服务端派发计算任务为例,客户端通过管道向服务端发送双操作数的计算请求,服务端接收到客户端的信息后需要计算出相应的结果。

这里我们无需更改客户端的代码,只需改变服务端处理通信信息的逻辑即可。

服务端代码:

cpp 复制代码
#include "comm.h"

int main()
{
	umask(0);

	InIt init;

	int fd = open(FILE_NAME, O_RDONLY);
	if(fd < 0)
	{
		perror("comm.h::main::open");
		return 1;
	}	

	char msg[SIZE];

	while(1)
	{
		msg[0] = '\0'; //每次循环缓冲区清空
		ssize_t n = read(fd, msg, sizeof(msg) - 1);
		if(n > 0)
		{
			msg[n] = '\0';
			cout << "serve say@ ";
			//printf("serve say@ %s\n", msg);

			const char *label = "+-*/";
			char *p = msg;
			int flag = 0;

			while(*p)
			{
				switch(*p)
				{
					case '+':
						flag = 1;
						break;
					case '-': 
						flag = 2;
						break;
					case '*': 
						flag = 3;
						break;
					case '/': 
						flag = 4;
						break;
				}
				p++;
			}

			char* str1 = strtok(msg, "+-*/");
			char* str2 = strtok(NULL, "+-*/");
			int num1 = atoi(str1);
			int num2 = atoi(str2);
			int ret = 0;
			switch(flag)
			{
				case 1:
					ret = num1 + num2;
					break;
				case 2:
					ret = num1 - num2;
					break;
				case 3:
					ret = num1 * num2;
					break;
				case 4:
					ret = num1 / num2;
					break;
			}

			printf("%d %c %d = %d\n", num1, label[flag - 1], num2, ret);

		}
		else if (n == 0)
		{
			cout << "read quit " << endl; 
			break;
		}
		else
		{
			cout << "read assert " << endl;
			break;
 		}
		sleep(1);
	}
	
	close(fd);

	return 0;
}

客户端代码:

cpp 复制代码
#include "comm.h"

int main()
{
	int fd = open(FILE_NAME, O_WRONLY);
	if(fd < 0)
	{
		perror("open");
		return 1;
	}

	string line;		
	while(true)
	{
		cout << "client Enter@ ";		
		getline(cin, line);

		write(fd, line.c_str(), line.size());
	}

	close(fd);
	return 0;
}

共用的comm.h头文件

cpp 复制代码
#ifndef __COMM_H__
#define __COMM_H__

#include <iostream>
#include <string>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstdio>
#include <cstring>

#define SIZE 1024
#define FILE_NAME "myfifo"


using namespace std;

class InIt
{
public:
    InIt()
    {
        if(mkfifo(FILE_NAME, 0666) < 0)
	    {
            perror("comm.h::mkfifo");
            exit(0);
    	}
    }

    ~InIt()
    {
        if(unlink(FILE_NAME) < 0)
        {
            printf("return success!\n");
        }
    }
};

#endif

运行代码如下:

用命名管道实现文件拷贝

需要拷贝的文件是mycopy.txt,该文件当中的内容如下:

我们要做的就是,让客户端将mycopy.txt文件通过管道发送给服务端,在服务端创建一个file.txt文件,并将从管道获取到的数据写入file.txt文件当中,至此便实现了mycopy.txt文件的拷贝。

其中服务端需要做的就是,创建命名管道并以读的方式打开该命名管道,再创建一个名为file.txt的文件,之后需要做的就是将从管道当中读取到的数据写入到file.txt文件当中即可。

服务端代码:

cpp 复制代码
#include "comm.hpp"

int main()
{
    Init init;

    int fd = open(PATHNAME, O_CREAT | O_RDONLY, MODE);
    if(fd < 0)
    {
        perror("client.cc::fd::main::open");
        return 1;
    }

    int fdin = open("file.txt", O_CREAT | O_WRONLY, MODE);
    if(fd < 0)
    {
        perror("client.cc::fdin::main::open");
        return 1;
    }

    char buffer[SIZE] = {0};
    while(true)
    {
        buffer[0] = '\0';
        //printf("");
        ssize_t s = read(fd, buffer, sizeof(buffer));
        if(s > 0)
        {
            buffer[s] = '\0';
            write(fdin, buffer, sizeof(buffer));
            printf("copy complete!\n");
        }
        else if(s == 0)
        {
            printf("process normal quit!!\n");
            break;
        }
        else
        {
            printf("process error quit!!\n");
            break;
        }
    }

    close(fd);
    close(fdin);
    return 0;
}

客户端代码:

cpp 复制代码
#include "comm.hpp"


int main()
{
    int fdin = open("mycopy.txt", O_CREAT | O_RDONLY, MODE);
    if(fdin < 0)
    {
        perror("serve.cc::mkfifo::main");
        return 1;
    }

    int fd = open(PATHNAME, O_CREAT | O_WRONLY, MODE);
    if(fd < 0)
    {
        perror("serve::open::main");
    }

    //缓冲区
    char buffer[SIZE] = {0};

    while(true)
    {
        buffer[0] = '\0';
        ssize_t s = read(fdin, buffer, sizeof(buffer));
        if(s > 0)
        {   
            buffer[s] = '\0';
            write(fd, buffer, sizeof(buffer));
            printf("copy success!!!\n");
        }
        else if(s == 0)
        {
            printf("client normal quit!!\n");
            break;
        }
        else
        {
            printf("client error quit!!\n");
            break;
        }
    }

    close(fdin);
    close(fd);
    return 0;
}

共用的comm.h头文件:

cpp 复制代码
#ifndef __COMM_HPP__
#define __COMM_HPP__

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define PATHNAME "myfifo"
#define MODE 0666
#define SIZE 1024

class Init
{
public:
    Init()
    {
        umask(0);

        //在服务端创建一个管道
        if(mkfifo(PATHNAME, MODE) < 0)
        {
            perror("comm.hpp::main::mkfifo");
        }
    }

    ~Init()
    {
        if(unlink(PATHNAME) < 0)
        {
            perror("comm.hpp::main::unlink");
        }
    }
};

#endif

运行结果:

用命名管道实现文件拷贝的意义是什么?

因为这里是使用管道在本地进行的文件拷贝,所以看似没什么意义,但我们若是将这里的管道想象成"网络",将客户端想象成"Windows Xshell",再将服务端想象成"centos服务器"。那我们此时实现的就是文件上传的功能,若是将方向反过来,那么实现的就是文件下载的功能。

匿名管道和命名管道的区别

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,由open函数打开。
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。
相关推荐
musk12127 分钟前
ubuntu 安装 g++
linux·运维·ubuntu
徐小黑ACG11 分钟前
Ubuntu下载docker、xshell
linux·ubuntu·docker
A~taoker20 分钟前
jmeter接口自动化+ant执行(方案)
运维·jmeter·自动化
即兴小索奇33 分钟前
王者荣耀服务器突然崩了
运维·服务器·王者荣耀
山外有山a1 小时前
neo4j知识图谱常用命令
服务器·数据库·oracle
重启就好1 小时前
【LVS】负载均衡群集部署(DR模式)
服务器·负载均衡·lvs
2101_824775821 小时前
Debian ubuntu源
服务器·ubuntu·debian
vortex51 小时前
如何为 Debian 和 Kali 系统更换软件源并更新系统
linux·运维·网络·网络安全·渗透测试·debian·kali
大明者省1 小时前
Xshell远程登录腾讯云高性能应用服务
linux·服务器·腾讯云
谦虚使人发胖2 小时前
Golang使用 ip2region 查询IP的地区信息
服务器·开发语言·golang