Why is client getting EOF on async_write? - c++

I'm writing an asynchronous server using boost::asio. I changed boost HTTP server example to use only simple protobufs. Now client gets libc++abi.dylib: terminating with uncaught exception of type boost::wrapexcept<boost::system::system_error>: read: End of file error when server's trying to send message using async_write.
I have tried sync and async clients, different types of buffers but the problem is still there. All the things are going well until async_write.
void TConnection::DoRead() {
std::cout << "Reading data" << std::endl;
auto self(shared_from_this());
Socket.async_read_some(boost::asio::buffer(Buffer),
[this, self](boost::system::error_code ec, std::size_t bytes_transferred)
{
if (!ec) {
TRequestParser::ResultType result;
TMessage request;
request.Parse(Buffer.data(), bytes_transferred);
Reply = MasterPtr->HandleMessange(request);
std::cout << "Replying: " << Reply.Content << std::endl;
DoWrite();
} else if (ec != boost::asio::error::operation_aborted) {
ConnectionManager.Stop(shared_from_this());
}
});
}
void TConnection::DoWrite() {
std::cout << "Writing data" << std::endl;
auto self(shared_from_this());
std::cout << "Replying2: " << Reply.Content << std::endl;
Reply.ToStreamBuf(&Streambuf);
std::cout << "Now I'm gonna write" << std::endl;
std::cout << Streambuf.size() << std::endl;
boost::asio::async_write(Socket, Streambuf,
[this, self](boost::system::error_code ec, std::size_t)
{
std::cout << "Oh, handled" << std::endl;
if (!ec)
{
// Initiate graceful connection closure.
boost::system::error_code ignored_ec;
Socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both,
ignored_ec);
}
if (ec != boost::asio::error::operation_aborted)
{
ConnectionManager.Stop(shared_from_this());
}
});
}
What I'm doing wrong?

Related

boost beast Websocket Multi Request Server/Client is not really multi

I am writing a c++ project to forward clients websocket requests to server and return their message to each other as soon as each one, this code handles multi clients, but for connecting each client to server async_read stays on the first endpoint, therefore next endpoints however are connected but blocked for reading, by the way every thing is asynchronous.
// my server
class websocket_session : public std::enable_shared_from_this<websocket_session>
{
std::string response = "";
std::shared_ptr<websocket::stream<beast::tcp_stream>> ws_;
boost::beast::multi_buffer buffer_;
boost::beast::multi_buffer buffer_client;
std::string meth = "", endpoint = "", message = "", host = "", port = "";
shared_ptr<SocketClient> javaClient;
net::io_context ioc_client;
public:
// Take ownership of the socket
explicit websocket_session(tcp::socket &&socket)
{
ws_ = make_shared<websocket::stream<beast::tcp_stream>>(std::move(socket));
}
// Start the asynchronous accept operation
template <class Body, class Allocator>
void do_accept(http::request<Body, http::basic_fields<Allocator>> req)
{
stringstream temprequest;
temprequest << req.target();
endpoint = temprequest.str();
temprequest.str(std::string());
temprequest << req.method();
meth = temprequest.str();
std::cout << "\n\t websocket_session::" << __FUNCTION__ << "\n";
//Get IP and port in case endpoint is eligible
if (EndPointSelection::getWebsocketHost(endpoint, meth, req.version(), host, port) == false)
{
std::cerr << "\n\t Websocket is NOT Authorized!!\n";
return;
}
// Set suggested timeout settings for the websocket
ws_->set_option(websocket::stream_base::timeout::suggested(beast::role_type::server));
// Set a decorator to change the Server of the handshake
ws_->set_option(websocket::stream_base::decorator(
[](websocket::response_type &res) {
res.set(http::field::server,
std::string(BOOST_BEAST_VERSION_STRING) +
" advanced-server");
}));
// Accept the websocket handshake
ws_->async_accept(req, beast::bind_front_handler(&websocket_session::on_accept, shared_from_this()));
}
private:
void on_accept(beast::error_code ec)
{
if (ec)
fail(ec, "accept");
std::cout << "\n\t websocket_session::" << __FUNCTION__ << " Endpoint: " << endpoint << "\n";
javaClient = std::make_shared<SocketClient>(ioc_client);
javaClient->run(host.c_str(), port.c_str(), endpoint.c_str());
javaClient->setWebsocket(ws_);
ioc_client.run();
do_read();
}
void do_read()
{
std::cout << "\n\t websocket_session::" << __FUNCTION__ << "\n";
// Read message from client and keep into our buffer
ws_->async_read(buffer_, beast::bind_front_handler(&websocket_session::on_read, shared_from_this()));
}
void on_read(beast::error_code ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
std::cout << "\n\t websocket_session::" << __FUNCTION__ << "\n";
// This indicates that the websocket_session was closed
if (ec == websocket::error::closed)
return;
if (ec)
fail(ec, "read");
ws_->text(ws_->got_text());
if (ws_->got_text())
{
std::cout << "\n\t Received Message from Client: " << boost::beast::buffers_to_string(buffer_.data()) << "\n";
sleep(1);
}
ioc_client.stop();
//ws_->async_read(buffer_, beast::bind_front_handler(&websocket_session::on_read, shared_from_this()));
}
void on_write(beast::error_code ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
std::cout << "\n\t websocket_session::" << __FUNCTION__ << "\n";
if (ec)
fail(ec, "write");
// Clear the buffer
buffer_client.consume(buffer_client.size());
// Do another read
do_read();
}
};
//my client
void SocketClient::run(char const *host, char const *port, char const *endpoint)
{
// Save these for later
host_ = host;
endpoint_ = endpoint;
std::cout << "\n run Endpoint:" << endpoint_ << "\n";
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// Look up the domain name
resolver_.async_resolve(host, port, beast::bind_front_handler(&SocketClient::on_resolve, shared_from_this()));
}
void SocketClient::setWebsocket(std::shared_ptr<websocket::stream<beast::tcp_stream>> ws_cl)
{
ws_client = ws_cl;
};
void SocketClient::getBuffer(boost::beast::multi_buffer &buffer_client)
{
buffer_client = buffer_;
}
void SocketClient::writeBuffer(boost::beast::multi_buffer buffer_client)
{
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
ws_.write(buffer_client.data());
// ws_.async_write(buffer_client.data(), beast::bind_front_handler(&SocketClient::on_write, shared_from_this()));
//ws_.async_read(buffer_, beast::bind_front_handler(&SocketClient::on_read, shared_from_this()));
}
void SocketClient::on_resolve(beast::error_code ec, tcp::resolver::results_type results)
{
if (ec)
fail(ec, "SocketClient::on_resolve Error: ");
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// Set the timeout for the operation
beast::get_lowest_layer(ws_).expires_after(std::chrono::seconds(30));
// Make the connection on the IP address we get from a lookup
beast::get_lowest_layer(ws_).async_connect(results, beast::bind_front_handler(&SocketClient::on_connect, shared_from_this()));
}
void SocketClient::on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type ep)
{
if (ec)
fail(ec, "connect");
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// Turn off the timeout on the tcp_stream, because
// the websocket stream has its own timeout system.
beast::get_lowest_layer(ws_).expires_never();
// Set suggested timeout settings for the websocket
ws_.set_option(websocket::stream_base::timeout::suggested(beast::role_type::client));
// Set a decorator to change the User-Agent of the handshake
ws_.set_option(websocket::stream_base::decorator(
[](websocket::request_type &req) {
req.set(http::field::user_agent,
std::string(BOOST_BEAST_VERSION_STRING) +
" websocket-client-async");
}));
// Update the host_ string. This will provide the value of the
// Host HTTP header during the WebSocket handshake.
// See https://tools.ietf.org/html/rfc7230#section-5.4
host_ += ':' + std::to_string(ep.port());
// Perform the websocket handshake
ws_.async_handshake(host_, endpoint_,
beast::bind_front_handler(
&SocketClient::on_handshake,
shared_from_this()));
}
void SocketClient::on_handshake(beast::error_code ec)
{
if (ec)
fail(ec, "SocketClient::on_handshake Error: ");
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
ws_.async_read(buffer_, beast::bind_front_handler(&SocketClient::on_read, shared_from_this()));
}
void SocketClient::on_write(beast::error_code ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
buffer_.consume(buffer_.size());
if (ec)
fail(ec, "SocketClient::on_write Error: ");
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// Read a message from server and keep it into our buffer
ws_.async_read(buffer_, beast::bind_front_handler(&SocketClient::on_read, shared_from_this()));
}
void SocketClient::do_read()
{
ws_.async_read(buffer_, beast::bind_front_handler(&SocketClient::on_read, shared_from_this()));
}
void SocketClient::on_read(beast::error_code ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
std::cout << "\nHi from SocketClient::" << __FUNCTION__ << "\n";
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
response_.append(boost::beast::buffers_to_string(buffer_.data())) ;
if (ec)
{
fail(ec, "SocketClient::on_read Error: ");
return;
}
if (response_.empty() == false)
{
std::cout << "\nServer: " << response_ << "\t Size: " << buffer_.size() << "\n";
ws_client->write(net::buffer(response_));
response_="";
}
buffer_.consume(buffer_.size());
sleep(1);
if (ws_client->got_text())
{
std::cout << "\n CLIENT Message\n ";
ws_.write(buffer_.data());
}
ws_.async_read(buffer_, beast::bind_front_handler(&SocketClient::on_read, shared_from_this()));
}
void SocketClient::close_connection()
{
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// Close the WebSocket connection
ws_.async_close(websocket::close_code::normal, beast::bind_front_handler(&SocketClient::on_close, shared_from_this()));
};
void SocketClient::on_close(beast::error_code ec)
{
if (ec)
fail(ec, "SocketClient::on_close Error: ");
std::cout << "\nSocketClient::" << __FUNCTION__ << "\n";
// If we get here then the connection is closed gracefully
// The make_printable() function helps print a ConstBufferSequence
std::cout << beast::make_printable(buffer_.data()) << std::endl;
}
It took me a while to understand what your code is doing - as you left some very relevant portions of your code out. And it appears you've got some boiler plate code that is effectively dead code. The bottom line is this:
I think you need to remove the ioc_client from websocket_session. Introducing a layered set of io_context instances is going to block subsequent connections and just introduce other issues.
I suspect you are trying to create your own instance of io_context because you don't have access to the io_context that's in the run state several layers down your call stack. But I think can get a reference to it.
From the original socket passed into websocket_session, invoke socket.get_executor() (or is it socket.get_executor.context() ?) to get the execution context. That will enable you to create your socket in SocketClient. Don't invoke run on it. It's already in the run state. If that doesn't work, find a way to get the original io_context passed up to your websocket_session.

Unexpected invocation of the destructor after an invokation of a product of std::bind

There is a simple example with making use of boost::asio::io_context
https://github.com/unegare/boost-ex/blob/500e46f4d3b41e2abe48e2deccfab39d44ae94e0/main.cpp
#include <boost/asio.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <thread>
#include <vector>
#include <memory>
#include <mutex>
#include <chrono>
#include <iostream>
#include <exception>
std::mutex m_stdout;
class MyWorker {
std::shared_ptr<boost::asio::io_context> io_context;
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
public:
MyWorker(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard):
io_context(_io_context), work_guard(_work_guard) {}
MyWorker(const MyWorker &mw): io_context(mw.io_context), work_guard(mw.work_guard) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker copy constructor" << std::endl;
m_stdout.unlock();
}
MyWorker(MyWorker &&mw): io_context(std::move(mw.io_context)), work_guard(std::move(mw.work_guard)) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker move constructor" << std::endl;
m_stdout.unlock();
}
~MyWorker() {}
void operator() () {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Thread Start" << std::endl;
m_stdout.unlock();
while(true) {
try {
boost::system::error_code ec;
io_context->run(ec);
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker: received an error: " << ec << std::endl;
m_stdout.unlock();
continue;
}
break;
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
}
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Thread Finish" << std::endl;
m_stdout.unlock();
}
};
class Client: public std::enable_shared_from_this<Client> {
std::shared_ptr<boost::asio::io_context> io_context;
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
std::shared_ptr<boost::asio::ip::tcp::socket> sock;
std::shared_ptr<std::array<char, 512>> buff;
public:
Client(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard, std::shared_ptr<boost::asio::ip::tcp::socket> &_sock):
io_context(_io_context), work_guard(_work_guard), sock(_sock) {
buff = std::make_shared<std::array<char,512>>();
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " with args" << std::endl;
m_stdout.unlock();
}
Client(const Client &cl): io_context(cl.io_context), work_guard(cl.work_guard), sock(cl.sock), buff(cl.buff) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " copy" << std::endl;
m_stdout.unlock();
}
Client(Client &&cl): io_context(std::move(cl.io_context)), work_guard(std::move(cl.work_guard)), sock(std::move(cl.sock)), buff(std::move(cl.buff)) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " move" << std::endl;
m_stdout.unlock();
}
~Client() {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " buff.use_count: " << buff.use_count() << " | sock.use_count: " << sock.use_count() << " | io_context.use_count: " << io_context.use_count() << std::endl;
m_stdout.unlock();
}
void OnConnect(const boost::system::error_code &ec) {
std::cout << __FUNCTION__ << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
// buff = std::make_shared<std::array<char, 512>>();
char req[] = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
memcpy(buff->data(), req, strlen(req));
m_stdout.lock();
std::cout << req << std::endl;
m_stdout.unlock();
sock->async_write_some(boost::asio::buffer(buff->data(), strlen(buff->data())), std::bind(std::mem_fn(&Client::OnSend), this, std::placeholders::_1, std::placeholders::_2));
}
std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
}
void OnSend(const boost::system::error_code &ec, std::size_t bytes_transferred) {
std::cout << __FUNCTION__ << " use_count: " << io_context.use_count() << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
buff->fill(0);
std::cout << __FUNCTION__ << std::endl;
sock->async_read_some(boost::asio::buffer(buff->data(), buff->size()), std::bind(std::mem_fn(&Client::OnRecv), this, std::placeholders::_1, std::placeholders::_2));
}
}
void OnRecv(const boost::system::error_code &ec, std::size_t bytes_transferred) {
std::cout << __FUNCTION__ << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
m_stdout.lock();
std::cout << buff->data() << std::endl;
m_stdout.unlock();
}
}
};
int main () {
std::shared_ptr<boost::asio::io_context> io_context(std::make_shared<boost::asio::io_context>());
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard(
std::make_shared<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> (boost::asio::make_work_guard(*io_context))
);
MyWorker mw(io_context, work_guard);
std::vector<std::thread> vth;
vth.reserve(1);
for (int i = 1; i > 0; --i) {
vth.emplace_back(mw);
}
std::shared_ptr<Client> cl = 0;
try {
boost::asio::ip::tcp::resolver resolver(*io_context);
boost::asio::ip::tcp::resolver::query query("unegare.info", "80");
boost::asio::ip::tcp::endpoint ep = *resolver.resolve(query);
m_stdout.lock();
std::cout << "ep: " << ep << std::endl;
m_stdout.unlock();
std::shared_ptr<boost::asio::ip::tcp::socket> sock(std::make_shared<boost::asio::ip::tcp::socket>(*io_context));
std::shared_ptr<Client> cl2(std::make_shared<Client>(io_context, work_guard, sock));
cl = cl2->shared_from_this();
m_stdout.lock();
std::cout << "HERE: use_count = " << cl.use_count() << std::endl;
m_stdout.unlock();
sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));
std::this_thread::sleep_for(std::chrono::duration<double>(1));
m_stdout.lock();
std::cout << "AFTER CALL" << std::endl;
m_stdout.unlock();
// asm volatile ("");
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
try {
char t;
std::cin >> t;
work_guard->reset();
// std::this_thread::sleep_for(std::chrono::duration<double>(1));
// std::cout << "Running" << std::endl;
// io_context->run();
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
std::for_each(vth.begin(), vth.end(), std::mem_fn(&std::thread::join));
return 0;
}
stdout:
[140487203505984] MyWorker copy constructor
[140487203505984] MyWorker move constructor
[140487185372928] Thread Start
ep: 95.165.130.37:80
[140487203505984] Client with args
HERE: use_count = 2
[140487203505984] Client copy
[140487203505984] Client move
[140487203505984] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
[140487185372928] Client move
[140487185372928] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
OnConnect
GET / HTTP/1.1
Host: unegare.info
OnConnect use_count: 2
[140487185372928] ~Client buff.use_count: 2 | sock.use_count: 3 | io_context.use_count: 5
Segmentation Fault (core dumped)
But there is a little problem with the understanding of the segfault caused by the bad reference to the Client object.
But I do not understand why cl2 becomes destructed after the call of
sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));
at the 162 line.
As well as ... Why was there an invocation of the copy constructor?
as it may be noticed from the stdout above.
It's good that you try to understand. However, start simple!
your copy constructor fails to call the base-class copy constructor (!!) oops
your binds bind to
this (which does NOT keep the shared pointer alive)
*cl2->shared_from_this() - oops this binds to a COPY of *cl2 by value¹. That is obviously the reason why that COPY is destructed when you're done
The invalid reference arose from the combination of 1. and 2.b.
I'd suggest to simplify. A lot!
Use Rule Of Zero (only declare special members when you need. In this, you introduced a bug because you did something wrong manually writing a copy-constructor that you didn't need)
Use non-owning references where appropriate (not everything needs to be a smart pointer)
Prefer std::unique_ptr for things that do not require shared ownership (shared ownership should be really rare)
Prefer std::lock_guard instead of manually locking and unlocking (that's not exception safe)
Many others: work_guard doesn't need to be copied, it has a reset() member already, catch by const-reference, if you're gonna catch, don't need to use error_code on io_context::run, check the end-point iterator of resolve before dereference, use boost::asio::connect instead so you don't have to check and iterate over different endpoints etc.
prefer std::string over fixed-size buffers if you're doing dynamic allocation anyways, use non-implementation defined chrono durations (for example 1.0s, not duration<double>(1)), consider using boost::thread_group instead of vector<std::thread> or at least only joining threads that are joinable() etc
Suggest to make that async_connect part of Client, since then you can simply bind members the same way as all the rest of the binds (using shared_from_this(), instead of writing the bug you had)
If you have time left, consider using Beast for the Http request and response creation/parsing
Just as a reference, this is what MyWorker could be:
class MyWorker {
ba::io_context& io_context;
public:
MyWorker(ba::io_context& _io_context) : io_context(_io_context) {}
//// best is to not mention these at all, because even `default`ing can change what compiler generates
//MyWorker(const MyWorker& mw) = default;
//MyWorker(MyWorker&& mw) = default;
//~MyWorker() = default;
void operator()() {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
}
};
At this point, you could very well just make it a lambda:
auto mw = [&io_context] {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
};
Much simpler.
The Full Code Simplified
Just see e.g. the simplicity of the new connection code:
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
[self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}
The endpoint is printed when OnConnect is called.
Live On Coliru²
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
namespace ba = boost::asio;
using ba::ip::tcp;
using namespace std::literals;
static std::mutex s_stdout;
#define TRACE(expr) { \
std::lock_guard<std::mutex> lk(s_stdout); \
std::cout << "[" << std::this_thread::get_id() << "] " << expr << std::endl; \
}
class Client : public std::enable_shared_from_this<Client> {
ba::io_context& io_context;
tcp::socket sock;
std::string buf;
public:
Client(ba::io_context& _io_context) : io_context(_io_context), sock{io_context}
{ }
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
std::bind(std::mem_fn(&Client::OnConnect), shared_from_this(), std::placeholders::_1));
}
void OnConnect(const boost::system::error_code& ec) {
TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message());
} else {
buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
TRACE(std::quoted(buf));
sock.async_write_some(ba::buffer(buf),
std::bind(std::mem_fn(&Client::OnSend), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
}
void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
TRACE(__FUNCTION__);
buf.assign(512, '\0');
sock.async_read_some(ba::buffer(buf), std::bind(std::mem_fn(&Client::OnRecv), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
}
void OnRecv(const boost::system::error_code& ec, std::size_t bytes_transferred) {
TRACE(__FUNCTION__);
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
buf.resize(bytes_transferred);
TRACE(std::quoted(buf));
}
}
};
int main() {
ba::io_context io_context;
auto work_guard = make_work_guard(io_context);
boost::thread_group vth;
auto mw = [&io_context] {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
};
vth.create_thread(mw);
try {
std::make_shared<Client>(io_context)->Start();
char t;
std::cin >> t;
work_guard.reset();
} catch (std::exception const& ex) {
TRACE("Main Thread: caught an exception: " << ex.what());
}
vth.join_all();
}
Prints:
[140095938852608] Thread Start
[140095938852608] OnConnect ep:95.165.130.37:80
[140095938852608] "GET / HTTP/1.1
Host: unegare.info
"
[140095938852608] OnSend
[140095938852608] OnRecv
[140095938852608] "HTTP/1.1 200 OK
Date: Sun, 22 Dec 2019 22:26:56 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Sun, 10 Mar 2019 10:17:38 GMT
ETag: \"37-583bac3f3c843\"
Accept-Ranges: bytes
Content-Length: 55
Content-Type: text/html
<html>
<head>
</head>
<body>
It works.
</body>
</html>
"
q
[140095938852608] Thread Finish
BONUS
std::bind is obsolete since C++11, consider using lambdas instead. Since Coliru didn't want to co-operate any more, I'll just post the three changed functions in full:
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
[self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}
void OnConnect(const boost::system::error_code& ec) {
TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message());
} else {
buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
TRACE(std::quoted(buf));
sock.async_write_some(ba::buffer(buf),
[self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnSend(ec, bytes_transferred); });
}
}
void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
TRACE(__FUNCTION__);
buf.assign(512, '\0');
sock.async_read_some(ba::buffer(buf),
[self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnRecv(ec, bytes_transferred); });
}
}
¹ Does boost::bind() copy parameters by reference or by value?
² Coliru doesn't allow network access
You don’t track destruction of cl2 with you output: cl2 is std::shared_ptr<Client>. You seem to track construction of Client objects.
You problem is the * in front of cl2->make_shared_from_this(): that will dereference the std::shared_ptr<Client>. The bind() expression sees a Client& and captures a Client by copy. Removing the * should fix the problem. I haven’t fully understood was code is trying to do (I’m reading it on a phone) but I guess you actually want to capture the std::shared_ptr<Client> rather than the Client.
Also, as cl2 is already a std::shared_ptr<Client> there is no point in calling make_shared_from_this() on the pointed to object. It just recreates an unnecessary st::shared_ptr<Client>.

Telnet server always returns garbage when connecting to it using Boost Asio

So I am trying to make a telnet client that connects to some address part for work and part for Boost::Asio learning purpose.
My small project has three handlers:
Resolve handler:
void resolverHandler(const boost::system::error_code& ec, ip::tcp::resolver::iterator iter) {
if (ec) {
cout << "Resolved failed with message: " << ec.message() << endl;
}
else {
ip::tcp::endpoint endpoint = *iter;
cout << "Connection to: " << endpoint.address() << ":" << endpoint.port() << endl;
tcpSocket.async_connect(endpoint, connectHandler);
}
}
Connect handler
void connectHandler(const boost::system::error_code& ec) {
if (ec) {
cout << "Connect failed with message: " << ec.message() << endl;
}
else {
cout << "Connection established" << endl;
tcpSocket.async_read_some(buffer(_data), readHandler);
}
}
Read handler:
void readHandler(const boost::system::error_code& ec, size_t amountOfBytes) {
if (ec) {
cout << "Read failed with message: " << ec.message() << endl;
}
else {
cout << amountOfBytes << endl;
cout << _data.data() << endl;
tcpSocket.async_read_some(buffer(_data), readHandler);
}
}
And this is my main function:
io_service ioservice;
ip::tcp::resolver resolver(ioservice);
ip::tcp::socket tcpSocket(ioservice);
array<char, 16> _data;
ip::tcp::resolver::query query("192.168.200.200", "23");
int main() {
resolver.async_resolve(query, resolverHandler);
ioservice.run();
return 0;
}
But I always get garbage like this:
Connection to: 192.168.206.226:23
Connection established
15
 ² ²  ²# ²' ²$
I admit that I am new to telnet, but I am not sure why do I get this response ? Not sure if I need to null terminate the data that I receive before printing it, but even like that I have the same response.
Here is the normal response I should receive - tried with Windows Telnet:
Welcome Message (localhost) (ttyp0)
login:
Apreciate if someone has any ideas on what to do.

Boost.Asio/OpenSSL HTTPS GET certificate trouble

I am using Boost Asio and my goal is to send an HTTPS GET request to www.realmofthemadgod.com. I found some code on GitHub claiming to do that which I've included below but first I'll go through some observations:
The code fails with an error sslv3 alert handshake failure.
On command line, the command openssl s_client -connect www.realmofthemadgod.com:443 results in the same error and some other messages about no certificates being available
However, the command openssl s_client -connect www.realmofthemadgod.com:443 -servername www.realmofthemadgod.com does find the correct certificate!
Now, the problem is how do I achieve what the -servername switch does, in code.
What I have so far:
#include <iostream>
#include <istream>
#include <ostream>
#include <string>
#pragma comment(lib, "libcryptoMD.lib")
#pragma comment(lib, "libsslMD.lib")
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
using boost::asio::ip::tcp;
class client
{
public:
client(boost::asio::io_service& io_service, boost::asio::ssl::context& context, const std::string& server, const std::string& path)
: resolver_(io_service), socket_(io_service, context)
{
// Form the request. We specify the "Connection: close" header so that the
// server will close the socket after transmitting the response. This will
// allow us to treat all data up until the EOF as the content.
std::ostream request_stream(&request_);
request_stream << "GET " << path << " HTTP/1.1\r\n";
request_stream << "Host: " << server << "\r\n";
request_stream << "Accept: */*\r\n";
request_stream << "Connection: close\r\n\r\n";
// Start an asynchronous resolve to translate the server and service names
// into a list of endpoints.
tcp::resolver::query query(server, "https");
resolver_.async_resolve(query,
boost::bind(&client::handle_resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
}
private:
void handle_resolve(const boost::system::error_code& err,
tcp::resolver::iterator endpoint_iterator)
{
if (!err)
{
std::cout << "Resolve OK" << "\n";
socket_.set_verify_mode(boost::asio::ssl::verify_peer);
//socket_.set_verify_mode(boost::asio::ssl::verify_none);
socket_.set_verify_callback(
boost::bind(&client::verify_certificate, this, _1, _2));
boost::asio::async_connect(socket_.lowest_layer(), endpoint_iterator,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Error resolve: " << err.message() << "\n";
}
}
bool verify_certificate(bool preverified,
boost::asio::ssl::verify_context& ctx)
{
// The verify callback can be used to check whether the certificate that is
// being presented is valid for the peer. For example, RFC 2818 describes
// the steps involved in doing this for HTTPS. Consult the OpenSSL
// documentation for more details. Note that the callback is called once
// for each certificate in the certificate chain, starting from the root
// certificate authority.
// In this example we will simply print the certificate's subject name.
char subject_name[256];
X509* cert = X509_STORE_CTX_get_current_cert(ctx.native_handle());
X509_NAME_oneline(X509_get_subject_name(cert), subject_name, 256);
std::cout << "Verifying " << subject_name << "\n";
return preverified;
}
void handle_connect(const boost::system::error_code& err)
{
if (!err)
{
std::cout << "Connect OK " << "\n";
socket_.async_handshake(boost::asio::ssl::stream_base::client,
boost::bind(&client::handle_handshake, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Connect failed: " << err.message() << "\n";
}
}
void handle_handshake(const boost::system::error_code& error)
{
if (!error)
{
std::cout << "Handshake OK " << "\n";
std::cout << "Request: " << "\n";
const char* header = boost::asio::buffer_cast<const char*>(request_.data());
std::cout << header << "\n";
// The handshake was successful. Send the request.
boost::asio::async_write(socket_, request_,
boost::bind(&client::handle_write_request, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Handshake failed: " << error.message() << "\n";
}
}
void handle_write_request(const boost::system::error_code& err)
{
if (!err)
{
// Read the response status line. The response_ streambuf will
// automatically grow to accommodate the entire line. The growth may be
// limited by passing a maximum size to the streambuf constructor.
boost::asio::async_read_until(socket_, response_, "\r\n",
boost::bind(&client::handle_read_status_line, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Error write req: " << err.message() << "\n";
}
}
void handle_read_status_line(const boost::system::error_code& err)
{
if (!err)
{
// Check that response is OK.
std::istream response_stream(&response_);
std::string http_version;
response_stream >> http_version;
unsigned int status_code;
response_stream >> status_code;
std::string status_message;
std::getline(response_stream, status_message);
if (!response_stream || http_version.substr(0, 5) != "HTTP/")
{
std::cout << "Invalid response\n";
return;
}
if (status_code != 200)
{
std::cout << "Response returned with status code ";
std::cout << status_code << "\n";
return;
}
std::cout << "Status code: " << status_code << "\n";
// Read the response headers, which are terminated by a blank line.
boost::asio::async_read_until(socket_, response_, "\r\n\r\n",
boost::bind(&client::handle_read_headers, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Error: " << err.message() << "\n";
}
}
void handle_read_headers(const boost::system::error_code& err)
{
if (!err)
{
// Process the response headers.
std::istream response_stream(&response_);
std::string header;
while (std::getline(response_stream, header) && header != "\r")
std::cout << header << "\n";
std::cout << "\n";
// Write whatever content we already have to output.
if (response_.size() > 0)
std::cout << &response_;
// Start reading remaining data until EOF.
boost::asio::async_read(socket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&client::handle_read_content, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Error: " << err << "\n";
}
}
void handle_read_content(const boost::system::error_code& err)
{
if (!err)
{
// Write all of the data that has been read so far.
std::cout << &response_;
// Continue reading remaining data until EOF.
boost::asio::async_read(socket_, response_,
boost::asio::transfer_at_least(1),
boost::bind(&client::handle_read_content, this,
boost::asio::placeholders::error));
}
else if (err != boost::asio::error::eof)
{
std::cout << "Error: " << err << "\n";
}
}
tcp::resolver resolver_;
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
boost::asio::streambuf request_;
boost::asio::streambuf response_;
};
int main(int argc, char* argv[])
{
try
{
//boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12);
ctx.set_default_verify_paths();
boost::asio::io_service io_service;
client c(io_service, ctx, "www.realmofthemadgod.com", "/");
io_service.run();
}
catch (std::exception& e)
{
std::cout << "Exception: " << e.what() << "\n";
}
return 0;
}
I did some digging and learned that the switch -servername enables something called server name indication (SNI) and I found a way to enable it in code.
This is added to the client constructor:
// Set SNI Hostname (many hosts need this to handshake successfully)
if (!SSL_set_tlsext_host_name(stream.native_handle(), host))
{
boost::system::error_code ec((int)ERR_get_error(), boost::asio::error::get_ssl_category());
throw boost::system::system_error(ec);
}
The code still doesn't quite work (it waits for an async callback to happen and times out) but this solves the original problem presented in the question.

asio write succeeding but no information sent

I am having a problem while creating a client program that sends requests. The request are using keep alive TCP HTTP connections. When a connection is closed(due to timeout or max being hit), I try and start a new connection if none are available, and resend the request. The connect works fine however, when I try and send the write, nothing is sent(according to Wireshark), but my error code for the write was a success. The receiving server does not receive any information either. Here is the main parts of my code:
void request_handler::send_1(std::vector<std::string> *bid_vector, std::string request_path, boost::mutex *bids_mutex)
{
try
{
boost::asio::streambuf request;
std::ostream request_stream(&request);
std::string reply_information;
request_stream << "GET /tests HTTP/1.1\r\n";
request_stream << "Host: 10.1.10.160\r\n";
request_stream << "Accept: */*\r\n";
request_stream << "Connection: keep-alive\r\n\r\n";
server1_mutex_.lock();
if(server1_available_map_.size() == 0)
{
server1_mutex_.unlock();
persistent_connection *new_connection = new persistent_connection("10.1.10.160","80");
if(new_connection->send(request, reply_information))
{
server1_mutex_.lock();
server1_available_map_[new_connection->get_id()] = new_connection;
server1_mutex_.unlock();
}
}
else
{
persistent_connection *current_connection = (*(server1_available_map_.begin())).second;
server1_available_map_.erase(current_connection->get_id());
server1_mutex_.unlock();
int retry_counter = 20;
while(!current_connection->query_rtb(request, reply_information) && --retry_counter != 0)
{
delete current_connection;
server1_mutex_.lock();
if(server1_available_map_.size() == 0)
{
server1_mutex_.unlock();
current_connection = new persistent_connection("10.1.10.160","80");
}
else
{
current_connection = (*(server1_available_map_.begin())).second;
server1_available_map_.erase(current_connection->get_id());
server1_mutex_.unlock();
}
}
//Could not connect to 20 connections
if(retry_counter == 0)
{
Log::fatal("Could not connect in 20 tries");
delete current_connection;
return;
}
server1_mutex_.lock();
server1_available_map_[current_connection->get_id()] = current_connection;
server1_mutex_.unlock();
}
bids_mutex->lock();
bid_vector->push_back(reply_information);
bids_mutex->unlock();
}
catch(boost::thread_interrupted& e)
{
std::cout << "before cancel 1" << std::endl;
return;
}
catch(...)
{
std::cout << "blah blah blah" << std::endl;
}
}
And my persistent_connection class
persistent_connection::persistent_connection(std::string ip, std::string port):
io_service_(), socket_(io_service_), host_ip_(ip)
{
boost::uuids::uuid uuid = boost::uuids::random_generator()();
id_ = boost::lexical_cast<std::string>(uuid);
boost::asio::ip::tcp::resolver resolver(io_service_);
boost::asio::ip::tcp::resolver::query query(host_ip_,port);
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ip::tcp::endpoint endpoint = *iterator;
socket_.async_connect(endpoint, boost::bind(&persistent_connection::handler_connect, this, boost::asio::placeholders::error, iterator));
io_service_.run();
}
void persistent_connection::handler_connect(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
if(ec)
{
std::cout << "Couldn't connect" << ec << std::endl;
return;
}
else
{
boost::asio::socket_base::keep_alive keep_option(true);
socket_.set_option(keep_option);
std::cout << "Connect handler" << std::endl;
}
}
bool persistent_connection::send(boost::asio::streambuf &request_information, std::string &reply_information)
{
std::cout << "DOING QUERY in " << id_ << std::endl;
boost::system::error_code write_ec, read_ec;
try
{
std::cout << "Before write" << std::endl;
boost::asio::write(socket_, request_information, write_ec);
std::cout << write_ec.message() << std::endl;
}catch(std::exception& e)
{
std::cout << "Write exception: " << e.what() << std::endl;
}
if(write_ec)
{
std::cout <<"Write error: " << write_ec.message() << std::endl;
return false;
}
boost::array<char,8192> buf;
buf.assign(0);
try
{
std::cout << "Before read" << std::endl;
boost::asio::read(socket_, boost::asio::buffer(buf), boost::asio::transfer_at_least(1), read_ec);
std::cout << read_ec.message() << std::endl;
}catch(std::exception& e)
{
std::cout << "Read exception: " << e.what() << std::endl;
}
if(read_ec)
{
std::cout << "Read error: " << read_ec.message() << std::endl;
return false;
}
reply_information = buf.data();
return true;
}
std::string persistent_connection::get_id()
{
return id_;
}
The path for this to happen is if server1_available_map_.size() > 0, and if the while executes, and fails. And then if the size == 0 on the second server1_available_map_.size();
The output for the call is:
DOING QUERY in 69a8f0ab-2a06-45b4-be26-37aea6d93ff2
Before write
Success
Before read
End of file
Read error: End of file
Connect handler
DOING QUERY in 4eacaa96-1040-4878-8bf5-c29b87fa1232
Before write
Success
Before read
Which shows that the first connection gets an end of file(connection closed by server on other end). The second connection connects fine(Connect handler message), and the query is executed in the second connection(different id), and the write is apparently successful, and the program hangs on the read(because there is nothing to read).
Does anyone have any idea why this would be happening? Is there something I seem to be doing wrong?
Thank you
It looks like you are passing the same boost::asio::streambuf to multiple write calls.
boost::asio::write(socket_, request_information, write_ec);
The contents of the buffer are consumed by the first call to boost::asio::write. This effectively empties the buffer so that there is nothing left to send. Pass a const string if you want to use the same buffer for multiple writes.