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

Posted

技术标签:

【中文标题】如何在 C++ 中使用管道构建聊天程序【英文标题】:How to build a chat program with pipe in C++ 【发布时间】:2020-10-10 06:55:11 【问题描述】:

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

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

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

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

请帮我修改我的代码。

#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", getpid());
            onServiceStart(chatter,
                            fileDescription2,       //chatter2 will use fileDescription2 to read,
                            fileDescription1);    //chatter2 will use fileDescription1 to write,
    

【问题讨论】:

你标记了这个 C++,而大部分代码是 C。你甚至设法混合 cout 和 printf。选择一种语言。 @JHBonarius 无需为此挑剔。可改进的、类似 c 的 C++ 仍然是 C++。 【参考方案1】:

有几个问题。进程立即退出的原因是因为您的程序只运行管道的一侧,而不是另一侧。例如,如果您使用chatter = 0 运行它,您使用fileDescription2 作为writePipe,但您在onServiceStart() 中做的第一件事是close() writePipe 的读取端。这意味着无论何时你写信给writePipe[1],你都会收到一个EPIPE 错误,因为writePipe[0] 已关闭。

如果打算启动程序的两个实例,一个以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个独立进程之间的通信,只能用于父子进程。

由于管道是单向的,我必须使用 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, 0, 10);
        int readByte = read(readPipeDescriptor,&buffer,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;

【讨论】:

以上是关于如何在 C++ 中使用管道构建聊天程序的主要内容,如果未能解决你的问题,请参考以下文章

在IOS中构建一个使用node.js服务器的聊天应用程序[关闭]

使用 Angularjs 和 Firebase 构建聊天应用程序

使用 Go 和 ReactJS 构建聊天系统(前言)

使用 androidNodeJs 和 Socket.io 创建一个实时聊天应用程序

使用 xmpp 或任何其他框架在 ios 中使用谷歌帐户构建聊天应用程序[关闭]

只需五步,快速构建Python聊天室