进程间通信之匿名管道和命名管道的理解和实现【Linux】

进程间通信之匿名管道和命名管道的理解和实现【Linux】

进程间通信之匿名管道和命名管道的理解和实现

进程间通信什么是管道匿名管道代码实现管道的读写规则管道特点

命名管道创建命名管道代码实现

进程间通信

进程间通信的目的

数据传输:一个进程需要将它的数据发送给另一个进程资源共享:多个进程之间共享同样的资源。通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程间通信的本质理解

进程间通信的前提,首先需要让不同的进程都能够看到同一块“内存”。这个“内存”不能隶属于任何进程应该强调共享。

进程间通信的必要性 单进程无法使用并发能力,更加无法实现多进程协同。

什么是管道

管道是进程通信的其中一种,管道分为两种:

管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。管道只能单向通信。

父进程fork之后,父进程会copy一份files_struct给子进程,但是父子进程的文件描述符依旧是指向同一份文件,而这个文件就叫做管道。 管道某种意义上来说不是文件,是一种方式,进行交流的数据作为临时数据放在内存,实际上是由内核维护的一个缓冲区,在内核中被称为管道缓存,当交流结束后,临时数据就会被释放。所以也可以把管道看作是一个缓冲区。

匿名管道

#include 功能:创建一无名管道 原型 int pipe(int fd[2]); 参数 fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端 返回值:成功返回0,失败返回错误代码

管道的实现

分别以读写的方式打开同一个文件。

fork创建子进程。

双方进程各自关闭自己不需要的文件描述符(为了实现单向通信,但是如果想实现全双工通信,可以创建多个管道来实现)。

如何做到让不同的进程看到同一份资源? :fork后让子进程继承—>能够让具有血缘关系的进程进行进程间通信—>常用于父子进程

代码实现

下面这是一个简易的匿名管道的实现,当使用pipe函数时,管道文件是通过 pipe 系统调用创建的。具体来说,pipe 系统调用会在内核中创建一个管道,并返回两个文件描述符,这两个文件描述符分别用于读取和写入管道。

#include

#include

#include

#include

int main()

{

int fd[2];

int retpp = pipe(fd);

if (retpp == -1)

{

perror("pipe");

exit(1);

}

int fk = fork();

if (fk < 0)

{

perror("fork");

exit(2);

}

if (fk == 0)

{

// child: 作读端,首先得关闭写端

close(fd[1]);

char buf[1024];

read(fd[0], buf, sizeof(buf));

printf("child: %s\n", buf);

}

if (fk > 0)

{

// parent:作写端,首先得关闭读端

close(fd[0]);

char *bufp = "qwweereewrrere";

write(fd[1], bufp, strlen(bufp));

return 0;

}

}

可不可以改变管道文件呢? 下面的代码将文件t.txt作为管道文件。

#include

#include

#include

#include

#include

#include

#include

int main()

{

int fd[2];

int retpp = pipe(fd);

if (retpp == -1)

{

perror("pipe");

exit(1);

}

int fk = fork();

if (fk < 0)

{

perror("fork");

exit(2);

}

int fd1 = open("F:\\t.txt", O_RDWR | O_CREAT, 0666);

if (fd1 < 0)

{

perror("open");

exit(3);

}

if (fk == 0)

{

// child: 作读端,首先得关闭写端

close(fd[1]);

char buf[1024];

dup2(fd[0], fd1);

read(fd[0], buf, sizeof(buf));

printf("%s\n", buf);

}

if (fk > 0)

{

// parent:作写端,首先得关闭读端

close(fd[0]);

const char *bufp = "qwweereewrrere";

dup2(fd[1], fd1);

write(fd[1], bufp, strlen(bufp));

return 0;

}

}

用fork来共享管道原理 站在文件描述符角度-深度理解管道

站在内核角度-管道本质

管道的读写规则

当没有数据可读时

O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。 当管道满的时候

O_NONBLOCK disable: write调用阻塞,直到有进程读走数据O_NONBLOCK enable:调用返回-1,errno值为EAGAIN 如果所有管道写端对应的文件描述符被关闭,则read返回0如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程 退出当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

管道提供了访问控制

写端快,读端慢, 写端满了就不能再写了。写端慢,读端快, 管道没有数据时,读端必须等待。写端关闭,读端没有数据可读,标识读到了文件结尾。读端关闭,写端继续写,OS终止写端进程。

管道特点

只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创 建,然后该进程调用fork,此后父、子进程之间就可应用该管道。管道提供流式服务一般而言,进程退出,管道释放,所以管道的生命周期随进程一般而言,内核会对管道操作进行同步与互斥管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

命名管道

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。命名管道是一种特殊类型的文件

创建命名管道

命名管道可以从命令行上创建,命令行方法是使用下面这个命令: mkfifo filename 命名管道也可以从程序里创建,相关函数有: int mkfifo(const char *filename,mode_t mode);

比如我们可以这么创建命名管道:

int main(int argc, char *argv[])

{

mkfifo("p2", 0644);

return 0;

}

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

匿名管道由pipe函数创建并打开。命名管道由mkfifo函数创建,打开用openFIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

命名管道的打开规则

如果当前打开操作是为读而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFOO_NONBLOCK enable:立刻返回成功 如果当前打开操作是为写而打开FIFO时

O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFOO_NONBLOCK enable:立刻返回失败,错误码为ENXIO

代码实现

下面的代码实现了客户端向命名管道中写入信息,服务端从命名管道中将数据读取出来并打印在终端屏幕上。

// server.cpp

#include

#include

#include

#include

#include

#include

#include

#include

int main()

{

srand((unsigned int)time(NULL));

if (mkfifo("shard", 0666) < 0)

{

perror("mkfifo");

exit(1);

}

int sfd = open("shard", O_WRONLY, 0666);

if (sfd < 0)

{

perror("server_open");

exit(2);

}

while (1)

{

int a = rand() % 1000;

char server_put[1024] = {'\0'};

sprintf(server_put, "%d", a);

printf("server: %s\n", server_put);

write(sfd, server_put, sizeof(server_put));

sleep(2);

}

return 0;

}

// client

#include

#include

#include

#include

#include

#include

#include

#include

int main()

{

srand((unsigned int)time(NULL));

int cfd = open("shard", O_RDONLY, 0666);

if (cfd < 0)

{

perror("client_open");

exit(2);

}

while (1)

{

char client_in[1024];

read(cfd, client_in, sizeof(client_in));

printf("client: %s\n", client_in);

sleep(1);

}

return 0;

}

// makefile

.PHANY:all

all: server client

server:server.cpp

g++ -o $@ $^

client:client.cpp

g++ -o $@ $^

.PHANY:clean

clean:

rm -f client server shard

😄 创作不易,你的点赞和关注都是对我莫大的鼓励,再次感谢您的观看😄

相关推荐

什么是hard模式?Hard模式是什么意思?
365信誉线上

什么是hard模式?Hard模式是什么意思?

📅 07-08 👁️ 5055
辐射4商人钱多久刷
365bet亚洲真人网

辐射4商人钱多久刷

📅 08-01 👁️ 9309
艺恩咨询
365bet世界

艺恩咨询

📅 10-02 👁️ 4670
变更到站是什么意思?变更到站和改签有什么区别
365bet亚洲真人网

变更到站是什么意思?变更到站和改签有什么区别

📅 08-08 👁️ 5161
如何设置CAD线宽: 优化您的绘图效果-BIM免费教程
365信誉线上

如何设置CAD线宽: 优化您的绘图效果-BIM免费教程

📅 07-30 👁️ 7574
我的世界:地球
365bet亚洲真人网

我的世界:地球

📅 07-14 👁️ 6295