I try to create simple echo server with zlib compression following this and this examples.
My idea is to send some string now because I can convert POD types to string (std::string(reinterpret_cast<const char *>(&pod), sizeof(pod))) before sending when I will be sure the transport layer works.
And there is a problem here. Client compresses data, sends it and says data were sended but the server is blocked on data reading. I cannot understand why it happens.
I tried to use operator<< with out.flush(), also I tried to use boost::iostreams::copy(). The result is the same. The code example is (I use the same source file for server and client depending on args):
#include <boost/iostreams/filtering_stream.hpp>
#include <boost/iostreams/filter/zlib.hpp>
#include <boost/iostreams/copy.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <sstream>
namespace ip = boost::asio::ip;
using ip::tcp;
const unsigned short port = 9999;
const char host[] = "127.0.0.1";
void receive()
{
boost::asio::io_context ctx;
tcp::endpoint ep(ip::address::from_string(host), port);
tcp::acceptor a(ctx, ep);
tcp::iostream stream;
a.accept(stream.socket());
std::stringstream buffer;
std::cout << "start session" << std::endl;
try
{
for (;;)
{
{
boost::iostreams::filtering_istream in;
in.push(boost::iostreams::zlib_decompressor());
in.push(stream);
std::cout << "start reading" << std::endl;
// looks like server is blocked here
boost::iostreams::copy(in, buffer);
}
std::cout << "data: " << buffer.str() << std::endl;
{
boost::iostreams::filtering_ostream out;
out.push(boost::iostreams::zlib_compressor());
out.push(stream);
boost::iostreams::copy(buffer, out);
}
std::cout << "Reply is sended" << std::endl;
}
}
catch(const boost::iostreams::zlib_error &e)
{
std::cerr << e.what() << e.error() << '\n';
stream.close();
}
}
void send(const std::string &data)
{
tcp::endpoint ep(ip::address::from_string(host), port);
tcp::iostream stream;
stream.connect(ep);
std::stringstream buffer;
buffer << data;
if (!stream)
{
std::cerr << "Cannot connect to " << host << ":" << port << std::endl;
return;
}
try
{
{
boost::iostreams::filtering_ostream out;
out.push(boost::iostreams::zlib_compressor());
out.push(stream);
out << buffer.str();
out.flush();
}
std::cout << "sended: " << data << std::endl;
buffer.str("");
{
boost::iostreams::filtering_istream in;
in.push(boost::iostreams::zlib_decompressor());
in.push(stream);
// looks like client is blocked here
boost::iostreams::copy(in, buffer);
}
std::cout << "result: " << buffer.str() << std::endl;
}
catch(const boost::iostreams::zlib_error &e)
{
std::cerr << e.what() << '\n';
}
}
int main(int argc, const char *argv[])
{
if (argc > 1 && argv[1] == std::string("sender"))
send("hello world");
else
receive();
return 0;
}
First I start server and then I start client. The following output is produced:
Server
$ ./example
# now it waits while client will be accepted
start session
start reading
Client
$ ./example sender
sended: hello world
The programs are blocked with the output above. I guess the server still waits for data from client and it does not know the client sent all what it had.
If I close client with Ctrl + C then the output is following:
$ ./example
# now it waits while client will be accepted
start session
start reading
# now it is blocked until I press Ctrl + C
data: hello world
Reply is sended
start reading
zlib error-5
and
$ ./example sender
sended: hello world
^C
I guess the zlib error-5 is because the server thinks the archive is incomplete.
The expected behavior is no blocking. The message must appear in the server program output when the client was started.
Why is the program blocked on reading? How can I fix it?
iostreams::copydoes just that: it copies stream.Compliments to your code. It's very readable :) It reminds me of this answer Reading and writing files with boost iostream socket. The main difference is that that answer sends a single compressed blob and closes.
You're "right" that the decompressor knows when one compressed block is complete, but it doesn't decide that another will not follow.
So you need to add framing. The traditional way is to pass a length out-of-band. I've implemented the changes while also reducing code duplication by using IO manipulators.
And
ZLIBmanipulatorThis one mainly encapsulates the code that you duplicated between the sender/receiver:
LengthPrefixedmanipulatorThis manipulator doesn't care what is being serialized, but will simply prefix it with the effective length on-the-wire:
We add a subtlety: by storing a reference or value depending on what we are constructed with we can also accept temporaries (like the ZLIB manipulator):
DEMO PROGRAM
Combining these two, you can write the sender/receiver simply as:
Indeed, it prints:
Complete Listing