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



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

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

然而,我在运行程序以在2个进程之间连续发送消息时遇到了问题。只要检测到键盘输入,进程就会退出。

我怀疑我没有正确打开管道。(我的代码中没有打开的函数(。open函数还要求我输入管道的路径名,该路径名位于/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", getpid());
onServiceStart(chatter,
fileDescription2,       //chatter2 will use fileDescription2 to read,
fileDescription1);    //chatter2 will use fileDescription1 to write,
}
}

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

如果打算启动程序的两个实例,一个以0为参数调用,另一个以1调用,那么您需要使用一对命名管道在它们之间进行通信,尽管如果您想要双向通信,命名的UNIX套接字甚至更好,因为您只需要一个。

请注意,使用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()循环同时等待来自标准输入和管道的数据。

我切换到使用命名管道。未命名管道不能用于两个独立进程之间的通信,只能用于父进程和子进程。

由于管道是单向的,我不得不使用两个管道。对于每个进程,管道将被用作只读或只读。例如,进程0只将管道1打开为只读,将只调用read((函数来读取管道。

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

这是我的密码。

#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, '', 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;
}

最新更新