NOTE: I am using the words "client" and "child" interchangeably in this post to refer to the process launched from the "server".
I am using boost::process::async_pipe to write the STDIN of a process that I launch using boost::process::child. Assume my server program looks something like this:
(This is not a working server-demo)
server.cpp
int main()
{
using namespace std::chrono_literals;
boost::process::async_pipe writePipe;
boost::process::child child { "client", boost::process::std_in < _writePipe };
std::vector<char> buffer;
buffer.resize(1024u * 1024u);
while (working)
{
auto length = 0u;
/*
do a bunch of work that takes a long time
and also determines `length`, in this case I'm
adding a sleep to simulate the time between
calls to `async_write()`
*/
std::this_thread::sleep_for(5s);
boost::asio::async_write(writePipe,
boost::asio::buffer(buffer.data(), length),
[&writePipe](boost::system::error_code, size_t)
{
// writePipe.close();
});
/*
I know that this code as-is would have issues with
synchronizing `buffer`, but for the purpose of this
question please ignore that
*/
}
}
Basically I have a buffer of memory in which I'm doing some work and every so often I want to send some binary data to the child process. My child process looks something like this:
child.cpp
#include <iostream>
#include <string_view>
void print_hex(const char* p, std::size_t size)
{
std::string_view input(p, size);
static const char* const lut = "0123456789ABCDEF";
size_t len = input.length();
std::string output;
output.reserve(2 * len);
for (size_t i = 0; i < len; ++i)
{
const unsigned char c = static_cast<const unsigned char>(input[i]);
// output.append("0x");
output.push_back(lut[c >> 4]);
output.push_back(lut[c & 15]);
output.append(" ");
}
if (output.size() > 0) output.pop_back();
std::cout << "HEX (" << size<< "): " << output << std::endl;
}
int main()
{
std::vector<char> buffer;
buffer.resize(BUFFER_SIZE);
bool done = false;
while (!done)
{
auto rdbuf = std::cin.rdbuf();
while (auto count = rdbuf->sgetn(buffer.data(), BUFFER_SIZE))
{
print_hex(buffer.data(), count);
}
}
}
With the writePipe.close()
commented out, I noticed that my child program never got any data until the server process was terminated. If I instead uncommented the call to close the pipe, then I was able to process the data only from the first time boost::asio::async_write()
was called.
EDIT:
Unfortunately the original answer from @sehe did not resolve the issue. I updated the server code a little bit to better illustrate the issue (and I resolved the reserve/resize issue).
However while looking around again, I read some language about sgetn()
that said:
The default definition of xsgetn in streambuf retrieves characters from the controlled input sequence and stores them in the array pointed by s, until either n characters have been extracted or the end of the sequence is reached.
So, I refactored my client to first ask the stream how many bytes were available, and then read the stream in chunks. Here was my first attempt:
bool done = false;
while (!done)
{
auto rdbuf = std::cin.rdbuf();
const auto available = rdbuf->in_avail();
if (available == 0)
{
continue;
}
auto bytesToRead = std::min(BUFFER_SIZE, static_cast<std::uint32_t>(available));
auto bytesRead = rdbuf->sgetn(buffer.data(), bytesToRead);
print_hex(buffer.data(), bytesRead);
while (bytesRead < available)
{
bytesToRead = std::min(BUFFER_SIZE, static_cast<std::uint32_t>(available - bytesRead));
bytesRead += rdbuf->sgetn(buffer.data(), bytesToRead);
print_hex(buffer.data(), bytesRead);
}
}
However, even after adding std::cin.sync_with_stdio(false);
(from the answer Why does in_avail() output zero even if the stream has some char?), the call to rdbuf->in_avail()
always returns 0
. Even if I try outside of my server and on the command line like: ls | client
I'd like my client program to read the data as it's coming in without having to (1) close the server process or (2) close the pipe (unless I can reopen the pipe to do a subsequent write
().
Thank you!