歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> UNIX網絡編程之管道與FIFO

UNIX網絡編程之管道與FIFO

日期:2017/3/1 11:51:40   编辑:關於Linux

管道是最初的Unix IPC形式,它們的最大局限是沒有名字,所以,管道只能用於有親緣關系的進程只見使用。之後,慢慢隨著FIFO的加入,這點才有所改觀。FIFO也成為又名管道。管道和FIFO的共同點就是它們都是通過read和write函數進行訪問的。

管道:
管道時有pipe函數創建,提供一個單路數據流。也就是說,所有的管道都是半雙工。
管道創建方法:
#include
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
#include
#include
#include
#include
#include
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 
#include 

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
#include
#include
#include
#include
#include
#include 
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;
}
Copyright © Linux教程網 All Rights Reserved