Unix网络编程学习笔记_进程间通信IPC之管道通信

一、前言

1、在从事嵌入式软件的相关工作中,也许会碰到许多编程技巧,比如,进程间的通信就算一种。大家可以用书本上的知识去指导下实践,反过来也可以用实践去检验书本上知识的正确性(6人还可以更深入地修改、完善参考书上的知识)。当然,笔者只是想整理和巩固下相关知识,并以项目的实际应用,让大家好对进程间的通信知识点重要性的定位,都是些基础知识,大神可以直接略过。在某网络编程之IPC参考书上,源码把许多宏和函数的定义都放在某一个文件中,当运行程序时需要依赖某些库文件;运笔者利用开源的思想,把每个案例所用到宏和函数的定义都提取出来,让大家最终都能看到标准的系统调用,单独编译一个主程序即可运行调试。
2、 首先,介绍下我碰到的实际应用案列。我开发嵌入式设备的需求:看上去有点类似手机的拍照功能,但不同的是运行的是Linux操作系统。首先,ARM的显存(Frame Buffer 0和Frame Buffer 1)有两个部分:一个是用来显示菜单部分的(Qt界面),另一个用来显示CCD图像,然后通过ARM 的叠加IP利用Overlay技术(Arm的 硬件模块,根据手册直接调用驱动接口即可实现),将两层进行叠加显示,大家就可以看到菜单界面的照相机了。
3、 那么Qt在Linux是一个进程,我的理解即单独运行的一个main函数,然后另一个进程就是CCD的预览程序(用到了V4L2显示架构的标准应用调用、DMA传输、线程同步、管道通信等技术)。如果想实现一个拍照工程,那么就在Qt菜单上进行拍照操作时,然后告知CCD预览进程并把要拍照的路径名字传递过去,在这里就用到了FIFO(有名管道),CCD预览程序就会抓一帧底层的数据,再通过ARM 的JPEG模块把拿到的一帧数据编码成图片。
4、 另外,加入在Qt界面还有其他的大量数据(从其他模块得到),比如GPS、日期、天气信息。然后我在拍照时,需要把这些数据添加到CCD照片中(以前听说只要你上传你的照片,就能查出你这张照片在何时何地拍出来的,这也没什么好惊讶的。照片包括了RGB图像数据和一些地理、日期的信息字节流数据,不明白的可以去看https://blog.csdn.net/psy6653/article/details/79658144)。这时又要用到进程间的通信,那么就不是用FIFO了,而是用的共享内存。

二、管道的特性以及开发环境

1、本节主要让大家了解下管道(有名和无名管道),个别函数使用的介绍(fork、watipid ),以及无名管道的演示。后续章节笔者会针对FIFO、锁和共享内存部分进行分析,我会用结合项目的代码进行讲解,并给大家共享源码。
无名管道和有名管道(FIFO)在一般的Linux系统中都是半双工通道,从一边写,从另一边读取,结构图如下,
在这里插入图片描述
无名管道一般用在有亲缘关系的父进程和子进程间(一个main函数中调用fork()创建子进程)的通信,而FIFO一般用于无亲缘关系的进程间(Linux操作系统下运行的两个main函数程序)的通信,FIFO往往是是用得最多的。

以下是有亲缘关系的父、子进程利用管道进行通信的结构图,用两根管道把它们连接起来,某著名参考书上管道2的数据流好像画反了。。。
在这里插入图片描述
管道1:父进程写数据(fd1[1]),子进程读数据(fd1[0]):
管道2:父进程读数据(fd2[0]),子进程写数据(fd2[1]):
所以fd1[1]和fd2[0]运行在父进程,fd1[0]和fd2[1]运行在子进程
在这里插入图片描述
平台:X86 PC
系统:LInux 内核3.1.0(Fedora16)
编译器:X86 gcc
绘图软件:Microsoft Office Visio
截图软件:FastStone_Capture

三、源码的分析

以下是管道通信的源代码
如果想查找某函数的头文件以及函数功能、参数的详细说明(标准系统调用),直接在终端用

`man 函数名`

客户端的源码程序运行在父进程,从管道1的fd1[1]端写数据,从管道2的fd2[0]端读数据,

void client(int readfd, int writefd)
{
        size_t  len;
        ssize_t n;
        char    buff[MAXLINE];

#ifdef RUN_STEP_DEBUG
        printf("[4]从控制台(stdin)读入输入路径\n");
#endif
		//等待终端输入文件的绝对路径
        fgets(buff, MAXLINE, stdin);/*stdin在<stdio.h>头文件声明*/
        len = strlen(buff);/* fgets()保证以字符串空(即null,ASCII值为0)结束*/
        if (buff[len-1] == '\n')
                len--;/* 删除输入路径中的换行符(即‘\n’,ASCII值为10)*/

#ifdef RUN_STEP_DEBUG
        printf("[7]把路径写入进程间管道(fd1[1])\n");
#endif
        write(writefd, buff, len);

#ifdef RUN_STEP_DEBUG
        printf("[8]从进程间管道(fd2[0])读出内容,并输出到标准输出控制台\n");
#endif
        while ( (n = read(readfd, buff, MAXLINE)) > 0)
                write(STDOUT_FILENO, buff, n);/*STDOUT_FILENO在<unistd.h>头文件声明*/
}

客户端需要用的strlen和open函数,man strlen命令即可查到,查找其他的函数类似,
这里写图片描述
这里写图片描述
服务端的源码程序运行在子进程,从管道1的fd1[0]端读数据,从管道2的fd2[1]端写数据,源码如下,

void server(int readfd, int writefd){
        int             fd;
        ssize_t n;
        char    buff[MAXLINE+1];
#ifdef RUN_STEP_DEBUG
        printf("[6]从进程间管道(fd1[0])读入输入路径\n");
#endif
        if ( (n = read(readfd, buff, MAXLINE)) == 0)
                buff[n] = '\0';/*路径以null结束 */

        if ( (fd = open(buff, O_RDONLY)) < 0) {
                #ifdef RUN_STEP_DEBUG
                        printf("[9]如果打开输入路径失败,向管道(fd2[1])写入提示原因告知客户端\n");
                #endif
                snprintf(buff + n, sizeof(buff) - n, ": can't open, %s\n",strerror(errno));/*errno在<errno.h>头文件声明*/
                n = strlen(buff);
                write(writefd, buff, n);

        } else {
                #ifdef RUN_STEP_DEBUG
                        printf("[9]如果打开输入路径成功,向管道(fd2[1])写入整个文件内容\n");
                #endif
                while ( (n = read(fd, buff, MAXLINE)) > 0)
                        write(writefd, buff, n);
                close(fd);
        }
}

子进程服务端需要用的snprintf()函数需要的头文件如下,其作用是把数据按照一定的格式放在buff中
这里写图片描述
main函数的源码如下:

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

#define MAXLINE (4096)
#define RUN_STEP_DEBUG

void    client(int, int), server(int, int); /*客户端(主进程)和服务端(子进程)函数声明*/

int main(int argc, char **argv){
        int             fd1[2], fd2[2];
        pid_t   pid;/*pid_t类型在<sys/types.h>头文件声明*/

#ifdef RUN_STEP_DEBUG
        printf("[1]创建2个管道\n");
#endif
        pipe(fd1);
        pipe(fd2);

#ifdef RUN_STEP_DEBUG
        printf("[2]创建一个子进程\n");
#endif
        pid = fork();
        if (pid < 0){
                printf("error in fork!");

        }else if ( pid == 0) {
                close(fd1[1]);
                close(fd2[0]);
                #ifdef RUN_STEP_DEBUG
                        printf("[5]子进程执行服务端函数\n");
                #endif
                server(fd1[0], fd2[1]);
                exit(0);
        }else{
                close(fd1[0]);
                close(fd2[1]);

#ifdef RUN_STEP_DEBUG
                printf("[3]执行客户端函数\n");
#endif
                client(fd2[0], fd1[1]);

                waitpid(pid, NULL, 0);/* 等待子进程结束 */
                exit(0);
        }
}

以下是pipe、fork、waitpid函数需要的头文件信息。
这里写图片描述
这里写图片描述
其中fork函数的使用方法,
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
这里写图片描述
waitpid函数在次是阻塞当前进程,直到子进程结束,接收子进程的结束状态值。根据某参考书上,在此解释下waitpid函数的作用,在子进程调用exit(0)终止后(fork的子进程),但父进程仍然在运行,内核会马上给父进程产生一个SIGCHLD信号,而父进程并没有捕捉该信号(默认是被忽略的),此时已终止的子进程称为僵尸进程。当父进程的client函数从管道读入最终数据返回,再调用waitpid函数获取该僵尸进程的状态。如果父进程没有调用该函数,那么子进程将托孤给init进程(操作系统的守护进程,一直都在运行)的孤儿进程,内核将向init进程发送另外一个SIGCHLD信号,让守护进程取得该终止子进程的终止状态。

makefile

OBJS=mainpipe.o
%.o:%.c
        gcc -c $< -o $@
all:$(OBJS)
        gcc $(OBJS) -o mainpipe
clean:
        rm *.o mainpipe

四、程序的编译测试

这里写图片描述
运行失败的结果
这里写图片描述
运行权限的问题
这里写图片描述
修改文件夹wwww无读、写、执行权限;
这里写图片描述

下图是程序正常运行的结果:
在这里插入图片描述
免费分享演示代码链接: https://pan.baidu.com/s/1JmuZGE0MFxC_aCFEHbuWHA 提取码: dsje
在这里插入图片描述
在这里插入图片描述

UNIX网络编程.卷2:进程间通信(第2版)》是一部UNIX网络编程的经典之作!进程间通信(IPC)几乎是所有Unix程序性能的关键,理解IPC也是理解如何开发不同主机间网络应用程序的必要条件。《UNIX网络编程.卷2:进程间通信(第2版)》从对Posix IPC和System V IPC的内部结构开始讨论,全面深入地介绍了4种IPC形式:消息传递(管道、FIFO、消息队列)、同步(互斥锁、条件变量、读写锁、文件与记录锁、信号量)、共享内存(匿名共享内存、具名共享内存)及远程过程调用(Solaris门、Sun RPC)。附录中给出了测量各种IPC形式性能的方法。   《UNIX网络编程.卷2:进程间通信(第2版)》内容详尽且具权威性,几乎每章都提供精选的习题,并提供了部分习题的答案,是网络研究和开发人员理想的参考书。 W.Richard Stevens,国际知名的UNIX和网络专家,备受赞誉的技术作家他1951年2月5日出生于赞比亚,后随父母回到美国中学时就读于弗吉尼亚菲什伯恩军事学校,1973年获得密歇根大学航空和航天工程学士学位,1975年至1982年,他在亚利桑那州图森市的基特峰国家天文台从事计算机编程工作,业余时间喜爱飞行运动,做过兼职飞行教练这期间他分别在1978年和1982年获得亚利桑那大学系统工程硕士和博士学位此后他去康涅狄格州纽黑文的健康系统国际公司任主管计算机服务的副总裁,1990年他回到图森,从事专业技术写作和咨询工作写下了多种经典的传世之作。 好不容易找到的高清版本,特意拿出来和大家共享。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值