Linux程序设计学习笔记----进程间通信——管道

转载请注明出处: http://blog.csdn.net/suool/article/details/38444149, 谢谢!

进程通信概述

在Linux系统中,进程是一个独立的资源管理单元,但是独立而不孤立,他们需要之间的通信,因此便需要一个进程间数据传递、异步、同步的机制,这个机制显然需要由OS来完成管理和维护。如下:

1、同一主机进程间数据交互机制:无名管道(PIPE),有名管道(FIFO),消息队列(Message Queue)和共享内存(Share Memory)。无名管道多用于亲缘关系的进程间通信,但是管道位单向,多进程使用一个同一个管道导致交叉读写。消息队列可以实现同主机上任意多的进程间通信,但是消息队列可存放的数据量有限,用与少量的数据传递。共享内存可以实现同主机任意进程间的大量数据传递,但因为共享数据空间访问时存在竞争问题。

2、同主机进程间同步机制:信号量(semaphore)

3、同主机进程间异步机制:信号(signal)

4、网络主机间数据交互机制:套接口(Socket)

本节主要记录UNIX进程间通信机制:PIPE,FIFO。

What‘s 管道?

管道是Linux支持的最初Unix IPC形式之一,具有以下特点:

  • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
  • 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);
  • 单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中。
  • 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。

管道的实现机制:

管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

进程间通信——PIPE

使用管道的示例:rpm -qa | grep telnet

使用管道”|“可以将两个命令连接起来,从而改变标准的输入输出方式。在下面的shell命令中,命令”rpm-qa“ 的输出作为telnet的输入。连接输入输出的中间设备即为一个管道文件,因此,使用管道可以将一个命令的输出作为一命令的输入这种管道是临时的,命令执行完成后,将自动消失,这类管道成为无名管道PIPE。

PIPE 和普通文件有着很大的区别,首先在进程通信的两端退出后,管道将自动消失并释放内核资源,不能像普通文件那样存储大量的信息。

PIPE的基本操作

创建PIPE

包含头文件<unistd.h>

功能:创建一无名管道

原型:

int pipe(int fd[2]);

参数:

fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端

返回值:成功返回0,失败返回错误代码

man帮助说明:

DESCRIPTION

       pipe() creates a pipe, a unidirectional data channel that can be used
       for interprocess communication.  The array pipefd is used to return
       two file descriptors referring to the ends of the pipe.  pipefd[0]
       refers to the read end of the pipe.  pipefd[1] refers to the write
       end of the pipe.  Data written to the write end of the pipe is
       buffered by the kernel until it is read from the read end of the
       pipe.  For further details, see pipe(7).

读写PIPE

任何进程读写PIPE都要确定还有一个进程(这个进程可以是自己),该进程以写读的方式(读对应写,写对应读)访问管道(即可以操作相应的文件描述符)。读写管道使用的系统调用是write和read,两者都默认以阻塞的方式读写管道,如果要修改这两个函数的行为可以通过fcntl函数实现。

用 pipe() 创建的管道两端处于同一个进程中,由于管道主要是用于在不同的进程间通信的,因此,在实际应用中没有太大意义。实际上,通常先是创建一个管道,再调用fork()函数创建一个子进程,该子进程会继承父进程所创建的管道,这时,父子进程管道的文件描述符对应关系如下图

此时的关系看似非常复杂,实际上却已经给不同进程之间的读写创造了很好的条件。父子进程分别拥有自己的读写通道,为了实现父子进程之间的读写,只需把无关的读端或写端的文件描述符关闭即可。例如,图4中,将父进程的写端fd[1]和子进程的读端fd[0]关闭,则父子进程之间就建立起一条“子进程写入父进程读取”的通道。   同样,也可以将父进程的读端fd[0]和子进程的写端fd[1]关闭,则父子进程之间就建立起一条“父进程写入子进程读取”的通道

另外,父进程还可以创建多个子进程,各个子进程都继承了相应的fd[0]和fd[1],此时,只需要关闭相应的端口就可以建立各子进程之间的的通道。

管道读写注意点

●  只有在管道的读端存在时,向管道写入数据才有意义。否则,向管道写入数据的进程将收到内核传来的 SIGPIPE 信号(通常为 Broken pipe错误)。

●  向管道写入数据时,Linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写进程将会一直阻塞。

●  父子进程在运行时,它们的先后次序并不能保证。因此,为了保证父子进程已经关闭了相应的文件描述符,可在两个进程中调用 sleep()函数。当然,这种调用不是很好的解决方法,以后我会用进程之间的同步与互斥机制来修改它的!

利用管道进行父子进程间数据传输
示例一:子进程向管道中写数据,父进程从管道中读出数据
/*************************************************************************
	> File Name: fathson.c
	> Author:SuooL
	> Mail:[email protected]
	> Created Time: 2014年08月08日 星期五 20时46分17秒
 ************************************************************************/

#include<stdio.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> 

int main(void)
{
    int fds[2];
    if(pipe(fds) == -1){
        perror("pipe error");
        exit(EXIT_FAILURE);
    }
    pid_t pid;
    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(EXIT_FAILURE);
    }
    if(pid == 0){
        close(fds[0]);//子进程关闭读端
        write(fds[1],"hello",5);
        exit(EXIT_SUCCESS);
    }

    close(fds[1]);//父进程关闭写端
    char buf[10] = {0};
    read(fds[0],buf,10);
    printf("receive datas = %s\n",buf);
    return 0;
}

结果:

示例二:利用管道实现ls |wc –w功能

/*************************************************************************
	> File Name: ls-wc.c
	> Author:SuooL
	> Mail:[email protected]
	> Created Time: 2014年08月08日 星期五 20时53分04秒
	> Description: 使用管道实现ls| wc -w命令
 ************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h> 

int main(void)
{
    int fds[2];
    if(pipe(fds) == -1){
        perror("pipe error");
        exit(EXIT_FAILURE);
    }
    pid_t pid;
    pid = fork();
    if(pid == -1){
        perror("fork error");
        exit(EXIT_FAILURE);
    }
    if(pid == 0){

        dup2(fds[1],STDOUT_FILENO);//复制文件描述符且指定新复制的fd为标准输出
        close(fds[0]);//子进程关闭读端
        close(fds[1]);
        execlp("ls","ls",NULL);
        fprintf(stderr,"exec error\n");
        exit(EXIT_FAILURE);
    }

    dup2(fds[0],STDIN_FILENO);
    close(fds[1]);//父进程关闭写端
    close(fds[0]);
    execlp("wc","wc","-w",NULL);
    fprintf(stderr, "error execute wc\n");
    exit(EXIT_FAILURE);
}

文件描述符重定向

1、shell重定向操作

2、重定向编程

一般的输入输出函数都会默认向指定的文教描述符的文件读写。因此,重定向操作实际上是关闭某个标准I/O 设备(文件描述符0,1,2),而将另外一个打开的普通文件描述符设置为0,1,2.

输入重定向:关闭标准输入设备,打开或复制普通文件,将其文件描述符设置为0,

输出重定向,错误输出重定向同理。

使用dup和dup2函数可以实现文件描述符的复制操作。

函数具体的说明参见网络说明文档。

下面是一个使用dup2函数将输出重定向到某文件的示例:

/*************************************************************************
	> File Name: dup_exp.c
	> Author:SuooL
	> Mail:[email protected]
	> Created Time: 2014年08月08日 星期五 21时15分31秒
	> Description: 使用dup2实现输出重定向
 ************************************************************************/

#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>
#define BUFFER_SIZE 1024

int main(int argc, char *argv[])
{
	int fd;
	char buffer[BUFFER_SIZE];

	if(argc != 2)
	{
		fprintf(stderr,"Usage:%s utfilename/n/a\n",argv[0]);
		exit(EXIT_FAILURE);
	}
	// 打开重定向文件
	if((fd=open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR)) == -1)
	{
		fprintf(stderr,"Open %s Error:%s/n/a\n",argv[1],strerror(errno));
		exit(EXIT_FAILURE);
	}
	if(dup2(fd,fileno(stdout)) == -1) // 重定向设备
	{
		fprintf(stderr,"Redirect standard out error:%s/n/a\n",strerror(errno));
		exit(EXIT_FAILURE);
	}
	fprintf(stderr,"Now,please input string\n");
	fprintf(stderr,"(to quit use CTRL+D)/n\n");
	while(1)
	{
		fgets(buffer,BUFFER_SIZE,stdin);   // 从标准输入读取
		if(feof(stdin))
			break;
		// 写数据,重定向到文件
		write(fileno(stdout),buffer,strlen(buffer));
	}
	exit(EXIT_SUCCESS);
	return 0;
}

实现who|sort命令

即是使用无名管道将执行who命令的进程与执行sort命令的进程联系一起,将登陆的用户的信息按排序输出。

需要:

系统创建一个PIPE,在执行who命令的进程将输出重定向到PIPE的写端,而在执行sort命令的进程将输入重定向到管道的读端,即是用管道将sort的输入连接到who的输出。

步骤:

1、主进程创建一个管道,显然主进程可以访问管道的两端。

2、主进程创建两个子进程分别执行who和sort命令。两个子进程都继承了父进程打开的文件描述符。

3、关闭每个进程与管道无关联系。

4、在两个子进程中调用execX 函数,执行who和sort命令。代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
	int fds[2];
	if(pipe(fds)==-1)
	{
		perror("pipe");
		exit(EXIT_FAILURE);
	}
	if (fork()== 0)
	{
		char buf[128];
		dup2(fds[0], 0);
		close(fds[1]);				//must include ,or block
		execlp("sort", "sort", (char *)0);
		//execlp("cat", "cat", (char *)0);
	}
	else
	{
		if(fork() == 0)
		{
			dup2(fds[1], 1);
			close(fds[0]);
			execlp("who", "who", (char *)0);
		}
		else
		{
			close(fds[0]);
			close(fds[1]);
			 wait(NULL);
			 wait(NULL);
		}
	}
	return 0;
}

进程间通信——FIFO

有名管道相关的关键概念

管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,在有名管道(named pipe或FIFO)提出后,该限制得到了克服。

FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。

值得注意的是,FIFO严格遵循先进先出(first in first out),对管道及FIFO的读总是从开始处返回数据,对它们的写则把数据添加到末尾。

它们不支持诸如lseek()等文件定位操作。

有名管道的基本操作

 有名管道的创建

使用mkfifo()函数来创建

包含头文件:

#include <sys/types.h>

#include <sys/stat.h>

函数原型:

int mkfifo(const char * pathname, mode_t mode)

参数:

pathname是一个普通的路径名,也就是创建后FIFO的名字;

mode指出FIFO文件的操作权限,与打开普通文件的open()函数中的mode 参数相同。

常用的mode:

O_CREAT:如果文件不存在,则创建之;

O_EXCL:确保此次调用会创建文件;

如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用于FIFO,如close、read、write等等。

返回值:

创建成功,则返回0;

失败则返回-1;

错误类型:

EEXIST 路径名已存在(最常用的判断)

ENAMETOOLONG/ ENOENT/ENOSPC/ ENOTDIR/ EROFS

有名管道的打开规则

有名管道比管道多了一个打开操作:open(),以供读取或者写入

包含头文件:

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

函数原型:

int open(const char *pathname, int flags);

int open(const char *pathname, int flags, mode_t mode);

参数:

1> pathname:指出文件名

2> flags:至少包含一下一种访问权限:O_RDONLY ( read only ),  O_WRONLY( write only ),  or  O_RDWR( read/write ).

falgs需要与一些文件状态标识进行“ 或 ”操作

FIFO常用的文件状态标识:O_NONBLOCK or O_NDELAY

即表示非阻塞(如果不设置,则默认为阻塞),含义看下文解释。

3> mode:一般取0即可;

返回值:

如果打开成功,则返回一个新的文件描述符;

如果打开失败,则返回-1,并设置响应的errno

FIFO常用到的错误:

ENXIO:当设置flags为O_NONBLOCK  |  O_WRONLY时,文件为FIFO,并且进程没有文件打开以供写;

FIFO的打开规则:

如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。

如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。

对FIFO打开规则的验证(主要验证写打开对读打开的依赖性)

[cpp] view plaincopy

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <errno.h>
  4. #include <fcntl.h>
  5. #define FIFO_SERVER "/tmp/fifoserver"
  6. int handle_client(char*);
  7. int main()
  8. {
  9. int r_rd;
  10. int w_fd;
  11. pid_t pid;
  12. //创建FIFO,并检查是否已有同名的FIFO文件
  13. if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))
  14. {
  15. printf("cannot create fifoserver\n");
  16. }
  17. //创建成功
  18. handle_client(FIFO_SERVER);
  19. }
  20. int handle_client(char* arg)
  21. {
  22. int ret;
  23. ret=w_open(arg);
  24. switch(ret)
  25. {
  26. case 0:
  27. {
  28. printf("open %s error\n",arg);
  29. printf("no process has the fifo open for reading\n");
  30. return -1;
  31. }
  32. case -1:
  33. {
  34. printf("something wrong with open the fifo except for ENXIO");
  35. return -1;
  36. }
  37. case 1:
  38. {
  39. printf("open server ok\n");
  40. return 1;
  41. }
  42. default:
  43. {
  44. printf("w_no_r return ----\n");
  45. return 0;
  46. }
  47. }
  48. unlink(FIFO_SERVER);
  49. }
  50. //返回0,表示打开失败,因为没有文件
  51. //返回-1,表示打开失败,但是因为别的原因
  52. //返回1,表示打开成功
  53. int w_open(char*arg)
  54. {
  55. if(open(arg,O_WRONLY|O_NONBLOCK,0)==-1)
  56. {
  57. if(errno==ENXIO)
  58. {
  59. return 0;
  60. }
  61. else
  62. {
  63. return -1;
  64. }
  65. }
  66. return 1;
  67. }

有名管道的读写规则

从FIFO中读取数据:

约定:

如果一个进程为了从FIFO中读取数据而阻塞打开FIFO(open()时不设置O_NONBLOCK ),那么称该进程内的读操作为设置了阻塞标志的读操作。

如果有进程写打开FIFO,且当前FIFO内没有数据

1> 则对于设置了阻塞标志的读操作来说,将一直阻塞

2> 对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。

对于设置了阻塞标志的读操作说,造成阻塞的原因有两种:

1> 当前FIFO内有数据,但有其它进程在读这些数据;

2> FIFO内没有数据。

解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。

读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样(此时,读操作返回0)。

如果没有进程写打开FIFO,则设置了阻塞标志的读操作会阻塞。

注:如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。

使用read()方法从FIFO中读取数据

包含头文件:

#include <unistd.h>

函数原型:

ssize_t read(int fd, void *buf, size_t count);

功能:

从文件描述符fd中读取count字节到buf中;

参数:

如果count==0,则函数返回0;如果count大于buf的size,结果不确定??

返回值:

如果读取成功,则返回读取的字节数;

(当申请的字节数多余fd中可读的字节数时,返回的值小于count)

如果读取失败,则返回-1;

FIFO中可能出现的错误:

EAGAIN or EWOULDBLOCK:The  file  descriptor  fd refers to a socket and has been marked nonblocking   (O_NONBLOCK),   and   the   read   would    block.

向FIFO中写入数据:

约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO(open()时不设置O_NONBLOCK ),那么称该进程内的写操作为设置了阻塞标志的写操作。

对于设置了阻塞标志的写操作:

1> 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。

2> 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。

对于没有设置阻塞标志的写操作:

1> 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。(不保证所有数据都能写入)

2> 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;

如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;

要注意几种不同情况下的区别!

使用write()方法向FIFO写入数据;

包含头文件:

#include <unistd.h>

函数原型:

ssize_t write(int fd, const void *buf, size_t count);

返回值:

如果成功,则返回写入的字节数;

如果失败,返回-1,并设置相应的errno;

可能出现的错误:

EAGAIN or EWOULDBLOCK:The  file  descriptor  fd refers to a socket and has been marked nonblocking   (O_NONBLOCK),   and   the   write   would   block.

对FIFO读写规则的验证:

下面提供了两个对FIFO的读写程序,适当调节程序中的很少地方或者程序的命令行参数就可以对各种FIFO读写规则进行验证。

程序1:写FIFO的程序

[cpp] view plaincopy

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <errno.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include "BYHFileOperation.h"
  9. #define FIFO_SERVER "/home/stuy1001593/fifoserver"
  10. void printMkfifoErrorInfo(int err);
  11. int main()
  12. {
  13. char w_buf[4*1024*2]={0};
  14. int real_wnum;
  15. //创建FIFO,并检查路径名是否存在
  16. int nRet=mkfifo(FIFO_SERVER,O_CREAT|O_EXCL);
  17. if(nRet<0)
  18. {
  19. int tempErrno=errno;
  20. printMkfifoErrorInfo(tempErrno);
  21. if(errno==EEXIST)
  22. {
  23. printf("文件%s已存在\n", FIFO_SERVER);
  24. }
  25. else
  26. {
  27. exit(-1);
  28. }
  29. }
  30. //设置非阻塞标志的只写操作
  31. //int open_flag=O_WRONLY|O_NONBLOCK;
  32. int open_flag=O_WRONLY;
  33. int fd=BYHFileOperation::Open(FIFO_SERVER, open_flag);
  34. if(fd==-1)
  35. {
  36. if(errno==ENXIO)
  37. {
  38. printf("进程没有文件打开,以供写入\n");
  39. }
  40. exit(-1);
  41. }
  42. //写入小于PIPE_BUFF的数据
  43. real_wnum=write(fd, w_buf, 2048);
  44. if(real_wnum==-1)
  45. {
  46. if(errno==EAGAIN)
  47. {
  48. printf("写入到FIFO中时发生错误,FIFO空闲缓存区没有足够的空间,稍后再试\n");
  49. }
  50. exit(-1);
  51. }
  52. else
  53. {
  54. printf("向FIFO空闲缓存中写入了%dByte\n",real_wnum);
  55. }
  56. //写入大于PIPE_BUFF的数据
  57. real_wnum=write(fd, w_buf, 5000);
  58. //5000用于测试写入字节大于4096时的非原子性
  59. //real_wnum=write(fd,w_buf,4096);
  60. //4096用于测试写入字节不大于4096时的原子性
  61. if(real_wnum==-1)
  62. {
  63. if(errno==EAGAIN)
  64. {
  65. printf("写入到FIFO中时发生错误,FIFO空闲缓存区没有足够的空间,稍后再试\n");
  66. }
  67. exit(-1);
  68. }
  69. else
  70. {
  71. printf("向FIFO空闲缓存中写入了%dByte\n",real_wnum);
  72. }
  73. }

程序2:与程序1一起测试写FIFO的规则,第一个命令行参数是请求从FIFO读出的字节数

[cpp] view plaincopy

  1. #include <sys/types.h>
  2. #include <sys/stat.h>
  3. #include <errno.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #define FIFO_SERVER "/tmp/fifoserver"
  9. int main()
  10. {
  11. char r_buf[4096*2]={0};
  12. //FIFO文件描述符
  13. int fd;
  14. int r_size=1024;
  15. int ret_size;
  16. //以非阻塞方式打开FIFO Server(FIFO发送者)
  17. fd=open(FIFO_SERVER,O_RDONLY|O_NONBLOCK,0);
  18. //以阻塞方式打开FIFO
  19. //fd=open(FIFO_SERVER,O_RDONLY,0);
  20. if(fd==-1)
  21. {
  22. printf("打开FIFO文件%s失败\n", FIFO_SERVER);
  23. exit(0);
  24. }
  25. while(true)
  26. {
  27. //从FIFO中读取到r_buf中
  28. ret_size=read(fd, r_buf, r_size);
  29. //读取失败
  30. if(ret_size==-1)
  31. {
  32. if(errno==EAGAIN)
  33. {
  34. printf("FIFO缓存中没有数据可读,请稍后再试\n");
  35. sleep(1);
  36. }
  37. }
  38. printf("从FIFO缓存中读取%dBytes\n",ret_size);
  39. sleep(1);
  40. }
  41. pause();
  42. //删除FIFO文件
  43. unlink(FIFO_SERVER);
  44. }

程序应用说明:

把读程序编译成两个不同版本:

1>阻塞读版本

2>非阻塞读版本nbr

把写程序编译成两个四个版本:

1> 非阻塞且请求写的字节数大于PIPE_BUF版本:nbwg

2> 非阻塞且请求写的字节数不大于PIPE_BUF版本:版本nbw

3> 阻塞且请求写的字节数大于PIPE_BUF版本:bwg

4> 阻塞且请求写的字节数不大于PIPE_BUF版本:版本bw

小结

FIFO可以说是管道的推广,克服了管道无名字的限制,使得无亲缘关系的进程同样可以采用先进先出的通信机制进行通信。

管道和FIFO的数据是字节流,应用程序之间必须事先确定特定的传输"协议",采用传播具有特定意义的消息。

要灵活应用管道及FIFO,理解它们的读写规则是关键。

管道基本特点总结

1、管道是特殊类型的文件,在满足FIFO的原则条件下可能进行读写,但不能定位读写位置。

2、管道单向,要实现双向则需要俩个管道。无名管道一般用于亲缘关系的进程间通信,而有名管道则以磁盘文件的形式存在,可以实现本机任意两个进程间的通信

3、阻塞问题。无名管道必须显示打开,创建的时候直接返回文件描述符,而在读写的时候需要确定对方的存在,即阻塞于读写位置,而有名管道则在打开的时候确定对方是否存在,否则阻塞。(关于此会具体讲述)

NEXT:

进程异步信号处理机制

System V 进程间通信

转载请注明出处: http://blog.csdn.net/suool/article/details/38444149, 谢谢!

Linux程序设计学习笔记----进程间通信——管道,布布扣,bubuko.com

时间: 08-07

Linux程序设计学习笔记----进程间通信——管道的相关文章

Linux程序设计学习笔记----System V进程间通信(信号量)

关于System V Unix System V,是Unix操作系统众多版本中的一支.它最初由AT&T开发,在1983年第一次发布,因此也被称为AT&T System V.一共发行了4个System V的主要版本:版本1.2.3和4.System V Release 4,或者称为SVR4,是最成功的版本,成为一些UNIX共同特性的源头,例如"SysV 初始化脚本"(/etc/init.d),用来控制系统启动和关闭,System V Interface Definitio

Linux 程序设计学习笔记----进程管理与程序开发(下)

转载请注明出处:http://blog.csdn.net/suool/article/details/38419983,谢谢! 进程管理及其控制 创建进程 fork()函数 函数说明具体参见:http://pubs.opengroup.org/onlinepubs/009695399/functions/fork.html 返回值:Upon successful completion, fork() shall return 0 to the child process and shall re

Linux 程序设计学习笔记----终端及串口编程基础之概念详解

转载请注明出处,谢谢! linux下的终端及串口的相关概念有: tty,控制台,虚拟终端,串口,console(控制台终端)详解 部分内容整理于网络. 终端/控制台 终端和控制台都不是个人电脑的概念,而是多人共用的小型中型大型计算机上的概念. 1.终端 一台主机,连很多终端,终端为主机提供了人机接口,每个人都通过终端使用主机的资源. 终端有字符哑终端和图形终端两种. 控制台是另一种人机接口, 不通过终端与主机相连, 而是通过显示卡-显示器和键盘接口分别与主机相连, 这是人控制主机的第一人机接口.

Linux程序设计学习笔记----网络通信编程API及其示例应用

转载请注明出处, http://blog.csdn.net/suool/article/details/38702855. BSD Socket 网络通信编程 BSD TCP 通信编程流程 图为面向连接的Socket通信的双方执行函数流程.使用TCP协议的通信双方实现数据通信的基本流程如下 建立连接的步骤 1.首先服务器端需要以下工作: (1)调用socket()函数,建立Socket对象,指定通信协议. (2)调用bind()函数,将创建的Socket对象与当前主机的某一个IP地址和TCP端口

Linux 程序设计学习笔记----POSIX 文件及目录管理

转载请注明:http://blog.csdn.net/suool/article/details/38141047 问题引入 文件流和文件描述符的区别 上节讲到ANSI C 库函数的实现在用户态,流的相应资源也在用户空间,但无论如何实现最终都需要通过内核实现对文件的读写控制.因此fopen函数必然调用了对OS的系统调用.这一调用在LINUX下即为open, close, read, write等函数.这些都遵循POSIX标准. so,在linux系统中是如何通过POSIX标准实现对文件的操作和目

Linux 程序设计学习笔记----命令行参数处理

转载请注明出处.http://blog.csdn.net/suool/article/details/38089001 问题引入----命令行参数及解析 在使用linux时,与windows最大的不同应该就是经常使用命令行来解决大多数问题.比如下面这样的: 而显然我们知道C语言程序的入口是mian函数,即是从main函数开始执行,而main函数的原型是: int main( int argc, char *argv[] ); int main( int argc, char **argv );

Linux程序设计学习笔记——异步信号处理机制

转载请注明出处: http://blog.csdn.net/suool/article/details/38453333 Linux常见信号与处理 基本概念 Linux的信号是一种进程间异步的通信机制,在实现上一种软中断.信号能够导致一个正在执行的进程被异步打断,转而去处理一个突发事件.异步事件不可预知,仅仅能通过一些特定方式预防.或者说,当该异步事件发生时依据原来的设定完毕对应的操作. 信号本质 信号是在软件层次上对中断机制的一种模拟.在原理上,一个进程收到一个信号与处理器收到一个中断请求能够

Linux系统学习笔记:序

Linux系统学习笔记:序 ??Linux是一套免费使用和自由传播的类Unix操作系统,是一个基于POSIX和UNIX的多用户.多任务.支持多线程和多CPU的操作系统.它能运行主要的UNIX工具软件.应用程序和网络协议.它支持32位和64位硬件.Linux继承了Unix以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统. 本人使用的Linux为Ubuntu,主要以<APUE>(第3版)为学习蓝本. 1. Unix/Linux 体系结构 如图: 内核的接口被称为系统调用.公用函数库构建在

Redis学习笔记7--Redis管道(pipeline)

redis是一个cs模式的tcp server,使用和http类似的请求响应协议.一个client可以通过一个socket连接发起多个请求命令.每个请求命令发出后client通常会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client.基本的通信过程如下: Client: INCR X Server: 1 Client: INCR X Server: 2 Client: INCR X Server: 3 Client: INCR X Server: 4