I have upgraded cpp-netlib from v0.11.0 to 0.13.0 and run into some difficulties.
Previously, when a request was sent to the server, the body of the request could be read from the request object.
The request body is now empty when I send the same request to a server using v0.13.0.
The rest of the request object appears to be correct - only the body is empty.
Is there something I need to do differently? I can't find any examples on the site that show how the body is extracted.
I have confirmed the same behaviour from the hello world example.
#include <boost/network/protocol/http/server.hpp>
#include <iostream>
namespace http = boost::network::http;
struct hello_world;
typedef http::server<hello_world> server;
struct hello_world
{
void operator()(const server::request &request, server::connection_ptr connection)
{
///////////////////////////////////
// request.body is empty
///////////////////////////////////
server::string_type ip = source(request);
unsigned int port = request.source_port;
std::ostringstream data;
data << "Hello, " << ip << ':' << port << '!';
connection->set_status(server::connection::ok);
connection->write(data.str());
}
};
int main(int argc, char *argv[]) {
try {
hello_world handler;
server::options options(handler);
server server_(options.address("192.168.0.19").port("9999"));
server_.run();
}
catch (std::exception &e) {
std::cerr << e.what() << std::endl;
return 1;
}
return 0;
}
Here is the request I'm sending:
curl -v -X POST http://192.168.0.19:9999/my-app/rest/foo/1.0/bar -H 'Content-Type: application/x-www-form-urlencoded' --data key=value
In older versions of cpp-netlib you could choose between a sync_server and a async_server class. Since version 0.12 only the async_server class is available. This class does not read body data of a POST request into request.body automatically, but requires the user to read the data in an asynchronous way using connection->read(callback).
Long story short, I've compiled a minimal echo server example that shows how to do this correctly. It also explains how to deal with the not well known Expect: 100-continue header that might be involved.
Please check out echo_async_server.cpp which has been added to the repo recently.
#include <vector>
#include <boost/config/warning_disable.hpp>
#include <boost/network/include/http/server.hpp>
#include <boost/network/utils/thread_pool.hpp>
#include <boost/range/algorithm/find_if.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
namespace net = boost::network;
namespace http = boost::network::http;
namespace utils = boost::network::utils;
struct async_hello_world;
typedef http::async_server<async_hello_world> server;
struct connection_handler : boost::enable_shared_from_this<connection_handler> {
connection_handler(server::request const &request)
:req(request), body("") {}
~connection_handler() {
std::cout << "connection_handler dctor called!" << std::endl;
}
void operator()(server::connection_ptr conn) {
int cl;
server::request::headers_container_type const &hs = req.headers;
for(server::request::headers_container_type::const_iterator it = hs.begin(); it!=hs.end(); ++it) {
if(boost::to_lower_copy(it->name)=="content-length") {
cl = boost::lexical_cast<int>(it->value);
break;
}
}
read_chunk(cl, conn);
}
void read_chunk(size_t left2read, server::connection_ptr conn) {
std::cout << "left2read: " << left2read << std::endl;
conn->read(
boost::bind(
&connection_handler::handle_post_read,
connection_handler::shared_from_this(),
_1, _2, _3, conn, left2read
)
);
}
void handle_post_read(
server::connection::input_range range, boost::system::error_code error, size_t size, server::connection_ptr conn, size_t left2read) {
if(!error) {
std::cout << "read size: " << size << std::endl;
body.append(boost::begin(range), size);
size_t left = left2read - size;
if(left>0) {
read_chunk(left, conn);
} else {
//std::cout << "FINISHED at " << body.size()<< std::endl;
}
}
std::cout << "error: " << error.message() << std::endl;
}
void handle_post_request(server::connection_ptr conn)
{
std::cout << "handle request..." << std::endl;
std::cout << "post size: " << body.size() << std::endl;
}
server::request const &req;
std::string body;
};
struct async_hello_world {
void operator()(server::request const &request, server::connection_ptr conn) {
boost::shared_ptr<connection_handler> h(new connection_handler(request));
(*h)(conn);
}
void error(boost::system::error_code const & ec) {
// do nothing here.
std::cout << "async error: " << ec.message() << std::endl;
}
};
int main(int argc, char * argv[]) {
utils::thread_pool thread_pool(4);
async_hello_world handler;
server instance("0.0.0.0", "1935", handler, thread_pool);
instance.run();
return 0;
}
You need to read manually the body. Now a connection_ptr object is used and a handler must be attached for doing the read.
So it must look something like this:
if (r.method == "POST") {
auto foundIt = std::find_if(r.headers.begin(), r.headers.end(),
[](auto const & h) { return h.name == "Content-Length"; });
if (foundIt == r.headers.end())
throw std::logic_error("No Content-Length header found in POST");
auto handleReadFunc = [this](auto &&... args) {
this->handleReadBody(args...);
};
//This attaches a callback to read the body
connection->read(handleReadFunc);
}
Related
I am trying to build a simple IPC protocol using Boost Asio where the server side will be sending a struct that contains a vector<uint8_t> to the client. I was suggested to use a scatter/gather IO approach, but I can't get it working, as it seems the client is only receiving part of the data it is expecting and it keeps waiting indefinitely for the rest of the data to arrive even though it should already be there.
This is what I have right now:
// File: client.cpp
#include <iostream>
#include <vector>
#include <boost/asio.hpp>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
int main(int argc, char *argv[])
{
ba::io_context io;
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1:
args = {args.at(0), "localhost", "6869"};
break;
case 2:
args = {args.at(0), args.at(1), "6869"};
break;
case 3:
args = {args.at(0), args.at(1), args.at(2)};
break;
default:
std::clog << "usage: " << args.at(0) << " [host = localhost] [port = 6869]" << std::endl;
return 1;
}
try {
propertiesPacket properties;
properties.val1 = 9;
properties.val2 = 45;
tcp::socket socket(io);
tcp::resolver resolver(io);
connect(socket, resolver.resolve(args.at(1), args.at(2)));
write(socket, ba::buffer(&properties, sizeof(properties)));
uint16_t responseSize {};
ba::read(socket, ba::buffer(&responseSize, sizeof(uint16_t)));
std::clog << "client responseSize: " << responseSize << std::endl;
processedData response {};
std::vector<ba::mutable_buffer> responseBuffers {
ba::buffer(&response.size, sizeof(uint16_t)),
ba::buffer(&response.values, responseSize - sizeof(uint8_t))
};
ba::read(socket, responseBuffers);
std::clog << response.serialize();
return 0;
} catch (std::exception &e) {
std::clog << e.what() << std::endl;
return 1;
}
}
// File: server.cpp
#include <vector>
#include <boost/asio.hpp>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using TCPSocket = tcp::socket;
class ServerConnection
: public std::enable_shared_from_this<ServerConnection>
{
public:
ServerConnection(TCPSocket socket)
: socket_(std::move(socket))
{ }
void start()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
doRead();
}
private:
void doRead()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
auto self(shared_from_this());
socket_.async_read_some(ba::buffer(&properties_, sizeof(properties_)),
[this, self](error_code ec, std::size_t length)
{
std::clog << "received " << length << std::endl;
if (!ec) {
processData();
std::vector<ba::const_buffer> msg {
ba::buffer(&filePacketSize_, sizeof(uint16_t)),
ba::buffer(&filePacket_.val, sizeof(filePacket_.val)),
ba::buffer(&filePacket_.values, sizeof(filePacket_.values))};
std::clog << "filePacketSize_: " << filePacketSize_ << std::endl;
ba::async_write(socket_, msg,
[this, self = shared_from_this()](error_code ec, std::size_t length)
{
std::clog << "written " << length << std::endl;
if (!ec) doRead();
});
}
});
}
void processData()
{
filePacket_.val = properties_.val1;
// Just for demonstration, we fill the vector with random values
std::random_device rd;
std::mt19937 re(rd()) ;
std::uniform_int_distribution<uint8_t> dist(0, 255);
for (size_t i {}; i < filePacket_.val; ++i) {
processedData.values.push_back(dist(re));
}
}
TCPSocket socket_;
propertiesPacket properties_;
processedData filePacket_;
uint16_t filePacketSize_;
};
class Server
{
public:
using IOContext = ba::io_context;
using TCPAcceptor = tcp::acceptor;
Server(IOContext& io, uint16_t port)
: socket_(io),
acceptor_(io, {tcp::v4(), port})
{
doAccept();
}
private:
void doAccept()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
acceptor_.async_accept(socket_,
[this](error_code ec)
{
if (!ec) {
std::clog << "Accepted " << socket_.remote_endpoint() << std::endl;
std::make_shared<ServerConnection>(std::move(socket_))->start();
doAccept();
}
else {
std::clog << "Accept " << ec.message() << std::endl;
}
});
}
TCPSocket socket_;
TCPAcceptor acceptor_;
};
int main(int argc, char* argv[])
{
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1:
args = {args.at(0), "6869"};
break;
case 2:
args = {args.at(0), args.at(1)};
break;
default:
std::clog << "usage: " << args.at(0) << " [port = 6869]" << std::endl;
return 1;
}
try {
ba::io_context io;
Server server(io, std::stoi(args.at(1)));
io.run();
} catch (std::exception &e) {
std::clog << e.what() << std::endl;
return 1;
}
return 0;
}
// File: ipc_common.hpp
#include <cstdint>
#include <vector>
#include <sstream>
#include <string>
struct propertiesPacket
{
uint8_t val1;
uint8_t val2;
};
struct processedData
{
uint8_t val;
std::vector<uint8_t> values;
std::string serialize()
{
std::stringstream sstream;
sstream << "val: " << (unsigned int)val << std::endl;
for (const auto &i : values)
{
sstream << i << " ";
}
sstream << std::endl;
return sstream.str();
}
};
What am I doing wrong?
The sample seems corrupted.
For one, args.at(3) and args.at(4) will by definition always throw, because by definition the switch statement earlier will always exit the client when there are more than 2 command line arguments (default:).
Secondly, the client read uses &response.size but no such member exists at all.
Thirdly, server processData uses a .val property of procesedData which isn't even a member (it's a type, likely should be filePacket_.val instead).
Fourthly, it assigns that from properties_.val; which ALSO doesn't exist at all (there's only val1 and val2).
Next up, rd isn't used to initialize the URBG (random engine, re). Instead it calls an unknown identifier named random_device(). Likely ought to be rd() instead.
Again, where it sais processedData.val you probably meant filePacket_.val
And where you write processedData.push_back(...) you probably meant to say filePacket_.values.push_back(...)...
There's a spurious ; behind void doAccept() in the Server
By contrast, the ; is missing after each struct definition in ipc_common.hpp
The processedData struct defines a serialize() method that is never used. It also uses a C-style cast where static_cast<unsigned>(val) would be safe.
Weirdly, the server "parses" args, and provides an optional default value BUT it never uses that. Instead, it uses argv[1] without checking argc at all. Oops.
That all aside, now comes the confusing part: how did you want the values to be written? This is not correct:
std::vector<ba::const_buffer> msg{
ba::buffer(&filePacketSize_, sizeof(uint16_t)),
ba::buffer(&filePacket_.val, sizeof(filePacket_.val)),
ba::buffer(&filePacket_.values, sizeof(filePacket_.values))};
values is a std::vector<> so you cannot hope to use it in a bitwise way. It'll just invoke Undefined Behaviour.
Besides, it's pretty unclear why filePacketSize_ is being written (it's never even assigned, or even initialized to a determinate value).
On the client side you read a responseSize as if one would be sent... Maybe you want to keep those two in sync.
Suggested Appraoch
I'd do away with the separate size value(s), since a vector already keeps track of that. I'd also make sure your processData doesn't always push_back because the vector would always keep growing.
I'd make a protocol that actually sends the message size before the message itself, and makes sure it's correct.
Let's also make the random data naturally printable (a..z) for simplicity:
void processData()
{
// Just for demonstration, we fill the vector with random characters
std::mt19937 re(std::random_device{}());
std::uniform_int_distribution<uint8_t> dist('a', 'z');
filePacket_.values.clear();
std::generate_n(back_inserter(filePacket_.values), properties_.val1,
[&] { return dist(re); });
}
Then in writing, let's do:
processData();
size_t length[] { filePacket_.values.size() };
std::vector<ba::const_buffer> msg{
ba::buffer(length),
ba::buffer(filePacket_.values)};
Note how, again, we avoid manually specifying any buffer sizes. Also, we let
the library figure out that values is a vector of POD elements and do the
math to convert the calculate the correct start address and buffer size for the
element data.
On the client side, we do the inverse:
size_t length = 0;
ba::read(socket, ba::buffer(&length, sizeof(length)));
response.values.resize(length);
ba::read(socket, ba::buffer(response.values));
(Here we can't avoid writing sizeof(length) without getting more clumsy than I'd like).
Full Demo
File ipc_common.hpp
// File: ipc_common.hpp
#include <cstdint>
#include <sstream>
#include <string>
#include <vector>
struct propertiesPacket {
uint8_t val1;
uint8_t val2;
};
struct processedData {
std::vector<uint8_t> values;
};
File server.cpp
#include <boost/asio.hpp>
#include <vector>
#include <iostream>
#include <random>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using TCPSocket = tcp::socket;
class ServerConnection : public std::enable_shared_from_this<ServerConnection> {
public:
ServerConnection(TCPSocket socket) : socket_(std::move(socket))
{
}
void start()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
doRead();
}
private:
void doRead()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
auto self(shared_from_this());
socket_.async_read_some(
ba::buffer(&properties_, sizeof(properties_)),
[this, self](error_code ec, std::size_t length) {
std::clog << "received " << length << std::endl;
if (!ec) {
processData();
size_t length[] { filePacket_.values.size() };
std::vector<ba::const_buffer> msg{
ba::buffer(length), ba::buffer(filePacket_.values)};
ba::async_write(socket_, msg,
[this, self = shared_from_this()](
error_code ec, std::size_t length) {
std::clog << "written " << length
<< std::endl;
if (!ec)
doRead();
});
}
});
}
void processData()
{
// Just for demonstration, we fill the vector with random characters
std::mt19937 re(std::random_device{}());
std::uniform_int_distribution<uint8_t> dist('a', 'z');
filePacket_.values.clear();
std::generate_n(back_inserter(filePacket_.values), properties_.val1,
[&] { return dist(re); });
}
TCPSocket socket_;
propertiesPacket properties_;
processedData filePacket_;
};
class Server {
public:
using IOContext = ba::io_context;
using TCPAcceptor = tcp::acceptor;
Server(IOContext& io, uint16_t port)
: socket_(io)
, acceptor_(io, {tcp::v4(), port})
{
doAccept();
}
private:
void doAccept()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
acceptor_.async_accept(socket_, [this](error_code ec) {
if (!ec) {
std::clog << "Accepted " << socket_.remote_endpoint() << std::endl;
std::make_shared<ServerConnection>(std::move(socket_))->start();
doAccept();
} else {
std::clog << "Accept " << ec.message() << std::endl;
}
});
}
TCPSocket socket_;
TCPAcceptor acceptor_;
};
int main(int argc, char* argv[])
{
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1: args.push_back("6869"); break;
case 2: break;
default:
std::clog << "usage: " << args.at(0) << " [port = 6869]" << std::endl;
return 1;
}
try {
ba::io_context io;
Server server(io, std::stoi(args.at(1)));
io.run();
} catch (std::exception const &e) {
std::clog << e.what() << std::endl;
return 1;
}
}
File client.cpp
#include <iostream>
#include <vector>
#include <boost/asio.hpp>
#include "ipc_common.hpp"
namespace ba = boost::asio;
using boost::asio::ip::tcp;
int main(int argc, char *argv[])
{
ba::io_context io;
std::vector<std::string> args(argv, argv + argc);
switch (args.size()) {
case 1: args.push_back("localhost"); [[fallthrough]];
case 2: args.push_back("6869"); [[fallthrough]];
case 3: args.push_back("42"); [[fallthrough]];
case 4: args.push_back("99"); [[fallthrough]];
case 5: break;
default:
std::clog << "usage: " << args.at(0)
<< " [host = localhost] [port = 6869] [val1=42] [val2=99]"
<< std::endl;
return 1;
}
try {
propertiesPacket properties;
properties.val1 = std::stoul(args.at(3));
properties.val2 = std::stoul(args.at(4));
tcp::socket socket(io);
tcp::resolver resolver(io);
connect(socket, resolver.resolve({args.at(1), args.at(2)}));
write(socket, ba::buffer(&properties, sizeof(properties)));
processedData response{};
{
size_t length = 0;
ba::read(socket, ba::buffer(&length, sizeof(length)));
response.values.resize(length);
}
std::clog << "client response size: " << response.values.size() << std::endl;
ba::read(socket, ba::buffer(response.values));
std::clog.write(reinterpret_cast<char const*>(response.values.data()),
response.values.size()) << "\n";
// return 0;
} catch (std::exception &e) {
std::clog << e.what() << std::endl;
return 1;
}
}
Demo output:
Portability
You should probably keep byte ordering in mind as well. You could consider using JSON or another Well Known serialization format.
I have a simple client /server application the code of which is mentioned below.
Please run the server in one shell and the client in another shell in linux.
First start the server and then the client.
When the server is done with it's work, it crashes with following exception:
terminate called after throwing an instance of 'std::system_error'
what(): Resource deadlock avoided
This happens from the line m_thread->join() from inside the function Service::HandleClient
I have no clue on what's going on.. Can someone please check the code.. I just want that the server application should also get closed correctly the way the client application got closed.
**Server Code : **
#include <boost/asio.hpp>
#include <thread>
#include <atomic>
#include <memory>
#include <iostream>
using namespace boost;
class Service {
public:
Service(){}
void StartHandligClient(
std::shared_ptr<asio::ip::tcp::socket> sock) {
m_thread.reset(new std::thread (([this, sock]() {
HandleClient(sock);
})) );
}
private:
void HandleClient(std::shared_ptr<asio::ip::tcp::socket> sock) {
while(1)
{
try {
asio::streambuf request;
std::cout << "Waiting to read \n";
asio::read_until(*sock.get(), request, '\n');
std::string s( (std::istreambuf_iterator<char>(&request)), std::istreambuf_iterator<char>() );
std::cout << "Server got : " << s << "\n";
// Emulate request processing.
int i = 0;
while (i != 1000000)
i++;
std::this_thread::sleep_for(
std::chrono::milliseconds(500));
// Sending response.
std::string response = "Response\n";
asio::write(*sock.get(), asio::buffer(response));
}
catch (system::system_error &e) {
boost::system::error_code ec = e.code();
if(ec == asio::error::eof)
{
std::cout << "Breaking loop \n";
break;
}
std::cout << "Error occured! Error code = "
<< e.code() << ". Message: "
<< e.what();
}
}
m_thread->join();
// Clean-up.
delete this;
}
std::unique_ptr<std::thread> m_thread;
};
class Acceptor {
public:
Acceptor(asio::io_service& ios, unsigned short port_num) :
m_ios(ios),
m_acceptor(m_ios,
asio::ip::tcp::endpoint(
asio::ip::address_v4::any(),
port_num))
{
m_acceptor.listen();
}
void Accept() {
std::cout << "Server Accept() \n" << std::flush;
std::shared_ptr<asio::ip::tcp::socket>
sock(new asio::ip::tcp::socket(m_ios));
std::cout << "BEFORE calling acceptor's accept function \n" << std::flush;
m_acceptor.accept(*sock.get());
std::cout << "AFTER calling acceptor's accept function \n" << std::flush;
(new Service)->StartHandligClient(sock);
}
void close()
{
std::cout << "Inside Acceptor.close() \n" << std::flush;
m_acceptor.close();
}
private:
asio::io_service& m_ios;
asio::ip::tcp::acceptor m_acceptor;
};
class Server {
public:
Server() : m_stop(false) {}
void Start(unsigned short port_num) {
m_thread.reset(new std::thread([this, port_num]() {
Run(port_num);
}));
}
void Stop() {
m_stop.store(true);
m_thread->join();
}
private:
void Run(unsigned short port_num) {
Acceptor acc(m_ios, port_num);
while (!m_stop.load()) {
std::cout << "Server accept\n" << std::flush;
acc.Accept();
}
acc.close();
}
std::unique_ptr<std::thread> m_thread;
std::atomic<bool> m_stop;
asio::io_service m_ios;
};
int main()
{
unsigned short port_num = 3333;
try {
Server srv;
srv.Start(port_num);
std::this_thread::sleep_for(std::chrono::seconds(4));
srv.Stop();
}
catch (system::system_error &e) {
std::cout << "Error occured! Error code = "
<< e.code() << ". Message: "
<< e.what();
}
return 0;
}
**Client Code : **
#include <boost/asio.hpp>
#include <iostream>
using namespace boost;
class SyncTCPClient {
public:
SyncTCPClient(const std::string& raw_ip_address,
unsigned short port_num) :
m_ep(asio::ip::address::from_string(raw_ip_address),
port_num),
m_sock(m_ios) {
m_sock.open(m_ep.protocol());
}
void connect() {
m_sock.connect(m_ep);
}
void close() {
m_sock.shutdown(
boost::asio::ip::tcp::socket::shutdown_both);
m_sock.close();
}
std::string emulateLongComputationOp(
unsigned int duration_sec) {
std::string request = "EMULATE_LONG_COMP_OP "
+ std::to_string(duration_sec)
+ "\n";
sendRequest(request);
return receiveResponse();
};
private:
void sendRequest(const std::string& request)
{
std::cout << "Inside sendRequest : " << request << "\n";
asio::write(m_sock, asio::buffer(request));
}
std::string receiveResponse() {
asio::streambuf buf;
asio::read_until(m_sock, buf, '\n');
std::istream input(&buf);
std::string response;
std::getline(input, response);
return response;
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
};
int main()
{
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 3333;
try {
SyncTCPClient client(raw_ip_address, port_num);
// Sync connect.
client.connect();
std::cout << "Sending request to the server... " << std::endl;
std::string response = client.emulateLongComputationOp(10);
std::cout << "Response received: " << response << std::endl;
sleep(2);
// Close the connection and free resources.
client.close();
}
catch (system::system_error &e) {
std::cout << "Error occured! Error code = " << e.code()
<< ". Message: " << e.what();
return e.code().value();
}
return 0;
}
#sehe .. can you run the code and let me know how to overcome the crash that I mentioned ? – Nishant Sharma
Actually, no I won't. The problem has already been analyzed: you can't join the current thread (it would deadlock).
But I can do something better:
Grabbing my crystal ball, I can guess you got this example from a particular book, named Boost.Asio C++ Network Programming Cookbook¹, around page 139.
I recognized it after a while when I added up all the code smells (delete this and m_stop.load() tipped me over the edge).
The good news is, I reviewed that code before:
ASIO example code closing socket before it should
You can probably profit from the particular comments I made there.
¹ from packtpub: https://www.packtpub.com/application-development/boostasio-c-network-programming-cookbook
I need a parallel synchronous TCP solution using ASIO. I'm trying to get the example code from these examples working: https://github.com/jvillasante/asio-network-programming-cookbook/tree/master/src (using the server in ch04: 02_Sync_parallel_tcp_server.cpp and the client in ch03: 01_Sync_tcp_client.cpp).
The only thing I changed is the logging to append to text files.
The problem is that while the server runs fine, the client dies after returning a single response from the server:
libc++abi.dylib: terminating with uncaught exception of type boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<boost::system::system_error> >: shutdown: Socket is not connected
Code for the server:
#include <boost/asio.hpp>
#include <atomic>
#include <memory>
#include <thread>
#include <iostream>
#include <fstream>
using namespace boost;
class Service {
public:
Service() = default;
void StartHandlingClient(std::shared_ptr<asio::ip::tcp::socket> sock) {
std::thread th{[this, sock]() { HandleClient(sock); }};
th.detach();
}
private:
void HandleClient(std::shared_ptr<asio::ip::tcp::socket> sock) {
try {
asio::streambuf request;
asio::read_until(*sock.get(), request, '\n');
std::istream is(&request);
std::string line;
std::getline(is, line);
std::ofstream log("logfile2.txt", std::ios_base::app | std::ios_base::out);
log << "Request: " << line << "\n" << std::flush;
// Emulate request processing.
int i = 0;
while (i != 1000000) i++;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// Sending response.
std::string response = "Response\n";
asio::write(*sock.get(), asio::buffer(response));
} catch (std::system_error& e) {
std::ofstream log("logfile1.txt", std::ios_base::app | std::ios_base::out);
log << "Error occurred! Error code = " << e.code().value() << ". Message: " << e.what() << "\n" << std::flush;
}
// Clean up
delete this;
}
};
class Acceptor {
public:
Acceptor(asio::io_service& ios, unsigned short port_num)
: m_ios{ios}, m_acceptor{m_ios, asio::ip::tcp::endpoint{asio::ip::address_v4::any(), port_num}} {
m_acceptor.listen();
}
void Accept() {
auto sock = std::make_shared<asio::ip::tcp::socket>(m_ios);
m_acceptor.accept(*sock.get());
(new Service)->StartHandlingClient(sock);
}
private:
asio::io_service& m_ios;
asio::ip::tcp::acceptor m_acceptor;
};
class Server {
public:
Server() : m_stop{false} {}
void Start(unsigned short port_num) {
m_thread.reset(new std::thread([this, port_num]() { Run(port_num); }));
}
void Stop() {
m_stop.store(true);
m_thread->join();
}
private:
void Run(unsigned short port_num) {
Acceptor acc{m_ios, port_num};
while (!m_stop.load()) {
acc.Accept();
}
}
private:
std::unique_ptr<std::thread> m_thread;
std::atomic<bool> m_stop;
asio::io_service m_ios;
};
int main() {
unsigned short port_num = 3333;
try {
Server srv;
srv.Start(port_num);
std::this_thread::sleep_for(std::chrono::seconds(60));
srv.Stop();
} catch (std::system_error& e) {
std::ofstream log("logfile1.txt", std::ios_base::app | std::ios_base::out);
log << "Error occurred! Error code = " << e.code().value() << ". Message: " << e.what() << "\n" << std::flush;
}
return 0;
}
Code for the client:
#include <boost/asio.hpp>
#include <iostream>
#include <fstream>
using namespace boost;
class SyncTCPClient {
public:
SyncTCPClient(const std::string& raw_ip_address, unsigned short port_num)
: m_ep(asio::ip::address::from_string(raw_ip_address), port_num), m_sock(m_ios) {
m_sock.open(m_ep.protocol());
}
~SyncTCPClient() { close(); }
void connect() { m_sock.connect(m_ep); }
std::string emulateLongComputationOp(unsigned int duration_sec) {
std::string request = "EMULATE_LONG_COMP_OP " + std::to_string(duration_sec) + "\n";
sendRequest(request);
return receiveResponse();
}
private:
void close() {
if (m_sock.is_open()) {
std::ofstream log("logfile1.txt", std::ios_base::app | std::ios_base::out);
log << "shutting down\n" << std::flush;
m_sock.shutdown(asio::ip::tcp::socket::shutdown_both);
log << "closing the socket\n" << std::flush;
m_sock.close();
log << "socket closed\n" << std::flush;
}
}
void sendRequest(const std::string& request) { asio::write(m_sock, asio::buffer(request)); }
std::string receiveResponse() {
asio::streambuf buf;
asio::read_until(m_sock, buf, '\n');
std::istream input(&buf);
std::string response;
std::getline(input, response);
return response;
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
};
int main() {
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 3333;
try {
SyncTCPClient client{raw_ip_address, port_num};
// Sync connect.
client.connect();
std::cout << "Sending request to the server...\n";
std::string response = client.emulateLongComputationOp(10);
std::cout << "Response received: " << response << "\n";
} catch (std::system_error& e) {
std::ofstream log("logfile1.txt", std::ios_base::app | std::ios_base::out);
log << "Error occurred! Error code = " << e.code().value() << ". Message: " << e.what() << "\n" << std::flush;
return e.code().value();
}
return 0;
}
I don't see a lot wrong, and I cannot reproduce the problem with the code shown.
Things I do see:
the thread procedure could be a static because it's stateless (delete this is a code smell)
the thread needn't be detached (using boost::thread_group::join_all would be much better)
you were writing to the same logfile from server as well as client; results are undefined
spelling .store() and .load() on an atomic<bool> is un-idiomatic
spelling out *sock.get() on any kind of smart pointer is unforgivably un-idiomatic
writing code().value() - swallowing the category - is a BAD thing to do, and e.what() is NOT the way to get the message (use e.code().message()).
If you need flush, you might as well use std::endl
There's really no reason to use a shared_ptr in c++14:
asio::ip::tcp::socket sock(m_ios);
m_acceptor.accept(sock);
std::thread([sock=std::move(sock)]() mutable { HandleClient(sock); }).detach();
In C++11 stick to:
auto sock = std::make_shared<asio::ip::tcp::socket>(m_ios);
m_acceptor.accept(*sock);
std::thread([sock] { HandleClient(*sock); }).detach();
This means HandleClient can just take a ip::tcp::socket& instead of a smart pointer.
INTEGRATING
Server.cpp
#include <atomic>
#include <boost/asio.hpp>
#include <fstream>
#include <iostream>
#include <memory>
#include <thread>
using namespace boost;
static void HandleClient(asio::ip::tcp::socket& sock) {
try {
asio::streambuf buf;
asio::read_until(sock, buf, '\n');
std::string request;
getline(std::istream(&buf), request);
std::ofstream log("server.log", std::ios_base::app | std::ios_base::out);
log << "Request: " << request << std::endl;
// Emulate request processing.
int i = 0;
while (i != 1000000)
i++;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// Sending response.
std::string response = "Response\n";
asio::write(sock, asio::buffer(response));
} catch (std::system_error &e) {
std::ofstream log("server.log", std::ios_base::app | std::ios_base::out);
log << e.what() << " " << e.code() << ": " << e.code().message() << std::endl;
}
}
class Acceptor {
public:
Acceptor(asio::io_service &ios, unsigned short port_num)
: m_ios{ ios }, m_acceptor{ m_ios, asio::ip::tcp::endpoint{ asio::ip::address_v4::any(), port_num } } {
m_acceptor.listen();
}
void Accept() {
auto sock = std::make_shared<asio::ip::tcp::socket>(m_ios);
m_acceptor.accept(*sock);
std::thread([sock] { HandleClient(*sock); }).detach();
}
private:
asio::io_service &m_ios;
asio::ip::tcp::acceptor m_acceptor;
};
class Server {
public:
Server() : m_stop{ false } {}
void Start(unsigned short port_num) {
m_thread.reset(new std::thread([this, port_num]() { Run(port_num); }));
}
void Stop() {
m_stop = true;
m_thread->join();
}
private:
void Run(unsigned short port_num) {
Acceptor acc{ m_ios, port_num };
while (!m_stop) {
acc.Accept();
}
}
private:
std::unique_ptr<std::thread> m_thread;
std::atomic<bool> m_stop;
asio::io_service m_ios;
};
int main() {
unsigned short port_num = 3333;
try {
Server srv;
srv.Start(port_num);
std::this_thread::sleep_for(std::chrono::seconds(60));
srv.Stop();
} catch (std::system_error &e) {
std::ofstream log("server.log", std::ios_base::app | std::ios_base::out);
log << e.what() << " " << e.code() << ": " << e.code().message() << std::endl;
}
}
Client.cpp
#include <boost/asio.hpp>
#include <fstream>
#include <iostream>
using namespace boost;
class SyncTCPClient {
public:
SyncTCPClient(const std::string &raw_ip_address, unsigned short port_num)
: m_ep(asio::ip::address::from_string(raw_ip_address), port_num), m_sock(m_ios) {
m_sock.open(m_ep.protocol());
}
~SyncTCPClient() { close(); }
void connect() { m_sock.connect(m_ep); }
std::string emulateLongComputationOp(unsigned int duration_sec) {
std::string request = "EMULATE_LONG_COMP_OP " + std::to_string(duration_sec) + "\n";
sendRequest(request);
return receiveResponse();
}
private:
void close() {
if (m_sock.is_open()) {
std::ofstream log("client.log", std::ios_base::app | std::ios_base::out);
log << "shutting down" << std::endl;
m_sock.shutdown(asio::ip::tcp::socket::shutdown_both);
log << "closing the socket" << std::endl;
m_sock.close();
log << "socket closed" << std::endl;
}
}
void sendRequest(const std::string &request) { asio::write(m_sock, asio::buffer(request)); }
std::string receiveResponse() {
asio::streambuf buf;
asio::read_until(m_sock, buf, '\n');
std::string response;
getline(std::istream(&buf), response);
return response;
}
private:
asio::io_service m_ios;
asio::ip::tcp::endpoint m_ep;
asio::ip::tcp::socket m_sock;
};
int main() {
const std::string raw_ip_address = "127.0.0.1";
const unsigned short port_num = 3333;
try {
SyncTCPClient client{ raw_ip_address, port_num };
// Sync connect.
client.connect();
std::cout << "Sending request to the server...\n";
std::string response = client.emulateLongComputationOp(10);
std::cout << "Response received: " << response << std::endl;
} catch (std::system_error &e) {
std::ofstream log("client.log", std::ios_base::app | std::ios_base::out);
log << e.what() << " " << e.code() << ": " << e.code().message() << std::endl;
return e.code().value();
}
}
I've started learning POCO C++ library and I'm stuck while trying to run 2 servers in the same application (so that they can use some common runtime variables). These are 2 different servers, one of them is TCP TimeServer and the other one is simple UDP EchoServer. The code:
#include "Poco/Net/TCPServer.h"
#include "Poco/Net/TCPServerConnection.h"
#include "Poco/Net/TCPServerConnectionFactory.h"
#include "Poco/Net/TCPServerParams.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/ServerSocket.h"
#include "Poco/Net/DatagramSocket.h"
#include "Poco/Timestamp.h"
#include "Poco/DateTimeFormatter.h"
#include "Poco/DateTimeFormat.h"
#include "Poco/Exception.h"
#include "Poco/Util/ServerApplication.h"
#include "Poco/Util/Option.h"
#include "Poco/Util/OptionSet.h"
#include "Poco/Util/HelpFormatter.h"
#include <iostream>
using Poco::Net::ServerSocket;
using Poco::Net::StreamSocket;
using Poco::Net::TCPServerConnection;
using Poco::Net::TCPServerConnectionFactory;
using Poco::Net::TCPServer;
using Poco::Timestamp;
using Poco::DateTimeFormatter;
using Poco::DateTimeFormat;
using Poco::Util::ServerApplication;
using Poco::Util::Application;
using Poco::Util::Option;
using Poco::Util::OptionSet;
using Poco::Util::HelpFormatter;
class TimeServerConnection : public TCPServerConnection
{
public:
TimeServerConnection(const StreamSocket& s, const std::string& format) :
TCPServerConnection(s),
_format(format)
{
}
void run()
{
Application& app = Application::instance();
bool isOpen = true;
Poco::Timespan timeOut(10, 0);
unsigned char incommingBuffer[1000];
app.logger().information("SYSLOG from " + this->socket().peerAddress().toString());
while (isOpen) {
if (socket().poll(timeOut, Poco::Net::Socket::SELECT_READ) == false) {
std::cout << "TIMEOUT!" << std::endl << std::flush;
} else {
int nBytes = -1;
try {
nBytes = socket().receiveBytes(incommingBuffer, sizeof(incommingBuffer));
std::cout << incommingBuffer << std::endl;
} catch (Poco::Exception& exc) {
std::cerr << "Network error: " << exc.displayText() << std::endl;
isOpen = false;
}
if (nBytes == 0) {
std::cout << "Client closes connection!" << std::endl << std::flush;
isOpen = false;
} else {
std::cout << "Receiving nBytes: " << nBytes << std::endl << std::flush;
}
}
}
try
{
Timestamp now;
std::string dt(DateTimeFormatter::format(now, _format));
dt.append("\r\n");
socket().sendBytes(dt.data(), (int)dt.length());
}
catch (Poco::Exception& exc)
{ app.logger().log(exc); }
}
private:
std::string _format;
};
class TimeServerConnectionFactory : public TCPServerConnectionFactory
{
public:
TimeServerConnectionFactory(const std::string& format) :
_format(format)
{
}
TCPServerConnection* createConnection(const StreamSocket& socket)
{ return new TimeServerConnection(socket, _format); }
private:
std::string _format;
};
class UDPServer : public Poco::Util::ServerApplication
{
public:
UDPServer(){}
~UDPServer(){}
protected:
void initialize(Application& self)
{
loadConfiguration(); // load default configuration files, if present
ServerApplication::initialize(self);
}
void uninitialize() { ServerApplication::uninitialize(); }
int main(const std::vector<std::string>& args)
{
unsigned short port = (unsigned short)config().getInt("udpport", 9002);
std::cout << "[UDP] Using port " << port << std::endl;
std::string format(config().getString("TimeServer.format", DateTimeFormat::ISO8601_FORMAT));
Poco::Net::SocketAddress socketaddress(Poco::Net::IPAddress(), 9001);
Poco::Net::DatagramSocket datagramsocket(socketaddress);
char buffer[1024]; // 1K byte
while (1) {
Poco::Net::SocketAddress sender;
int n = datagramsocket.receiveFrom(buffer, sizeof(buffer) - 1, sender);
buffer[n] = '\0';
std::cout << sender.toString() << ":" << buffer << std::endl;
}
return 0;
}
};
class TimeServer : public Poco::Util::ServerApplication
{
public:
TimeServer() : _helpRequested(false)
{
}
~TimeServer()
{
}
protected:
void initialize(Application& self)
{
loadConfiguration(); // load default configuration files, if present
ServerApplication::initialize(self);
}
void uninitialize()
{
ServerApplication::uninitialize();
}
void defineOptions(OptionSet& options)
{
ServerApplication::defineOptions(options);
options.addOption(
Option("help", "h", "display help information on command line arguments")
.required(false)
.repeatable(false));
}
void handleOption(const std::string& name, const std::string& value)
{
ServerApplication::handleOption(name, value);
if (name == "help")
_helpRequested = true;
}
void displayHelp()
{
HelpFormatter helpFormatter(options());
helpFormatter.setCommand(commandName());
helpFormatter.setUsage("OPTIONS");
helpFormatter.setHeader("A server application that serves the current date and time.");
helpFormatter.format(std::cout);
}
int main(const std::vector<std::string>& args)
{
if (_helpRequested)
{
displayHelp();
}
else
{
unsigned short port = (unsigned short)config().getInt("tcpport", 9911);
std::cout << "Using port " << port << std::endl;
std::string format(config().getString("TimeServer.format", DateTimeFormat::ISO8601_FORMAT));
ServerSocket svs(port);
TCPServer srv(new TimeServerConnectionFactory(format), svs);
srv.start();
std::cout << "Server started!\n";
waitForTerminationRequest();
srv.stop();
std::cout << "Server stopped!\n";
}
return Application::EXIT_OK;
}
private:
bool _helpRequested;
};
int main(int argc, char** argv)
{
TimeServer app;
UDPServer app2;
app.run(argc, argv);
app2.run(argc, argv);
}
In the end of code I have int main() method where I'm trying to run 2 servers. However I get assertion violation here. There is a similar question on StackOverflow, however boost is used there while I'm using plain C++, so that solution is not relevant to me.
How can I run simultaneously these 2 servers?
ServerApplication was not designed for multiple instances. What you should do is run one ServerApplication and launch TCPServer and UDPServer in that application.
Actually if you want to made like this (as you question), seperated both
tcp (a) class and
udp (b) class.
Call both in other class (c) and
define which one
(c) -> (a)
(c) -> (b)
u need to call first and when. So u need make condition and decision.
Note: give them space time before run to made poco breath. 😂
I translated the example from Programming in Lua by Roberto Ierusalimschy for downloading several files via HTTP using coroutines to C++ using boost::asio and stackful coroutines. Here is the code:
#include <iostream>
#include <chrono>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
using namespace std;
using namespace boost::asio;
io_service ioService;
void download(const string& host, const string& file, yield_context& yield)
{
clog << "Downloading " << host << file << " ..." << endl;
size_t fileSize = 0;
boost::system::error_code ec;
ip::tcp::resolver resolver(ioService);
ip::tcp::resolver::query query(host, "80");
auto it = resolver.async_resolve(query, yield[ec]);
ip::tcp::socket socket(ioService);
socket.async_connect(*it, yield[ec]);
ostringstream req;
req << "GET " << file << " HTTP/1.0\r\n\r\n";
write(socket, buffer(req.str()));
while (true)
{
char data[8192];
size_t bytesRead = socket.async_read_some(buffer(data), yield[ec]);
if (0 == bytesRead) break;
fileSize += bytesRead;
}
socket.shutdown(ip::tcp::socket::shutdown_both);
socket.close();
clog << file << " size: " << fileSize << endl;
}
int main()
{
auto timeBegin = chrono::high_resolution_clock::now();
vector<pair<string, string>> resources =
{
{"www.w3.org", "/TR/html401/html40.txt"},
{"www.w3.org", "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf"},
{"www.w3.org", "/TR/REC-html32.html"},
{"www.w3.org", "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt"},
};
for(const auto& res : resources)
{
spawn(ioService, [&res](yield_context yield)
{
download(res.first, res.second, yield);
});
}
ioService.run();
auto timeEnd = chrono::high_resolution_clock::now();
clog << "Time: " << chrono::duration_cast<chrono::milliseconds>(
timeEnd - timeBegin).count() << endl;
return 0;
}
Now I'm trying to translate the code to use stackless coroutines from boost::asio but the documentation is not enough for me to grok how to organize the code in such way to be able to do it. Can someone provide solution for this?
Here is a solution based on stackless coroutines as provided by Boost. Given that they are essentially a hack, I would not consider the solution particularly elegant. It could probably be done better with C++20, but I think that would be outside the scope of this question.
#include <functional>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/yield.hpp>
using boost::asio::async_write;
using boost::asio::buffer;
using boost::asio::error::eof;
using boost::system::error_code;
using std::placeholders::_1;
using std::placeholders::_2;
/**
* Stackless coroutine for downloading file from host.
*
* The lifetime of the object is limited to one () call. After that,
* the object will be copied and the old object is discarded. For this
* reason, the socket_ and resolver_ member are stored as shared_ptrs,
* so that they can live as long as there is a live copy. An alternative
* solution would be to manager these objects outside of the coroutine
* and to pass them here by reference.
*/
class downloader : boost::asio::coroutine {
using socket_t = boost::asio::ip::tcp::socket;
using resolver_t = boost::asio::ip::tcp::resolver;
public:
downloader(boost::asio::io_service &service, const std::string &host,
const std::string &file)
: socket_{std::make_shared<socket_t>(service)},
resolver_{std::make_shared<resolver_t>(service)}, file_{file},
host_{host} {}
void operator()(error_code ec = error_code(), std::size_t length = 0,
const resolver_t::results_type &results = {}) {
// Check if the last yield resulted in an error.
if (ec) {
if (ec != eof) {
throw boost::system::system_error{ec};
}
}
// Jump to after the previous yield.
reenter(this) {
yield {
resolver_t::query query{host_, "80"};
// Use bind to skip the length parameter not provided by async_resolve
auto result_func = std::bind(&downloader::operator(), this, _1, 0, _2);
resolver_->async_resolve(query, result_func);
}
yield socket_->async_connect(*results, *this);
yield {
std::ostringstream req;
req << "GET " << file_ << " HTTP/1.0\r\n\r\n";
async_write(*socket_, buffer(req.str()), *this);
}
while (true) {
yield {
char data[8192];
socket_->async_read_some(buffer(data), *this);
}
if (length == 0) {
break;
}
fileSize_ += length;
}
std::cout << file_ << " size: " << fileSize_ << std::endl;
socket_->shutdown(socket_t::shutdown_both);
socket_->close();
}
// Uncomment this to show progress and to demonstrace interleaving
// std::cout << file_ << " size: " << fileSize_ << std::endl;
}
private:
std::shared_ptr<socket_t> socket_;
std::shared_ptr<resolver_t> resolver_;
const std::string file_;
const std::string host_;
size_t fileSize_{};
};
int main() {
auto timeBegin = std::chrono::high_resolution_clock::now();
try {
boost::asio::io_service service;
std::vector<std::pair<std::string, std::string>> resources = {
{"www.w3.org", "/TR/html401/html40.txt"},
{"www.w3.org", "/TR/2002/REC-xhtml1-20020801/xhtml1.pdf"},
{"www.w3.org", "/TR/REC-html32.html"},
{"www.w3.org", "/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt"},
};
std::vector<downloader> downloaders{};
std::transform(resources.begin(), resources.end(),
std::back_inserter(downloaders), [&](auto &x) {
return downloader{service, x.first, x.second};
});
std::for_each(downloaders.begin(), downloaders.end(),
[](auto &dl) { dl(); });
service.run();
} catch (std::exception &e) {
std::cerr << "exception: " << e.what() << "\n";
}
auto timeEnd = std::chrono::high_resolution_clock::now();
std::cout << "Time: "
<< std::chrono::duration_cast<std::chrono::milliseconds>(timeEnd -
timeBegin)
.count()
<< std::endl;
return 0;
}
Compiled with Boost 1.72 and g++ -lboost_coroutine -lpthread test.cpp. Example output:
$ ./a.out
/TR/REC-html32.html size: 606
/TR/html401/html40.txt size: 629
/TR/2002/REC-xhtml1-20020801/xhtml1.pdf size: 115777
/TR/2000/REC-DOM-Level-2-Core-20001113/DOM2-Core.txt size: 229699
Time: 1644
The log line at the end of the () function can be uncommented to demonstrate the interleaving of the downloads.