linux写管道返回 1,linux进程间通信(1)之管道

linux进程间通信(一)之管道

我们先来说说进程间通信(IPC)的一般目的,大概有数据传输、共享数据、通知事件、资源共享和进程控制等。但是我们知道,对于每一个进程来说这个进程看到属于它的一块内存资源,这块资源是它所独占的,所以进程之间的通信就会比较麻烦,原理就是需要让不同的进程间能够看到一份公共的资源。所以交换数据必须通过内核,在内核中开辟⼀块缓冲区,进程1把数据从⽤户空间 拷到内核缓冲区,进程2再从内核缓冲区把数据读⾛,内核提供的这种机制称为进程间通信。一般我们采用的进程间通信方式有

管道(pipe)和有名管道(FIFO)

信号(signal)

消息队列

共享内存

信号量

套接字(socket)

我们先来从最简单的通信方式来说起;

匿名管道 pipe

---------------------------------------------------------------------------------------------------------------------------------

管道的创建

管道是一种最基本的进程间通信机制。管道由pipe函数来创建:

151746486.png

调用pipe函数,会在内核中开辟出一块缓冲区用来进行进程间通信,这块缓冲区称为管道,它有一个读端和一个写端。

pipe函数接受一个参数,是包含两个整数的数组,如果调用成功,会通过pipefd[2]传出给用户程序两个文件描述符,需要注意pipefd[0]指向管道的读端, pipefd [1]指向管道的写端,那么此时这个管道对于用户程序就是一个文件,可以通过read(pipefd [0]);或者write(pipefd [1])进行操作。pipe函数调用成功返回0,否则返回-1..

那么再来看看通过管道进行通信的步骤:

》父进程创建管道,得到两个文件描述符指向管道的两端

151746487.png

》利用fork函数创建出子进程,则子进程也得到两个文件描述符指向同一管道

151746488.png

》父进程关闭读端(pipe[0]),子进程关闭写端pipe[1],则此时父进程可以往管道中进行写操作,子进程可以从管道中读,从而实现了通过管道的进程间通信。

151746489.png

示例代码:

#include

#include

#include

int main()

{

int _pipe[2];

int ret=pipe(_pipe);

if(ret<0)

{

perror("pipe\n");

}

pid_t id=fork();

if(id<0)

{

perror("fork\n");

}

else if(id==0) // child

{

close(_pipe[0]);

int i=0;

char *mesg=NULL;

while(i<100)

{

mesg="I am child";

write(_pipe[1],mesg,strlen(mesg)+1);

sleep(1);

++i;

}

}

else //father

{

close(_pipe[1]);

int j=0;

char _mesg[100];

while(j<100)

{

memset(_mesg,'\0',sizeof(_mesg ));

read(_pipe[0],_mesg,sizeof(_mesg));

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

j++;

}

}

return 0;

}

结果演示:

151746490.png

pipe的特点:

1. 只能单向通信

2. 只能血缘关系的进程进行通信

3. 依赖于文件系统

4、生命周期随进程

5. 面向字节流的服务

6. 管道内部提供了同步机制

说明:因为管道通信是单向的,在上面的例子中我们是通过子进程写父进程来读,如果想要同时父进程写而子进程来读,就需要再打开另外的管道;

管道的读写端通过打开的⽂件描述符来传递,因此要通信的两个进程必须从它们的公共祖先那⾥继承管道⽂件描述符。 上⾯的例⼦是⽗进程把⽂件描述符传给⼦进程之后⽗⼦进程之 间通信,也可以⽗进程fork两次,把⽂件描述符传给两个⼦进程,然后两个⼦进程之间通信, 总之 需要通过fork传递⽂件描述符使两个进程都能访问同⼀管道,它们才能通信。

四个特殊情况:

》 如果所有指向管道写端的⽂件描述符都关闭了,⽽仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到⽂件末尾⼀样

》 如果有指向管道写端的⽂件描述符没关闭,⽽持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

》 如果所有指向管道读端的⽂件描述符都关闭了,这时有进程指向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终⽌。

》 如果有指向管道读端的⽂件描述符没关闭,⽽持有管道写端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再write会阻塞,直到管道中有空位置了才写⼊数据并返回。

命名管道FIFO

---------------------------------------------------------------------------------------------------------------------------------

在管道中,只有具有血缘关系的进程才能进行通信,对于后来的命名管道,就解决了这个问题。FIFO不同于管道之处在于它提供⼀个路径名与之关联,以FIFO的⽂件形式存储于⽂件系统中。命名管道是⼀个设备⽂件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够通过FIFO相互通信。值得注意的是, FIFO(first input first output)总是按照先进先出的原则⼯作,第⼀个被写⼊的数据将⾸先从管道中读出。

命名管道的创建

创建命名管道的系统函数有两个: mknod和mkfifo。两个函数均定义在头⽂件sys/stat.h,

函数原型如下:#include

#include

int mknod(const char *path,mode_t mod,dev_t dev);

int mkfifo(const char *path,mode_t mode);

函数mknod参数中path为创建的命名管道的全路径名: mod为创建的命名管道的模指

明其存取权限; dev为设备值,该值取决于⽂件创建的种类,它只在创建设备⽂件时才会⽤到。这两个函数调⽤成功都返回0,失败都返回-1。下⾯使⽤mknod函数创建了⼀个命名管道:

umask(0);

if (mknod("/tmp/fifo",S_IFIFO | 0666) == -1)

{

perror("mkfifo error");

exit(1);

}

函数mkfifo前两个参数的含义和mknod相同。下⾯是使⽤mkfifo的⽰例代码:

umask(0);

if (mkfifo("/tmp/fifo",S_IFIFO|0666) == -1)

{

perror("mkfifo error!");

exit(1);

}

"S_IFIFO|0666"指明创建⼀个命名管道且存取权限为0666,即创建者、与创建者同组的

⽤户、其他⽤户对该命名管道的访问权限都是可读可写( 这⾥要注意umask对⽣成的

管道⽂件权限的影响) 。

命名管道创建后就可以使⽤了,命名管道和管道的使⽤⽅法基本是相同的。只是使⽤命

名管道时,必须先调⽤open()将其打开。因为命名管道是⼀个存在于硬盘上的⽂件,⽽管道

是存在于内存中的特殊⽂件。

需要注意的是,调⽤open()打开命名管道的进程可能会被阻塞。但如果同时⽤读写⽅式

( O_RDWR)打开,则⼀定不会导致阻塞;如果以只读⽅式( O_RDONLY)打开,则调

⽤open()

运行示例

-----------------------------------------------------------

那么此时我们早server.c中创建命名管道并打开,对管道中进行写操作,在client.c中进行读操作,把读到的内容进行打印,就实现了我们的使用命名管道通信。

Server.c:

#include

#include

#include

#include

#include

#include

#define _PATH_NAME_ "/tmp/file.tmp"

#define _SIZE_ 100

int main()

{

int ret=mkfifo(_PATH_NAME_,S_IFIFO|0666);

if(ret==-1){

printf("make fifo error\n");

return 1;

}

char buf[_SIZE_];

memset(buf,'\0',sizeof(buf));

int fd=open(_PATH_NAME_,O_WRONLY);

while(1)

{

//scanf("%s",buf);

fgets(buf,sizeof(buf)-1,stdin);

int ret=write(fd,buf,strlen(buf)+1);

if(ret<0){

printf("write error");

break;

}

}

close(fd);

return 0;

}

Client.c:

#include

#include

#include

#include

#include

#include

#define _PATH_NAME "/tmp/file.tmp"

#define _SIZE_ 100

int main()

{

int fd=open(_PATH_NAME,O_RDONLY);

if(fd<0){

printf("open file error");

return 1;

}

char buf[_SIZE_];

memset(buf,'\0',sizeof(buf));

while(1)

{

int ret=read(fd,buf,sizeof(buf));

if(ret<0){

printf("read end or error\n");

break;

}

printf("%s",buf);

}

close(fd);

return 0;

}

结果演示:

151746491.png

可以看到我实在服务端发送了“Hello” “I am aerver”,在客户端可以收到此内容,完成简单的进程间通信。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值