UNIX网络编程之管道与FIFO

来源:互联网 发布:卖家多久查出淘宝联盟 编辑:程序博客网 时间:2024/06/05 06:52

管道是最初的Unix IPC形式,它们的最大局限是没有名字,所以,管道只能用于有亲缘关系的进程之间使用。之后,慢慢随着FIFO的加入,这点才有所改观。FIFO也成为有名管道。管道和FIFO的共同点就是它们都是通过read和write函数进行访问的。

管道:
管道是由pipe函数创建,提供一个单路数据流。也就是说,所有的管道都是半双工。
管道创建方法:

#include <unistd.h> int pipe(int fd[2]);

该函数返回两个文件描述符:fd[0] (用来打开读)、fd[1] (用来打开写)。管道只是它形象的叫法,它的本质实际上就是文件。

单个进程中的管道模式:

这里写图片描述
一般管道很少只在单个进程中进行使用。管道最常用于两个不同但有亲缘关系的进程(一个父进程,一个子进程。或两个有共同祖先的进程)中,提供进程间的通信。

进程间通信模式:
这里写图片描述
只要有亲缘关系的两个进程都可以用管道进行通信。这里我们用父进程和子进程进行介绍。
首先,我们有主进程创建一个管道后,调用fork()函数派生一个自身的副本。此时主进程将成为父进程,它的副本将成为子进程。完成这些预备操作后,父进程将关闭相应管道的读出端(fd[0]),子进程将关闭该管道的写入端(fd[1])。这样父进程可以通过write函数写入数据,而子进程通过read函数读出数据【必须先写入数据才能读出】。

进程间双向数据流:
这里写图片描述
双向数据与进程间单向数据流十分相似。只是它是创建了两个管道。父进程关闭了管道1的读端口(fd1[0])和管道2的写端口(fd2[1]),子进程则恰好相反,它关闭的是管道2的读端口(fd2[0])和管道1的写端口(fd1[1])。这样,两个管道可以保证数据的双向流动。父进程由管道2进行读数据,由管道1进行写数据,而子进程则由管道2写数据,由管道1读数据。

下面是进程间双向数据流的实现代码:
步骤:
(1)、创建管道1和管道2(利用pipe函数)
(2)、fork一个子进程
(3)、父进程关闭管道1的读端口(fd1[0])和管道2的写端口(fd2[1])
(4)、子进程关闭管道1的写端口(fd1[1])和管道2的读端口(fd2[1])

#include<iostream>#include<unistd.h>#include<stdio.h>#include<string.h>#include<stdlib.h>#include<sys/wait.h>using namespace std;int main(){    //分别定义一个字符串数组记录父进程和子进程所传数据,最后一个为NULL    char* parent_talk[] = {"Hello",                           "can you tell me current data and time?",                           "I have to go, Bye",                          NULL};    char* child_talk[] = {"Hi",                          "No problem:",                         "Bye.",NULL};    int fd1[2], fd2[2];  //创建两个管道    //检测管道是否创建成功,如果创建成功会返回0,否则返回-1    if(pipe(fd1)<0)         {        printf("create pipe1 error.\n");        exit(1);    }    if(pipe(fd2)<0)    {        printf("create pipe2 error.\n");        exit(1);    }    pid_t pid;    pid = fork(); //fork一个子进程,并将子进程的id号符给父进程的pid    if(pid == 0)   //子进程没有自己的子进程,所以子进程pid = 0    {        char buffer[256];        //关闭子进程需要关闭的端口        close(fd1[1]);        close(fd2[0]);        int i=0;        char *child = child_talk[i];        while(child != NULL)        {   //只要子进程字符串数组不为NULL,就说明通信为及未结束            //从管道1中读出数据,并打印出来            read(fd1[0],buffer,256);            printf("Parent:>%s\n",buffer);            //给管道2中写入数据            if(i == 1)            {                time_t t;                time(&t);                sprintf(buffer,"%s%s",child,ctime(&t));                write(fd2[1],buffer,strlen(buffer)+1);            }else{                write(fd2[1],child,strlen(child)+1);            }            i++;            child = child_talk[i];        }        //数据传输结束后,关闭所有端口        close(fd1[0]);        close(fd2[1]);    }    //父进程    else if(pid > 0)    {        char buffer[256];        close(fd1[0]);        close(fd2[1]);        int i = 0;        char *parent = parent_talk[i];        //父进程的字符串数组中数据不为NULL时,继续写入数据        while(parent != NULL)        {             //将数据写入管道1            write(fd1[1],parent,strlen(parent)+1);            //从管道2中读出子进程发送的数据            read(fd2[0],buffer,256);            printf("Child:>%s\n",buffer);            i++;            parent = parent_talk[i];        }        //通信结束后,关闭所有端口        close(fd1[1]);        close(fd2[0]);        //等待子进程结束,然后回收它的空间,防止它成为孤儿进程        int status;        wait(&status);    }    //如果pid不满足上述条件,则说明fork子进程失败    else    {        printf("Create child process error!\n");    }    return 0;}

FIFO(有名管道):
FIFO即先进先出,每个FIFO有一个路径名与之相关联,所以它可以实现无亲缘关系的进程之间进行通信访问同一个FIFO。FIFO又称为有名管道。与管道不同的是,FIFO是由mkfifo函数创建,创建成功则返回0,失败则返回1。

#include <sys/types.h>#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);

其中pathname是一个普通的路径名,它将是该FIFO的名字;mode则指定文件权限位,一般使用的权限位参数为:O_CREAT|O_EXCL,意思为,它要么创建一个新的FIFO,要么返回一个EEXIST(已存在错误)。在使用mkfifo函数时,它会检测是否返回EEXIST错误,如果返回该错误,则直接调用open函数打开即可。
在创建出一个FIFO后,必须打开读或写,但不能同时打开读和写,因为它和管道一样也是半双工。
对于管道和FIFO而言,write是往末尾添加数据,而read则是从头部返回数据。

用两个FIFO实现客户-服务器:
这里写图片描述
它的原理和用管道实现双向数据流相似,它是用FIFO1来进行服务器给客户端发送数据,而用FIFO2来实现客户端给服务器传送数据。

实现程序:
utili.h 头文件:

#pragma once#include<iostream>#include<unistd.h>#include<stdio.h>#include<stdlib.h>#include<string.h>#include<fcntl.h>#include <sys/stat.h>using namespace std;//创建两个路径名(mkfifo函数中pathname参数)const char *write_fifo_name = "write_fifo";const char *read_fifo_name = "read_fifo";

ser.cpp:服务端程序:

#include"utili.h"int main(){    int write_fd;    int read_fd;    //创建一个write_fifo_name的FIFO                                                  int res = mkfifo(write_fifo_name,O_CREAT|O_EXCL|S_IRUSR|S_IWUSR);    if(res == -1)   //如果返回值为-1,则创建FIFO失败    {        printf("make write fifo error.\n");        exit(1);    }    //创建成功后,以只写方式打开write_fifo_name管道    write_fd = open(write_fifo_name,O_WRONLY);    //如果返回-1则表明打开失败    if(write_fd == -1)    {        printf("open write fifo error.\n");        unlink(write_fifo_name);        exit(1);    }    //打开成功后等待客户端    printf("Wait Client Connect......\n");    //以只读方式打开read_fifo_name,并等待客户端的连接    while((read_fd = open(read_fifo_name, O_RDONLY)) == -1)    {        sleep(1);    }    printf("Client Connect Ok.\n");    定义一个发送数组和接收数组    char sendbuf[256];    char recvbuf[256];    while(1)    {        //服务器从write_fifo_name写入数据        printf("Ser:>");        scanf("%s",sendbuf);        write(write_fd,sendbuf,strlen(sendbuf)+1);        //服务器从read_fifo_name读出来自客户端的数据        read(read_fd,recvbuf,256);        printf("Cli:>%s\n",recvbuf);    }    return 0;}

cli.cpp客户端程序:

#include"utili.h"int main(){    int write_fd, read_fd;//创建一个名为read_fifo_name的FIFO,如果创建失败则返回-1,成功则返回0        int res = mkfifo(read_fifo_name, O_CREAT|O_EXCL|S_IRUSR|S_IWUSR);    if(res == -1)    {        printf("make read fifo error.\n");        exit(1);    }    //客户端以只读的形式打开write_fifo_name    read_fd = open(write_fifo_name, O_RDONLY);    if(read_fd == -1)  //如果返回值为-1则表明打开失败    {        printf("Server Error.\n");        unlink(read_fifo_name);        exit(1);    }    //客户端以只写方式打开read_fifo_name    write_fd = open(read_fifo_name,O_WRONLY);    if(write_fd == -1)  //如果返回值为-1,则打开失败    {        printf("Client Connect Server Error.\n");        exit(1);    }    //定义两个字符串数组,分别用来存放客户端发送数据和接收的数据    char sendbuf[256];    char recvbuf[256];    while(1)    {        //客户端通过write_fifo_name来读取来自服务器的数据        read(read_fd,recvbuf,256);        printf("Ser:>%s\n",recvbuf);        //客户端通过write_fifo_name写入数据        printf("Cli:>");        scanf("%s",sendbuf);        write(write_fd,sendbuf,strlen(sendbuf)+1);    }    return 0;}
1 0
原创粉丝点击