管道通信
【预备知识】
进程间管道通信有无名管道通信和有名管道通信两种形式。
无名管道的创建与读写
- pipe()
系统调用pipe()来创建无名管道,与之相关的pipe()函数定义在头文件unistd.h中,它的一般形式为:int pipe(int fd[2])。该函数创建的管道的两端处于一个进程中,一个进程在由pipe()创建管道后,一般再创建一个子进程,然后通过管道实现父子进程间的通信。
pipe系统调用需要打开两个文件,文件标识符通过参数传递给pipe()函数。函数两端固定了任务,即一端只能用于读,用文件描述符fd[0]表示,称为管道读端;另一端只能用于写,用fd[1]表示,称为管道写端。调用成功时,pipe()返回值为0,错误时返回-1.
- write(pipeID[1],buf,size)
把buf中长度为size的消息写入管道写入口pipeID[1]。与文件不同的是,管道长度受到限制,管道满时写入操作将被阻塞。执行写操作的进程进入睡眠状态,直到管道中的数据被读取。fcntl()函数可将管道设置为非阻塞模式,管道满时,write()函数的返回值为0。如果写入数据的长度小于管道长度,则要求一次写入完成。如果写入数据的长度大于管道长度,在写完管道长度的数据时,write()函数将被阻塞。
- read(pipeID[1],buf,size)
从管道读出口pipeID[0]把长度为size的消息读出到buf中。读取的顺序与写入顺序相同。当数据被读取后,这些数据将自动被管道清除。因此,使用管道通信的方式只能是一对一,不能由一个进程同时向多个进程传递同一数据。如果读取的管道为空,并且管道写入端口是打开的,read()函数将被阻塞。读取操作的进程进入睡眠状态,直到有数据写入管道为止。fcntl()函数也可将管道读取模式设置为非阻塞。
- close()
管道虽然有两个端口,但只有一个端口能被打开,这样避免了同时对管道进行读和写的操作。关闭端口使用的是close()函数,关闭读端口时,在管道口进行写操作的进程将收到SIGPIPE信号。关闭写端口时,进行读操作的read()函数将返回0。
- lockf(files,function,size)
对文件上锁和开锁的系统调用。管道为一临界资源,使用过程中父子进程之间除了需要读写同步以外,在对管道进行读写操作时还需要互斥进入。统调用lockf(files,function,size)可对管道读写端进行加锁或解锁。其中,files是管道的读写端口;function是功能选择,为1表示上锁,为0表示开锁;size表示锁定或开锁的字节数,其值为0则表示文件的全部内容。
命名管道的创建与读写
创建命名管道的系统函数有两个:mknod和mkfifo。两个函数均定义在sys/stat.h中,函数原型如下:
- int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const char *path,mode_t mod);
函数mknod()中的参数path为创建的命名管道的全路径名;mode为创建的命名管道的模式,指明其存取权限;dev为设备值,该值取决于文件创建的种类,只在创建设备文件时才会用到。这两个函数调用成功都返回0,失败都返回-1。函数mkfifo的两个参数的含义和mknod相同。
命名管道创建后就可以使用了,命名管道和无名管道的使用方法基本相同。只是使用命名管道时,必须先调用open()将其打开。因为命名管道是一个存在于硬盘上的文件,而无名管道是存在于主存中的特殊文件。调用open()打开命名管道的进程可能会被阻塞。但如果同时用读写方式(O_RDWR)打开,则一定不会导致阻塞;如果以只读方式(O_RDONLY)打开,则调用open()函数的进程将会被阻止直到有写方式打开管道;同样以写方式(O_WRONLY)打开也会阻止直到有打开管道。
编程实现例<一>
【任务】
建立一个pipe,同时父进程产生一个子进程,子进程向pipe中写入一个字符串,父进程读出该字符串,并每隔3秒钟输出打印一次。
【程序】
#include <stdio .h> #include <signal.h> #include <unistd.h> main() { int x,fd [2]; char s[30]; pipe(fd); for( ; ;) { x=fork(); if(x==0) { sprintf(s ,“Good-night!\n”); write(fd[1],s,20); sleep(3); exit(0); } else { wait(0); read(fd[0],s ,20); printf(“********\n”, S) } } }
x
修改后的代码:
【运行结果】
【分析】
该程序建立了一个管道(pipe),并创建了一个子进程。子进程向管道中写入字符串"Good-night!\n",父进程从管道中读取该字符串,并每隔3秒钟输出打印一次
- 首先,程序通过pipe(fd)函数创建了一个管道,其中fd是一个包含两个文件描述符的数组,fd[0]用于读取数据,fd[1]用于写入数据。
- 进入无限循环,每次循环都会调用fork()函数创建一个子进程。
- 在子进程中,通过sprintf()函数将字符串"Good-night!\n"写入变量s中,然后使用write()函数将该字符串写入管道的写入端fd[1]。
- 子进程调用sleep(3)函数暂停3秒钟,然后调用exit(0)函数退出。
- 在父进程中,使用wait(0)函数等待子进程结束。
- 子进程结束后,父进程通过read()函数从管道的读取端fd[0]读取字符串,并将其存储在变量s中。
- 父进程使用printf()函数输出字符串"********",每次循环输出一次。
编程实现例<二>
【任务】编程实现两个子进程互斥向管道中写入信息,父进程从管道中读出信息。
【程序】
#include <stdio.h> main() { int i,r,p1,p2,fd[2]; char buf[50],s[50]; pipe(fd); while((p1=fork())==-1); if(p1==0) { lockf(fd[1],1,0); sprintf(buf,"Child process P1 is sending message!\n"); printf("Child process P1 is sending message!\n"); write(fd[1],buf,50); sleep(0); lockf(fd[1],0,0); exit(0); } else { while((p2=fork())==-1); if(p2==0) { lockf(fd[1],1,0); sprintf(buf,"Child process P2 is sending message!\n"); printf("Child process P2 is sending message!\n"); write(fd[1],buf,50); sleep(0); lockf(fd[1],0,0); exit(0); } wait(0); if ((r=read(fd[0],s,50))==-1) printf("can't read pipe.\n"); else printf("Parent process is reading (%s) \n",s); wait(0); if ((r=read(fd[0],s,50))==-1) printf("can't read pipe.\n"); else printf("Parent process is reading (%s) \n",s); } }
修改后的代码:
【运行结果】
【分析】
该程序实现了两个子进程互斥地向管道中写入信息,然后父进程从管道中读取信息
- 首先,程序通过pipe(fd)函数创建了一个管道,其中fd是一个包含两个文件描述符的数组,fd[0]用于读取数据,fd[1]用于写入数据。
- 进入循环,第一个子进程通过fork()函数创建。如果创建失败,则继续尝试创建子进程,直到成功。
- 在第一个子进程中,通过lockf(fd[1],1,0)函数对管道的写入端进行上锁,然后使用sprintf()函数将字符串"Child process P1 is sending message!\n"写入变量buf中,然后使用write()函数将该字符串写入管道的写入端fd[1]。
- 子进程调用sleep(0)函数暂停一段时间,然后通过lockf(fd[1],0,0)函数解锁管道的写入端,最后调用exit(0)函数退出。
- 在父进程中,继续循环,第二个子进程通过fork()函数创建。如果创建失败,则继续尝试创建子进程,直到成功。
- 在第二个子进程中,执行与第一个子进程相同的操作,但是写入的字符串是"Child process P2 is sending message!\n"。
- 父进程通过wait(0)等待子进程结束。
- 父进程使用read()函数从管道的读取端fd[0]读取字符串,并将其存储在变量s中。然后使用printf()函数输出读取到的字符串。
- 父进程再次使用read()函数从管道的读取端fd[0]读取另一个字符串,并将其存储在变量s中。然后使用printf()函数输出读取到的字符串。
因此,程序的运行结果是两个子进程分别向管道中写入信息,然后父进程从管道中读取并输出这些信息。由于使用了互斥锁,所以两个子进程的写入操作是互斥的,父进程可以按照子进程的顺序读取信息。
编程实现例<三>
【任务】编程实现独立进程通过有名管道进行通信。
【程序】
#include <fcntl.h> char sring[]=”this is a example to show fifo communication”; main(int argc,char *argv[]) { int fd; char buf[256]; int i; mknod(“fifo”,010777,0);/*创建属性为010777的管道文件,010为管道文件的类型,777为允许读写执行的属性*/ if (argc==2) { fd=open(“fifo”,O_WRONLY); } else { fd=open(“fifo”,O_RDONLY); } for (i=0;i<26;i++) { if(argc==2) { printf(“\I have wrote:%s”,string); write(fd,string,45); string[0]+=1; } else { read(fd,buf,256); printf(“\n The context by I have read is:!%s”,buf); buf[0]=”\0”; } } close(fd); }
修改后的代码:
【运行结果】
该程序一次在后台运行(&),另一次在前台运行。
【分析】
该程序实现了两个独立的进程通过有名管道进行通信。
运行结果中提到该程序一次在后台运行,另一次在前台运行
- 程序首先调用mknod("fifo", 010777, 0)函数创建一个属性为010777的有名管道文件,其中010为管道文件的类型,777为允许读、写、执行的属性。
- 在运行程序时,如果命令行参数argc的值为2,表示当前进程是写入进程。进程通过open("fifo", O_WRONLY)函数以只写模式打开管道文件,返回的文件描述符存储在变量fd中。
- 如果argc的值不为2,表示当前进程是读取进程。进程通过open("fifo", O_RDONLY)函数以只读模式打开管道文件,返回的文件描述符存储在变量fd中。
- 进入循环,循环次数为26次,每次循环执行以下操作:
- 如果是写入进程,进程使用printf()函数输出字符串"I have wrote: [string]",然后使用write()函数将字符串string写入到管道中。
- 如果是读取进程,进程使用read()函数从管道中读取数据,并将读取到的数据存储在变量buf中,然后使用printf()函数输出读取到的数据。
- 循环结束后,关闭管道文件的文件描述符fd。
根据运行结果的描述,该程序运行了两次,一次在后台运行,一次在前台运行。
实验总结
实验1: 建立一个pipe,同时父进程产生一个子进程,子进程向pipe中写入一个字符串,父进程读出该字符串,并每隔3秒钟输出打印一次。
该实验通过使用管道(pipe)实现了父子进程之间的通信。子进程向管道中写入一个字符串,父进程从管道中读取该字符串并每隔3秒钟输出打印一次。这个实验展示了管道的基本用法和进程间通信的机制。
实验2: 编程实现两个子进程互斥向管道中写入信息,父进程从管道中读出信息。
该实验使用互斥锁实现了两个子进程互斥地向管道中写入信息,然后父进程从管道中读取信息。子进程通过上锁和解锁管道的写入端来确保它们的写入操作是互斥的,父进程可以按照子进程的顺序读取信息。这个实验展示了互斥锁的使用和进程间通信的互斥性。
实验3: 独立进程通过有名管道进行通信。
该实验通过有名管道实现了两个独立进程之间的通信。进程通过创建一个有名管道文件,并使用不同的打开模式(只读或只写)打开管道文件来实现进程间的通信。一个进程向管道中写入数据,另一个进程从管道中读取数据。这个实验展示了有名管道的创建和打开方式,以及进程间通信的基本原理。
总结:
这三个实验都涉及了进程间的通信机制,包括使用管道和有名管道进行进程间的数据传递。通过这些实验,可以理解进程间通信的基本原理和常用方法,如管道、互斥锁等。进程间通信在多进程编程中起着重要的作用,可以实现不同进程之间的数据交换和协作,进而实现更复杂的应用程序。
12-24
11-03
12-11
2968
![](https://csdnimg.cn/release/blogv2/dist/pc/img/readCountWhite.png)
01-14
3365
![](https://csdnimg.cn/release/blogv2/dist/pc/img/readCountWhite.png)
04-16
3818
![](https://csdnimg.cn/release/blogv2/dist/pc/img/readCountWhite.png)
“相关推荐”对你有帮助么?
-
非常没帮助
-
没帮助
-
一般
-
有帮助
-
非常有帮助
提交