如何在C ++中使用管道构建聊天程序

问题描述

我想实现一个运行小程序1或小程序2的小程序,具体取决于调用main时输入的参数。打开2个终端和第1个聊天室后,我希望在2个终端之间发送小的字符串消息。

我尝试实现一个简单的管道程序,在该程序中,进程将以读取或写入的方式打开管道。然后,如果进程不是您用于其他目的的管道,则该进程将关闭相应的一端,读取端或写入端。打开2个终端,并使用参数0或1调用main时,我调用2个进程打开管道。

但是,我在运行程序以在2个进程之间连续发送消息时遇到麻烦。每当检测到键盘输入时,该过程就会退出

我怀疑我没有正确打开管道。 (我的代码中没有打开函数)。打开函数还要求我输入管道的路径名,该路径名在/ proc / [PID] / fd中。我看到每次运行程序时PID都会不同。因此,如何将其放入开放函数的参数中。

请帮助我修改代码

#include <unistd.h>   /* to use pipe,getpid*/
#include <stdio.h>    /*to use prinf*/
#include <iostream>   /*to use cin,cout*/
#include <string.h>   /*to use str function*/
#include <errno.h>    /* to use with errno*/

int onServiceStart(int chatter,int readPipe[],int writePipe[]){
    if( close(readPipe[1]) == -1 ){   //if use the pipe to read,then close the write end of the pipe
        std::cout<<"Error closing the write end of read pipe"<<errno<<std::endl;
    };
    if( close(writePipe[0]) == -1 ){   //if use the pipe to write,then close the read end of the pipe
        std::cout<<"Error closing the read end of write pipe"<<errno<<std::endl;
    };
    while(true){
        //preparing to get the string from keyboard
        std::string chatInput;
        getline(std::cin,chatInput);
        //send the string to pipe
        if ( write( writePipe[1],chatInput.c_str(),strlen( chatInput.c_str() )) == -1) {
            std::cout<<"Error writing to write end of write pipe"<<errno<<std::endl;
        };
        //preparing the buffer to put the string to after reading from pipe
        char buffer;
        if ( read(readPipe[0],&buffer,1)== -1) {
            std::cout<<"Error reading from read end of read pipe"<<errno<<std::endl;
        };
        std::cout<<buffer;
    }
};

int main(int argc,char *argv[]){
    int chatter = *(argv[1]) - '0';
    int fileDescription1[2],fileDescription2[2] ;
    if ( pipe(fileDescription1)==-1 ) {
        printf("cannot create pipe 1");
    };
    if ( pipe(fileDescription2)==-1 ) {
        printf("cannot create pipe 2");
    };

    switch (chatter) {
        case 0:
            printf("PID %d,chatter 1: \n",getpid());
            onServiceStart(chatter,fileDescription1,//chatter1 will use fileDescription1 to read,fileDescription2);     //chatter1 will use fileDescription2 to write,case 1:
            printf("PID %d,chatter 2: \n",fileDescription2,//chatter2 will use fileDescription2 to read,fileDescription1);    //chatter2 will use fileDescription1 to write,}
}

解决方法

有几个问题。进程立即退出的原因是,您的程序仅在管道的一侧运行,而不在另一侧运行。例如,如果您使用chatter = 0运行它,则将fileDescription2用作writePipe,但是在onServiceStart()中要做的第一件事是close()的阅读面writePipe中的。这意味着每当您写入writePipe[1]时,由于writePipe[0]已关闭,您将收到EPIPE错误。

如果要启动程序的两个实例,一个以0作为参数,另一个以1进行调用,则需要使用一对named pipes它们之间可以进行通信,尽管如果您希望进行双向通信,则命名UNIX socket甚至更好,因为您只需要一个即可。

请注意,只有在您的程序派生或创建多个线程时,使用pipe()创建的一对匿名管道才有用。

另一个问题是您没有从管道的另一端读取整个响应:

//preparing the buffer to put the string to after reading from pipe
char buffer;
if ( read(readPipe[0],&buffer,1)== -1) {
    ...

这仅读取一个字符。之后,您将再次从标准输入中读取内容,因此发送的每一行只会收到一个字符,这显然不是您想要的。

处理此问题的正确方法是通过传递O_NONBLOCK标志使管道成为非阻塞管道,并使用select()poll()循环来等待两个标准中的数据同时输入和管道。

,

我改用命名管道。未命名管道不能用于2个独立进程之间的通信,而只能用于父进程和子进程。

由于管道是单向的,因此我必须使用2个管道。对于每个进程,管道将用作只读或仅写。例如,进程0仅将管道1打开为只读,将仅调用read()函数读取管道。

由于它被命名为管道,因此我必须手动关闭和删除管道。在这段代码中,我还没有实现。在关闭程序或“ Crtl + C”程序时,将需要一种信号捕获机制来删除管道。如果您在main 0之前运行main 1,还会有一个小错误。不过,下面的小代码演示了管道的基础。

这是我的代码。

#include <unistd.h>    /* to use pipe,getpid */
#include <sys/stat.h>  /* to use named pipe mkfifo */
#include <sys/types.h> /* to use open() */
#include <fcntl.h>     /* to use open() */
#include <stdio.h>     /*to use prinf */
#include <iostream>    /*to use cin,cout */
#include <string.h>    /*to use str function */
#include <errno.h>     /* to use with errno */
#include <thread>      /* to use thread */

#define PIPE1_PATH "/home/phongdang/pipe1"
#define PIPE2_PATH "/home/phongdang/pipe2"

int openPipe(const char* pipePathName,int flag){
    int fileDescriptor;
    fileDescriptor = open(pipePathName,flag) ;
    if ( fileDescriptor == -1) {
        std::cout<<"Error open pipe at "<<pipePathName<<" .Error code: "<<errno<<std::endl;
    };
    return fileDescriptor;
}

void writePipe(int writePipeDescriptor){
    while (true){
        //preparing to get the string from keyboard
        std::string chatInput;
        getline(std::cin,chatInput);
        int writeBytes = write( writePipeDescriptor,chatInput.c_str(),strlen( chatInput.c_str() ));
        //send the string to pipe
        if ( writeBytes == -1) {
        std::cout<<"Error writing to write end of write pipe"<<errno<<std::endl;
        }
        else {
            printf("Writing to pipe %d bytes \n",writeBytes);
        }
        sleep(1);
    }
}

void readPipe(int readPipeDescriptor){
        char buffer[100];
    while (true){
        //preparing the buffer to put the string to after reading from pipe
memset(buffer,'\0',100);
       // memset(buffer,10);
        int readByte = read(readPipeDescriptor,10);
        if ( readByte== -1) {
            std::cout<<"Error reading from read end of read pipe"<<errno<<std::endl;
        }
        else std::cout<<"Read "<<readByte<<" bytes from pipe :"<<buffer<<std::endl;
        sleep(1);
    }
}

int main(int argc,char *argv[]){
    int chatter = *(argv[1]) - '0';
    int writePipeDescriptor,readFileDescriptor;
    switch (chatter) {
        case 0:
        {
            printf("PID %d,chatter 1: \n",getpid());
            //create pipe is done by chatter 0 only (this is just a hot fix to prevent error 17 EEXIST)
            if ( mkfifo(PIPE1_PATH,S_IRUSR | S_IWUSR | S_IWGRP ) ==-1 ) {  //create pipe for read/write/execute by owner,and others
                std::cout<<("cannot create pipe 1 \n")<<errno<<std::endl;
            };
            writePipeDescriptor = openPipe(PIPE1_PATH,O_WRONLY);
            readFileDescriptor  = openPipe(PIPE2_PATH,O_RDONLY);
            std::thread readThread(readPipe,readFileDescriptor);    //has to create thread and execute thread first.
            writePipe(writePipeDescriptor);
            readThread.join();
            break;
        }
        case 1:
        {
            printf("PID %d,chatter 2: \n",getpid());

            if ( mkfifo(PIPE2_PATH,S_IRUSR | S_IWUSR | S_IWGRP ) ==-1 ) {           //create pipe for read/write/execute by owner,and others
                std::cout<<("cannot create pipe 2 \n")<<errno<<std::endl;
            };
            readFileDescriptor = openPipe(PIPE1_PATH,O_RDONLY);
            writePipeDescriptor = openPipe(PIPE2_PATH,O_WRONLY);
            std::thread writeThread(writePipe,writePipeDescriptor);
            readPipe(readFileDescriptor);
            writeThread.join();
            break;
        }
    }
    return 0;
}