在linux程序中如何使用命名管道实现对文件的读写、求帮助

在linux程序中如何使用命名管道实现对文件的读写、求帮助,第1张

//创建server管道

mkfifo(Server_FIFO_Name,0777)

//打开服务器端口,等待读取。此时如果客户端还未写入数据服务器端会被阻塞。

server_fifo_fd = open(Server_FIFO_Name , O_RDONLY)

if( -1 == server_fifo_fd ){

fprintf( stderr , "Server fifo failure\n" )

exit(EXIT_FAILURE)

}

//从管道中读取数据。

read_res = read ( server_fifo_fd , &my_data , sizeof(my_data))

if(read_res >0){

//将字符串翻转.

reverse ( my_data.str )

//将客户端的pid号加入回送管道文件名中.

sprintf ( client_fifo, Client_FIFO_Name , my_data.client_pid)

//打开回送管道。

client_fifo_fd = open ( client_fifo , O_WRONLY )

if( -1 != client_fifo_fd ){

//向管道中写入返回的数据.

write ( client_fifo_fd , &my_data, sizeof(my_data))

close ( client_fifo_fd )

}

}

Linux 实现 IPC 其中的一种方式——管道

管道又分:

1、无名管道:无名管道只能用于有亲缘关系的进程

2、有名管道:有名管道用于任意两进程间通信。

你就可以把管道理解成位于进程内核空间的“文件”。

给文件加引号,是因为它和文件确实很像,因为它也有描述符。但是它确实又不是普通的本地文件,而是一种抽象的存在。

当进程使用 pipe 函数,就可以打开位于内核中的这个特殊“文件”。同时 pipe 函数会返回两个描述符,一个用于读,一个用于写。如果你使用 fstat函数来测试该描述符,可以发现此文件类型为 FIFO。

而无名管道的无名,指的就是这个虚幻的“文件”,它没有名字。本质上,pipe 函数会在进程内核空间申请一块内存(比如一个内存页,一般是 4KB),然后把这块内存当成一个先进先出(FIFO)的循环队列来存取数据,这一切都由 *** 作系统帮助我们实现了。

pipe 函数打开的文件描述符是通过参数(数组)传递出来的,而返回值表示打开成功(0)或失败(-1)。

它的参数是一个大小为 2 的数组。此数组的第 0 个元素用来接收以读的方式打开的描述符,而第 1 个元素用来接收以写的方式打开的描述符。也就是说,pipefd[0] 是用于读的,而 pipefd[1] 是用于写的。

打开了文件描述符后,就可以使用 read(pipefd[0]) 和 write(pipefd[1]) 来读写数据了。

注意事项

1、这两个分别用于读写的描述符必须同时打开才行,否则会出问题。

2、如果关闭读 (close(pipefd[0])) 端保留写端,继续向写端 (pipefd[1]) 端写数据(write 函数)的进程会收到 SIGPIPE 信号。

3、如果关闭写 (close(pipefd[1])) 端保留读端,继续向读端 (pipefd[0]) 端读数据(read 函数),read 函数会返回 0。

当在进程用 pipe 函数打开两个描述符后,我们可以 fork 出一个子进程。这样,子进程也会继承这两个描述符,而且这两个文件描述符的引用计数会变成 2。

如果你需要父进程向子进程发送数据,那么得把父进程的 pipefd[0] (读端)关闭,而在子进程中把 pipefd[1] 写端关闭,反之亦然。为什么要这样做?实际上是避免出错。传统上 pipe 管道只能用于半双工通信(即一端只能发,不能收;而另一端只能收不能发),为了安全起见,各个进程需要把不用的那一端关闭(本质上是引用计数减 1)。

步骤一:fork 子进程

步骤二:关闭父进程读端,关闭子进程写端

父进程 fork 出一个子进程,通过无名管道向子进程发送字符,子进程收到数据后将字符串中的小写字符转换成大写并输出。

有名管道打破了无名管道的限制,进化出了一个实实在在的 FIFO 类型的文件。这意味着即使没有亲缘关系的进程也可以互相通信了。所以,只要不同的进程打开 FIFO 文件,往此文件读写数据,就可以达到通信的目的。

1、文件属性前面标注的文件类型是 p

2、代表管道文件大小是 0

3、fifo 文件需要有读写两端,否则在打开 fifo 文件时会阻塞

通过命令 mkfifo 创建

通过函数 mkfifo创建

函数返回 0 表示成功,-1 失败。

例如:

cat 命令打印 test文件内容

接下来你的 cat 命令被阻塞住。

开启另一个终端,执行:

然后你会看到被阻塞的 cat 又继续执行完毕,在屏幕打印 “hello world”。如果你反过来执行上面两个命令,会发现先执行的那个总是被阻塞。

有两个程序,分别是发送端 send 和接收端面 recv。程序 send 从标准输入接收字符,并发送到程序 recv,同时 recv 将接收到的字符打印到屏幕。

发送端

接收端

编译

运行

因为 recv 端还没打开test文件,这时候 send 是阻塞状态的。

再开启另一个终端:

这时候 send 端和 recv 端都在终端显示has opend fifo

此时在 send 端输入数据,recv 打印。

Linux关于管道 原创

2018-09-14 12:22:41

Gaodes

码龄5年

关注

管道的概念

管道是Unix中最古老的进程间通信的形式。 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道” 我们通常把是把一个进程的输出连接或“管接”(经过管道来连接)到另一个进程的输入。

管道特点

管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

pipe函数

包含头文件<unistd.h>功能:创建一无名管道 原型

int pipe(int file_descriptor[2])

参数 file_descriptor:文件描述符数组,其中file_descriptor[0]表示读端,file_descriptor[1]表示写端 返回值:成功返回0,失败返回错误代码

示例代码:

#include<stdio.h>

#include<unistd.h>

#include<stdlib.h>

#include<signal.h>

#include<string.h>

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

{

int fd[2]

printf("f[0]=%d,f[1]=%d\n",fd[0],fd[1])

pipe(fd)

printf("f[0]=%d,f[1]=%d\n",fd[0],fd[1])

char buf[1024]={0}

int fid = fork()

if(fid >0)

{

read(fd[0],buf,1024)

printf("read data %s\n",buf)

}

else if(fid == 0)

{

write(fd[1],"helloworld",strlen("helloworld"))

}

else

{

perror("fork error")

}

return 0

}

打印结果

管道读写规则:如果试图从管道写端读取数据,或者向管道读端写入数据都将导致错误发生 当没有数据可读时,read调用就会阻塞,即进程暂停执行,一直等到有数据来到为止。 如果管道的另一端已经被关闭,也就是没有进程打开这个管道并向它写数据时,read调用就会阻塞

复制文件描述符dup

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<signal.h>

int main()

{

int fd = dup(1)

printf("file fd= %d\n",fd)

write(fd,"helloworld",strlen("helloworld"))

return 0

}

打印结果:

1为输入到终端

shell管道的实现

原理通过把发的fd[1]写复制到shell的1(标准输入),fd[0]复制到shell的2(标准输出)

以下是代码:

#include<stdio.h>

#include<stdlib.h>

#include<fcntl.h>

#include<unistd.h>

#include<string.h>

#include<signal.h>

int main()

{

int fd[2]

char buf[1024] ={0}

pipe(fd)

int pid = fork()

if(pid >0)

{

read(fd[0],buf,1024)

printf(buf)

}

else if(pid == 0)

{

dup2(fd[1],1)

close(fd[0])

close(fd[1])

execlp("ls","ls","-al",NULL)

}

else

{

}

return 0

}

实现结果:

popen函数

作用:允许一个程序把另外一个程序当作一个新的进程来启 动,并能对它发送数据或接收数据

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

command:待运行程序的名字和相应的参数 open_mode:必须是“r”或“w” 如果 *** 作失败,popen会返回一个空指针

以下代码:

#include<stdio.h>

#include<stdlib.h>

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<string.h>

int main()

{

FILE *file = popen("ls -al","r")

char buf[1024] = {0}

fread(buf,1,1024,file)

fclose(file)

FILE *wcfile = popen("wc","w")

fwrite(buf,1,strlen(buf),wcfile)

fclose(wcfile)

return 0

}

代码结果:

命名管道破裂测试

我们首先要知道命名管道,要读段和写段同时开启,才能向文件读写数据。

贴上代码来理解命名管道的规则

首先是读端:

#include<stdio.h>

#include<unistd.h>

#include<stdlib.h>

#include<signal.h>

#include<string.h>

#include<fcntl.h>

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

{

printf("open before\n")

int fd = open("/home/gao/tmp/fifo",O_RDONLY)

printf("open after\n")

//休眠5秒,读端退出

sleep(5)

return 0

}

接下来是写端:

#include<stdio.h>

#include<unistd.h>

#include<stdlib.h>

#include<signal.h>

#include<string.h>

#include<fcntl.h>

void handle(int signo)

{

printf("cat signale = %d\n",signo)

}

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

{

signal(SIGPIPE,handle)

printf("open before\n")

int fd = open("/home/gao/tmp/fifo",O_WRONLY)

printf("open after\n")

//命名管道规则,如果写入读断被中断,写入会返回-1,并且管道会破裂,产生信号(SIGPIPE)

while(1)

{

int wrsize = write(fd,"helloworld",strlen("helloworld"))

printf("size data:%d\n",wrsize)

sleep(1)

}

}

执行写端:

它在等待另一端的开启,才能向里面写入数据

此时我们开启读端:

马上可以看到写段可以写数据

而执行5秒后,我们可以看到写的时候返回-1,并且获取到管道破裂的信息(SIGPIPE)

所以这里就是我们所注意的点,当我们写客户端和服务器进行管道传输的时候,如果客户端一旦退出来,就会使管道破裂,所以我们必须通过捕捉信号,来避免这种事情发生。


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/yw/6239473.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-03-18
下一篇 2023-03-18

发表评论

登录后才能评论

评论列表(0条)

保存