【问题标题】:Implementing a Pipe in C++在 C++ 中实现管道
【发布时间】:2015-09-29 00:01:01
【问题描述】:

我在实现我的管道时遇到了麻烦。它从文本文件中读取 Unix 终端命令以获取诸如“ls|wc”之类的命令,并在其中打开管道,以便 ls 的输出可用于 wc。

我已经实现它来解析程序名称(“ls”,“wc”)并将它们存储在两个单独的数组(参数和参数2)中,派生一个子进程,然后让该子进程派生另一个子进程,然后是第二个子进程调用 execvp() 命令并传递第一个要执行的程序。

然后通过更改标准输出将 ("ls") 的输出写入管道。前一个子进程然后 execvp() 的另一个进程(“wc”)并通过更改标准输入从管道中读取。

但是,wc 无限循环,似乎没有计算来自 ls 的单词数。 ls 在有字数的目录中执行。

有什么建议吗?非常感谢,很抱歉冗长的解释。

这是一个更简单的例子:它分叉,创建一个管道,实现“ls”并将其输出写入管道,返回父级并从管道中读取“ls”的输出。它似乎仍然在永远阅读或无法正常工作。

//
//  main.cpp
//  Pipe_Test
//
//  Created by Dillon Sheffield on 9/28/15.
//  Copyright © 2015 Dillon Sheffield. All rights reserved.
//

#include <iostream>
using namespace std;

int main()
{
    char* arguments[2];
    char* programArguments[1];
    int fd[2];

    arguments[0] = new char[2];
    arguments[1] = new char[2];
    programArguments[0] = new char[1];
    programArguments[0][0] = '\0';
    string ls = "ls";
    string wc = "wc";
    for (int i = 0; i < 1; i++) {
        arguments[0] = &ls.at(i);
        arguments[1] = &wc.at(i);
    }

    pid_t pid = fork();
    pipe(fd);

    if (pid < 0) {
        perror("Failed.\n");
    } else if (pid == 0) {
        dup2(fd[1], STDOUT_FILENO);

        execvp(arguments[0], programArguments);
    }

    wait(NULL);

    dup2(fd[0], STDIN_FILENO);
    execvp(arguments[1], programArguments);

    close(0);
    close(1);

    return 0;
}

这是我的原始代码:

//
//  main.cpp
//  homework2
//
//  Created by Dillon Sheffield on 9/19/15.
//  Copyright © 2015 Dillon Sheffield. All rights reserved.
//

#include <iostream>
#include <fstream>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;

// Global Variable(s)
const short inputLineSize = 10; // Size of programName, arguments, and argument name.
char *arguments[inputLineSize];
char *arguments2[inputLineSize];
ifstream inputFile;
char* input;

void readLine()
{
    // Create new char array(s)
    input = new char[inputLineSize];

    // Initialize the char array(s)
    for (int i = 0; i < inputLineSize; i++)
    {
        input[i] = '\0';
    }

    // Read a line and skip tabs, spaces, and new line characters
    for (int i = 0; !inputFile.eof() && inputFile.peek() != '\n'; i++)
    {
        while (inputFile.peek() == '\n' || inputFile.peek() == '\t' || inputFile.peek() == ' ') inputFile.get();
        inputFile.get(input[i]);
    }

    // If the file is multi-spaced, keep reading new line char(s) to clear them
    while (inputFile.peek() == '\n') inputFile.get();
}

void parseTokens()
{
    //----------Parse the read line into tokens--------------------------------------------//

    // Get the program name
    for (int i = 0; i < inputLineSize; i++)
    {
        arguments[i] = new char[inputLineSize];
        for (int j = 0; j < inputLineSize; j++)
            arguments[i][j] = '\0';
    }

    int i = 0;
    int j = 0;
    while (input[i] != '\0' && input[i] != '-' && input[i] != '|')
    {
        arguments[j][i] = input[i];
        i++;
    }

    // Tokenize arguments if supplied
    j++;
    int k;
    while (input[i] == '-')
    {
        k = 0;
        arguments[j][k] = input[i];
        i++;
        k++;

        while (input[i] != '-' && input[i] != '\0')
        {
            arguments[j][k] = input[i];
            i++;
            k++;
        }
        j++;
    }

    // Delete unused arguments
    while (j < inputLineSize)
    {
        delete arguments[j];
        arguments[j] = NULL;
        j++;
    }

    // Check if the pipe command '|' is supplied
    if (input[i] == '|')
    {
        i++;

        // Get the other program name
        for (int x = 0; x < inputLineSize; x++)
        {
            arguments2[x] = new char[inputLineSize];
            for (int y = 0; y < inputLineSize; y++)
                arguments2[x][y] = '\0';
        }

        int x = 0;
        int j = 0;
        while (input[i] != '\0' && input[i] != '-' && input[i] != '|')
        {
            arguments2[j][x] = input[i];
            i++;
            x++;
        }

        // Tokenize arguments if supplied
        j++;
        int k;
        while (input[i] == '-')
        {
            k = 0;
            arguments2[j][k] = input[i];
            i++;
            k++;

            while (input[i] != '-' && input[i] != '\0')
            {
                arguments2[j][k] = input[i];
                i++;
                k++;
            }
            j++;
        }

        // Delete unused arguments
        while (j < inputLineSize)
        {
            delete arguments2[j];
            arguments2[j] = NULL;
            j++;
        }
    }
}

int main()
{
    // Variable(s)
    pid_t pid;
    pid_t pid2;
    int fd[2];

//--Open the file named "input"-------------------------------------------------------//

    inputFile.open("input", ios::in);

    // Check if opening the file was successful
    if (inputFile.is_open())
    {
        // Read until the file has reached the end
        while (!inputFile.eof())
        {
            // Read a line and parse tokens
            readLine();
            parseTokens();

//----------Now create a new process with parsed Program Name and Arguments-----------//

            // Create a pipe
            pipe(fd);

            // Fork
            pid = fork();
            if (pid < 0)
            {
                perror("Fork failed.\n");
                return -2;
            }
            else if (pid == 0)
            {
                // Fork again
                pid2 = fork();

                if (pid2 < 0)
                {
                    perror("Fork failed.\n");
                    return -2;
                }
                else if (pid2 == 0)
                {
                    // Change standard output
                    if (dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO) perror("dup2 error to stdout.\n");

                    // Execute the given program
                    execvp(arguments[0], arguments);
                }


                // Change the standard input to the pipe
                if (dup2(fd[0], STDIN_FILENO) != STDIN_FILENO) perror("dup2 error to stdin.\n");

                int returnValue = execvp(arguments2[0], arguments2);
                if (returnValue == -1) perror("Error has occurred.\n");

                // Close the pipe and exit
                close(fd[0]);
                close(fd[1]);
                exit(0);
            }

            // Wait for the child so it doesn't become a Zombie
            wait(NULL);


//----------Clean up-----------------------------------------------------------------//
            delete input;
            input = NULL;
            int i = 0;
            while (arguments[i] != NULL)
            {
                delete arguments[i];
                arguments[i] = NULL;
                i++;
            }
            i = 0;
        }
    }
    else perror("Cannot open file.\n");

    inputFile.close();
    return 0;
}

【问题讨论】:

  • 您似乎同时尝试了两件棘手的事情:读取/解析 unix 命令,并使用叉子和管道执行它们。 分离这两个东西,单独测试;至少这会让你更接近minimal complete example

标签: c++ unix input pipe output


【解决方案1】:

您首先执行pipe(),然后执行fork(),子进程通过一些额外的工作执行您的命令。

这里的问题是pipe()d 文件描述符,它们都在原始父进程中保持打开状态。管道的读取端,更重要的是管道的写入端。从所有外观来看,这些文件描述符确实为您的子进程正确设置,但由于文件描述符在父进程中也保持打开状态,因此管道永远不会关闭,即使在写入管道写入端的进程终止后也是如此.

由于管道的写入端保持打开状态,因此在父进程中,正在读取管道读取端的子进程将继续读取。永远。

你需要做的是,在fork之后,在父进程中,关闭管道的读端和写端。一旦初始进程被分叉,父进程就不需要管道,并且它的打开文件描述符会妨碍。

【讨论】:

  • 谢谢你,山姆。我似乎无法让它继续工作。我应该在哪里添加 close(fd[0]) 和 close(fd[1]) 命令?我想我对此有点困惑。
  • 在父进程中,正如我解释的那样。
  • 我的代码确实关闭了父进程中的管道末端?
  • 不,它显然在第一个分叉子进程中关闭了它们。它不会在原始父进程中关闭它们。
  • 好的,我在 fork 之后的父进程中关闭了它们,但它似乎仍然在无休止地阅读......
猜你喜欢
  • 2014-03-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-25
  • 2012-01-13
相关资源
最近更新 更多