I am trying to combine the famous boost ssl client/server connection examples into a single program. For your kind reference, the base classes are like this:
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
namespace bt
{
//
// client.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
enum { max_length = 1024 };
class client
{
public:
client(boost::asio::io_service& io_service, boost::asio::ssl::context& context,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
: socket_(io_service, context)
{
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
socket_.lowest_layer().async_connect(endpoint,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error, ++endpoint_iterator));
}
void handle_connect(const boost::system::error_code& error,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
std::cout << "handle_connect\n";
if (!error)
{
std::cout << "handle_connect No error\n";
socket_.async_handshake(boost::asio::ssl::stream_base::client,
boost::bind(&client::handle_handshake, this,
boost::asio::placeholders::error));
}
else if (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator())
{
std::cout << "handle_connect retry!\n";
socket_.lowest_layer().close();
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
socket_.lowest_layer().async_connect(endpoint,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error, ++endpoint_iterator));
}
else
{
std::cout << "Connect failed: " << error << "\n";
}
}
void handle_handshake(const boost::system::error_code& error)
{
std::cout << "client handle_handshake\n";
if (!error)
{
std::cout << "Enter message: ";
// std::cin.getline(request_, max_length);
sprintf(request_, "%s", "Hi Testing...");
size_t request_length = strlen(request_);
boost::asio::async_write(socket_,
boost::asio::buffer(request_, request_length),
boost::bind(&client::handle_write, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "Handshake failed: " << error << "\n";
}
}
void handle_write(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
boost::asio::async_read(socket_,
boost::asio::buffer(reply_, bytes_transferred),
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "Write failed: " << error << "\n";
}
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
std::cout << "Reply: ";
std::cout.write(reply_, bytes_transferred);
std::cout << "\n";
}
else
{
std::cout << "Read failed: " << error << "\n";
}
}
private:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
char request_[max_length];
char reply_[max_length];
};
//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2011 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
class session
{
public:
session(boost::asio::io_service& io_service, boost::asio::ssl::context& context)
: socket_(io_service, context)
{
}
ssl_socket::lowest_layer_type& socket()
{
return socket_.lowest_layer();
}
void start()
{
std::cout << "session start->handshake\n";
socket_.async_handshake(boost::asio::ssl::stream_base::server,
boost::bind(&session::handle_handshake, this,
boost::asio::placeholders::error));
}
void handle_handshake(const boost::system::error_code& error)
{
std::cout << "session handle_handshake\n";
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
delete this;
}
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
}
else
{
delete this;
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
delete this;
}
}
private:
ssl_socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, unsigned short port)
: io_service_(io_service),
acceptor_(io_service,
boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)),
context_(io_service, boost::asio::ssl::context::sslv23)
{
//std::cout << "server()\n";
context_.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
context_.set_password_callback(boost::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.crt");
context_.use_private_key_file("server.key", boost::asio::ssl::context::pem);
context_.use_tmp_dh_file("dh1024.pem");
session* new_session = new session(io_service_, context_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
std::string get_password() const
{
return "test";
}
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
std::cout << "server() handle_accept\n";
if (!error)
{
std::cout << "server() handle_accept !error\n";
new_session->start();
new_session = new session(io_service_, context_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
else
{
std::cout << "server() handle_accept error:" << error.message() << std::endl;
delete new_session;
}
}
private:
boost::asio::io_service& io_service_;
boost::asio::ip::tcp::acceptor acceptor_;
boost::asio::ssl::context context_;
};
}//namespace bt
And the the main program is:
BOOST_AUTO_TEST_CASE(accept_ssl_connection_1)
{
boost::asio::io_service io_service_1;
boost::asio::io_service io_service_2;
int port = random_port();
std::stringstream i("");
i << port;
std::cout << "Port is:" << i.str() << std::endl;
//server
bt::server(io_service_1, port);
//client
boost::asio::ip::tcp::resolver resolver(io_service_2);
boost::asio::ip::tcp::resolver::query query("127.0.0.1", i.str());
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ssl::context ctx(io_service_2, boost::asio::ssl::context::sslv23);
ctx.set_verify_mode(boost::asio::ssl::context::verify_peer);
ctx.load_verify_file("server.crt");
bt::client c(io_service_2, ctx, iterator);
boost::thread thread1(boost::bind(&boost::asio::io_service::run, &io_service_1));
boost::thread thread2(boost::bind(&boost::asio::io_service::run, &io_service_2));
thread1.join();
thread2.join();
}
And here is the output I am getting:
Port is:7200
server() handle_accept
handle_connect
Connect failed: system:111
server() handle_accept error:Operation canceled
The program works if clien and server are built and run individually. I guess I have a mistake in io_service usage.
Could you please help me detect the issue?
1. Style
I suggest you put more effort in making the code readable.
Code is for humans to read, not computers
In your case, the extreme brevity like
bt::client c(...);
Leads to bugs like
bt::server(io_service_1, port);
There's not a lot of difference with the - probably intended - variable declaration
bt::server s(io_service_1, port);
Otherwise, the newly constructed server is immediately destructed and thereby cancels all pending operations.
2. Debugging
Try to actually present readable messages:
std::cout << "Connect failed: " << error.message() << "\n";
std::cout << "Handshake failed: " << error.message() << "\n";
std::cout << "Write failed: " << error.message() << "\n";
std::cout << "Read failed: " << error.message() << "\n";
std::cout << "server() handle_accept error:" << error.message() << std::endl;
This would tell you that "125" means "Operation aborted" etc.. This is what made me add a little trace here and there:
~session() { std::cout << "Deleting session!\n"; }
~server() { std::cout << "Deleting server!\n"; }
2. Asio Review, more style
Instead of doing things manually, prefer the composed operations defined in boost:
client(ba::io_service &io_service, ssl::context &context, tcp::resolver::iterator endpoint_iterator)
: socket_(io_service, context)
{
ba::async_connect(socket_.lowest_layer(), endpoint_iterator,
boost::bind(&client::handle_connect, this, bap::error));
}
void handle_connect(const boost::system::error_code &error) {
std::cout << "handle_connect\n";
if (!error) {
std::cout << "handle_connect No error\n";
socket_.async_handshake(ssl::stream_base::client, boost::bind(&client::handle_handshake, this, bap::error));
} else {
std::cout << "Connect failed: " << error.message() << "\n";
}
}
This does the whole iterator dance. But less error-prone.
Use namespace aliases to get readable/manageable lines
using boost::asio::ip::tcp;
namespace ba = boost::asio;
namespace bap = boost::asio::placeholders;
namespace ssl = boost::asio::ssl;
Use smart pointers (delete this? ugh)
Consider using 1 io_service. Using two doesn't add anything, really, and the names didn't clarify a thing. In fact, the first minutes of staring at your code had me dis-entangling the code for client and server, painstakingly verifying that they didn't mistakenly use the wrong service, leading to premature run() completion.
Account for race conditions. In your code, server and client run independently unsynchronized. At least add a delay:
boost::this_thread::sleep_for(boost::chrono::seconds(1));
to avoid the client connecting to the server before it started accepting connections.
Prefer boost::thread_group over lose threads:
boost::thread_group tg;
// ...
tg.create_thread(boost::bind(&ba::io_service::run, &io_service_1));
// ...
tg.create_thread(boost::bind(&ba::io_service::run, &io_service_2));
// ...
tg.join_all();
In fact, with 1 io_service and 1 thread, you sidestep all of the above (the async operations are synchronized due the implicit strand)
use higherlevel standard library features (e.g. std::to_string(int) instead of std::ostringstream; if you cannot use c++11, use boost::lexical_cast or write your own to_string-type helper function).
If the address is hardcoded to loopback, no need to "resolve" anything: just connect to tcp::endpoint{{}, port}
Consider moving ctx into client (like you moved the ssl params for the server into that class too)
prefer boost::array/std::array over raw arrays (request_ and reply_)
Why do you read as many bytes as you sent? Did you mean
ba::async_read(socket_, ba::buffer(reply_, bytes_transferred),
boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
I'd expect something like
ba::async_read(socket_, ba::buffer(reply_, reply.size()), // assuming array<>, see previous
boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
Consider composed operations over read_some again. read_some may not read a complete request. Consider adding a framing protocol or sending request length up front.
Avoid code duplication: async_accept is coded twice. Instead make it a separate function and call it twice:
void do_accept() {
session::ptr new_session = boost::make_shared<session>(io_service_, context_);
acceptor_.async_accept(new_session->socket(), boost::bind(&server::handle_accept, this, new_session, bap::error));
}
BONUS
Add a deadline to the accept so we can stop the server at a certain idle time interval
Since you are using smart pointers now (aren't you?) it's easy to add a session shutdown at this place too (session::close())
Let's do two client for the price of one, just for fun
Live On Coliru
//#define BOOST_ASIO_ENABLE_HANDLER_TRACKING 1
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <cstdlib>
#include <iostream>
using boost::asio::ip::tcp;
namespace ba = boost::asio;
namespace bap = boost::asio::placeholders;
namespace ssl = boost::asio::ssl;
namespace bt {
enum { max_length = 1024, idle_timeout_seconds = 2 };
class client {
public:
client(ba::io_service &io_service, tcp::resolver::iterator endpoint_iterator, std::string const& request)
: ctx_(io_service, ssl::context::sslv23),
socket_(io_service, ctx_),
request_(request)
{
ctx_.set_verify_mode(ssl::context::verify_peer);
ctx_.load_verify_file("server.crt");
ba::async_connect(socket_.lowest_layer(), endpoint_iterator,
boost::bind(&client::handle_connect, this, bap::error));
}
void handle_connect(const boost::system::error_code &error) {
std::cout << "handle_connect\n";
if (!error) {
std::cout << "handle_connect No error\n";
socket_.async_handshake(ssl::stream_base::client, boost::bind(&client::handle_handshake, this, bap::error));
} else {
std::cout << "Connect failed: " << error.message() << "\n";
}
}
void handle_handshake(const boost::system::error_code &error) {
std::cout << "client handle_handshake\n";
if (!error) {
ba::async_write(socket_, ba::buffer(request_),
boost::bind(&client::handle_write, this, bap::error, bap::bytes_transferred));
} else {
std::cout << "Handshake failed: " << error.message() << "\n";
}
}
void handle_write(const boost::system::error_code &error, size_t bytes_transferred) {
if (!error) {
ba::async_read(socket_, ba::buffer(reply_, bytes_transferred),
boost::bind(&client::handle_read, this, bap::error, bap::bytes_transferred));
} else {
std::cout << "Write failed: " << error.message() << "\n";
}
}
void handle_read(const boost::system::error_code &error, size_t bytes_transferred) {
if (!error) {
std::cout << "Reply: ";
std::cout.write(reply_.data(), bytes_transferred);
std::cout << "\n";
} else {
std::cout << "Read failed: " << error.message() << "\n";
}
}
private:
ssl::context ctx_;
ssl::stream<tcp::socket> socket_;
std::string request_;
std::array<char, max_length> reply_;
};
class session : public boost::enable_shared_from_this<session> {
public:
using ptr = boost::shared_ptr<session>;
session(ba::io_service &io_service, ssl::context &context) : socket_(io_service, context) {}
typedef ssl::stream<tcp::socket> ssl_socket;
ssl_socket::lowest_layer_type &socket() { return socket_.lowest_layer(); }
void start() {
std::cout << "session start->handshake\n";
socket_.async_handshake(ssl::stream_base::server, boost::bind(&session::handle_handshake, shared_from_this(), bap::error));
}
void handle_handshake(const boost::system::error_code &error) {
std::cout << "session handle_handshake\n";
if (error) return;
socket_.async_read_some(ba::buffer(data_),
boost::bind(&session::handle_read, shared_from_this(), bap::error, bap::bytes_transferred));
}
void handle_read(const boost::system::error_code &error, size_t bytes_transferred) {
if (error) return;
ba::async_write(socket_, ba::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, shared_from_this(), bap::error));
}
void handle_write(const boost::system::error_code &error) {
if (error) return;
socket_.async_read_some(ba::buffer(data_),
boost::bind(&session::handle_read, shared_from_this(), bap::error, bap::bytes_transferred));
}
void close() {
socket_.get_io_service().post([this] {
std::cout << "session::close()\n";
socket_.lowest_layer().cancel();
socket_.lowest_layer().close();
});
}
~session() { std::cout << "Deleting session\n"; }
private:
ssl_socket socket_;
std::array<char, max_length> data_;
};
class server {
public:
server(ba::io_service &io_service, unsigned short port)
: io_service_(io_service), acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
context_(io_service, ssl::context::sslv23),
deadline_(io_service)
{
// std::cout << "server()\n";
context_.set_options(ssl::context::default_workarounds | ssl::context::no_sslv2 | ssl::context::single_dh_use);
context_.set_password_callback(boost::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.crt");
context_.use_private_key_file("server.crt", ssl::context::pem);
context_.use_tmp_dh_file("dh2048.pem");
do_accept();
}
~server() { std::cout << "Deleting server\n"; }
std::string get_password() const { return "test"; }
void do_accept() {
session::ptr new_session = boost::make_shared<session>(io_service_, context_);
deadline_.expires_from_now(boost::posix_time::seconds(idle_timeout_seconds));
deadline_.async_wait(boost::bind(&server::handle_deadline, this, bap::error()));
acceptor_.async_accept(new_session->socket(), boost::bind(&server::handle_accept, this, new_session, bap::error));
}
void handle_accept(session::ptr new_session, const boost::system::error_code &error) {
std::cout << "server() handle_accept\n";
if (!error) {
std::cout << "server() handle_accept ok\n";
sessions_.push_back(new_session);
new_session->start();
do_accept();
} else {
std::cout << "server() handle_accept error:" << error.message() << std::endl;
}
}
void handle_deadline(boost::system::error_code ec) {
if (!ec) {
io_service_.post([this] {
// assuming 1 thread runs io_service, no more locking required
std::cout << "server() shutdown after idle timeout\n";
acceptor_.cancel();
acceptor_.close();
for (auto weak_sess : sessions_)
if (auto sess = weak_sess.lock())
sess->close();
});
}
}
private:
ba::io_service &io_service_;
tcp::acceptor acceptor_;
ssl::context context_;
ba::deadline_timer deadline_;
std::vector<boost::weak_ptr<session> > sessions_;
};
} // namespace bt
void accept_ssl_connection_1() {
ba::io_service svc;
int port = 6767;
std::cout << "Port is:" << port << std::endl;
// server
bt::server s(svc, port);
// client
tcp::resolver resolver(svc);
bt::client c(svc, resolver.resolve({"127.0.0.1", std::to_string(port)}), "Hello, I'm Bob");
bt::client d(svc, resolver.resolve({"127.0.0.1", std::to_string(port)}), "Hello, I'm Cindy");
svc.run();
}
int main() {
accept_ssl_connection_1();
}
Prints
Port is:6767
server() handle_accept
server() handle_accept ok
session start->handshake
handle_connect
handle_connect No error
handle_connect
handle_connect No error
server() handle_accept
server() handle_accept ok
session start->handshake
session handle_handshake
client handle_handshake
session handle_handshake
client handle_handshake
Reply: Hello, I'm Bob
Reply: Hello, I'm Cindy
server() shutdown after idle timeout
server() handle_accept
server() handle_accept error:Operation canceled
Deleting session
session::close()
session::close()
Deleting session
Deleting session
Deleting server
Error code 111 (ECONNREFUSED) means (in Linux):
"The target address was not listening for connections or refused the
connection request."
It usually occurs when a client try to connect to a server, and no one is listening the port. Possible reasons:
the server program is not running
the server program uses different TCP port number than the client
the server program is still starting. The port is not yet bound, when client try to connect.
In your case, the problem could be the option #3. Because you face the problem when the client and the server are started almost the same time.
I didn't check all of your code, is it really possible that client try connect before the server is ready.
Related
I code a ssl server and client run a pingpang process, after a little time, client say send data success but server not get it.
client run in multi thread, when single thread, seem normal.
and i try add timer to add shake hand, then server can get all data, but i want it can run rightly with out shake hand
can anyone help figure out whats wrong.
here is my server
#include <cstdlib>
#include <functional>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
using boost::asio::ip::tcp;
class session : public std::enable_shared_from_this<session> {
public:
session(tcp::socket socket, boost::asio::ssl::context &context)
: socket_(std::move(socket), context), m_strand(socket.get_executor()) {
}
void start() {
do_handshake();
}
private:
void do_handshake() {
auto self(shared_from_this());
socket_.async_handshake(boost::asio::ssl::stream_base::server,
[this, self](const boost::system::error_code &error) {
if (!error) {
do_read();
}
});
}
void do_read() {
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_),
[this, self](const boost::system::error_code &ec, std::size_t length) {
if (!ec) {
std::cout << "get <";
std::cout.write(data_, length);
std::cout << std::endl;
do_write(length);
}
});
}
void do_write(std::size_t length) {
auto self(shared_from_this());
std::cout << "send <";
std::cout.write(data_, length);
std::cout << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
[this, self](const boost::system::error_code &ec,
std::size_t /*length*/) {
if (!ec) {
do_read();
}
});
}
boost::asio::ssl::stream<tcp::socket> socket_;
boost::asio::strand<boost::asio::ip::tcp::socket::executor_type> m_strand;
char data_[1024];
};
class server {
public:
server(boost::asio::io_context &io_context, unsigned short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
context_(boost::asio::ssl::context::sslv23) {
context_.set_options(
boost::asio::ssl::context::default_workarounds
| boost::asio::ssl::context::no_sslv2
| boost::asio::ssl::context::single_dh_use);
context_.set_password_callback(std::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.pem");
context_.use_private_key_file("server.pem", boost::asio::ssl::context::pem);
context_.use_tmp_dh_file("dh2048.pem");
do_accept();
}
private:
std::string get_password() const {
return "test";
}
void do_accept() {
acceptor_.async_accept(
[this](const boost::system::error_code &error, tcp::socket socket) {
if (!error) {
std::make_shared<session>(std::move(socket), context_)->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
boost::asio::ssl::context context_;
};
int main(int argc, char *argv[]) {
try {
if (argc != 2) {
std::cerr << "Usage: server <port>\n";
return 1;
}
boost::asio::io_context io_context;
using namespace std; // For atoi.
server s(io_context, atoi(argv[1]));
io_context.run();
}
catch (std::exception &e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
and next client
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::tcp;
using std::placeholders::_1;
using std::placeholders::_2;
enum {
max_length = 1024
};
class client {
public:
client(boost::asio::io_context &io_context,
boost::asio::ssl::context &context,
const tcp::resolver::results_type &endpoints)
: socket_(io_context, context), strand_(io_context.get_executor()) {
socket_.set_verify_mode(boost::asio::ssl::verify_peer);
socket_.set_verify_callback(
std::bind(&client::verify_certificate, this, _1, _2));
connect(endpoints);
}
private:
bool verify_certificate(bool preverified,
boost::asio::ssl::verify_context &ctx) {
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 true;
}
void connect(const tcp::resolver::results_type &endpoints) {
boost::asio::async_connect(socket_.lowest_layer(), endpoints,
[this](const boost::system::error_code &error,
const tcp::endpoint & /*endpoint*/) {
if (!error) {
handshake();
} else {
std::cout << "Connect failed: " << error.message() << "\n";
}
});
}
void handshake() {
socket_.async_handshake(boost::asio::ssl::stream_base::client,
[this](const boost::system::error_code &error) {
if (!error) {
send_request("hello ssl");
boost::asio::post(strand_, std::bind(&client::recv, this));
} else {
std::cout << "Handshake failed: " << error.message() << "\n";
}
});
}
void send_request(const std::string &msg) {
boost::asio::async_write(
socket_, boost::asio::buffer(msg),
[this](const boost::system::error_code &error, std::size_t length) {
if (!error) {
std::cout << "send data success, size : " << length << std::endl;
} else {
std::cout << "Write failed: " << error.message() << std::endl;
}
});
}
void recv() {
boost::asio::async_read(
socket_, buffer_, boost::asio::transfer_exactly(9),
boost::asio::bind_executor(
strand_, [this](const boost::system::error_code &error, std::size_t length) {
if (!error) {
std::istream buffer(&buffer_);
std::vector<char> msg(length, 0);
buffer.readsome(msg.data(), length);
std::string recvMsg(msg.begin(), msg.end());
std::cout << recvMsg << std::endl;
send_request(recvMsg);
boost::asio::post(strand_, std::bind(&client::recv, this));
} else {
std::cout << "Read failed: " << error.message() << std::endl;
}
}));
}
boost::asio::ssl::stream<tcp::socket> socket_;
boost::asio::streambuf buffer_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
};
int main(int argc, char *argv[]) {
try {
if (argc != 3) {
std::cerr << "Usage: client <host> <port>\n";
return 1;
}
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve(argv[1], argv[2]);
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
ctx.load_verify_file("ca.pem");
client c(io_context, ctx, endpoints);
boost::thread_group threadPool;
for (size_t i = 0; i < boost::thread::hardware_concurrency(); ++i) {
threadPool.create_thread(boost::bind(&boost::asio::io_context::run, &io_context));
}
io_context.run();
}
catch (std::exception &e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
when data not send to server,client will print like this
hello ssl
hello ssl
send data success, size : 9
send data success, size : 9
Check this out. If you remove the thread_group (as far as I can tell, it adds no value), everything works. This is a good sign that you have a threading bug.
I'm not in the mood to read the code until I see the problem, so let's circle a bit.
Adding ASAN/UBSAN shows nothing bad immediately, so that's good.
Let me look at the code a little then.
session creates a m_strand - which is never used...
you forgot to join the extra threads
Now that I noticed some potential confusion around the strand, I looked at the client strand use. And see that it is inconsistent:
the socket itself is NOT on the strand
send_request doesn't run on nor bind the completion handler to the strand's executor
the communication is full-duplex (meaning async_write and async_read happen concurrently).
this means that where client::recv is posted to the strand, it doesn't actually synchronize threaded access to socket_ (because the send_request is not tied to the strand in the first place)
If the above is surprising, you're not the first to fall into that:
Why is `net::dispatch` needed when the I/O object already has an executor?. In your example connect() and handshake() can be considered safe because they form a logical strand (sequential flow of execution). The problem arises with the concurrent paths.
By far the simplest way to fix the situation seems to construct socket_ from the strand_. This implies reordering the members so strand_ is initialized first:
client(boost::asio::io_context& io_context, ssl::context& context,
const tcp::resolver::results_type& endpoints)
: strand_(io_context.get_executor())
, socket_(strand_, context)
Next up, all posts to the strand can be dropped because they always happen from a completion handler on that strand.
send_request("hello ssl");
recv(); // already on the strand in this completion handler
The mild irony is that send_request was executed under the implied assumption that it was on the strand.
The cleaned up programs until this point are
File client.cpp
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/thread.hpp>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
using boost::asio::ip::tcp;
using boost::system::error_code;
using std::placeholders::_1;
using std::placeholders::_2;
namespace ssl = boost::asio::ssl;
class client {
public:
client(boost::asio::io_context& io_context, ssl::context& context,
const tcp::resolver::results_type& endpoints)
: strand_(io_context.get_executor())
, socket_(strand_, context)
{
socket_.set_verify_mode(ssl::verify_peer);
socket_.set_verify_callback(
std::bind(&client::verify_certificate, this, _1, _2));
connect(endpoints);
}
private:
bool verify_certificate(bool preverified, ssl::verify_context& ctx)
{
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 true;
}
void connect(const tcp::resolver::results_type& endpoints)
{
async_connect( //
socket_.lowest_layer(), endpoints,
bind_executor(
strand_, [this](error_code error, const tcp::endpoint&) {
if (!error) {
handshake();
} else {
std::cout << "Connect failed: " << error.message() << "\n";
}
}));
}
void handshake()
{
socket_.async_handshake(
ssl::stream_base::client,
bind_executor(strand_, [this](error_code error) {
if (!error) {
send_request("hello ssl");
recv(); // already on the strand in this completion handler
} else {
std::cout << "Handshake failed: " << error.message()
<< "\n";
}
}));
}
void send_request(std::string const& msg)
{
msg_ = msg;
async_write(
socket_, boost::asio::buffer(msg_),
bind_executor(
strand_, [/*this*/](error_code error, std::size_t length) {
if (!error) {
std::cout << "send data success, size : " << length << std::endl;
} else {
std::cout << "Write failed: " << error.message() << std::endl;
}
}));
}
void recv()
{
async_read(
socket_, buffer_, boost::asio::transfer_exactly(9),
boost::asio::bind_executor(
strand_, [this](error_code error, std::size_t length) {
if (!error) {
std::istream buffer(&buffer_);
std::vector<char> msg(length, 0);
buffer.readsome(msg.data(), length);
msg_.assign(msg.begin(), msg.end());
std::cout << msg_ << std::endl;
send_request(msg_);
recv(); // already on the strand in this completion handler
} else {
std::cout << "Read failed: " << error.message() << std::endl;
}
}));
}
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
ssl::stream<tcp::socket> socket_;
boost::asio::streambuf buffer_;
std::string msg_;
};
int main(int argc, char* argv[])
{
try {
if (argc != 3) {
std::cerr << "Usage: client <host> <port>\n";
return 1;
}
boost::asio::io_context io_context;
tcp::resolver resolver(io_context);
auto endpoints = resolver.resolve(argv[1], argv[2]);
ssl::context ctx(ssl::context::sslv23);
ctx.load_verify_file("ca.pem");
client c(io_context, ctx, endpoints);
boost::thread_group threadPool;
for (size_t i = 0; i < boost::thread::hardware_concurrency(); ++i) {
threadPool.create_thread(
boost::bind(&boost::asio::io_context::run, &io_context));
}
threadPool.join_all();
//io_context.run();
return 0;
} catch (std::exception const& e) {
std::cerr << "Exception: " << e.what() << "\n";
return 1;
}
}
File server.cpp
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
namespace ssl = boost::asio::ssl;
using boost::asio::ip::tcp;
using boost::system::error_code;
class session : public std::enable_shared_from_this<session> {
public:
session(tcp::socket socket, ssl::context& context)
: socket_(std::move(socket), context)
, m_strand(socket.get_executor())
{
}
void start()
{
do_handshake();
}
private:
void do_handshake()
{
auto self(shared_from_this());
socket_.async_handshake(ssl::stream_base::server,
[this, self](error_code error) {
if (!error) {
do_read();
}
});
}
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(
boost::asio::buffer(data_),
[this, self](error_code ec, std::size_t length) {
if (!ec) {
std::cout << "get <";
std::cout.write(data_, length);
std::cout << std::endl;
do_write(length);
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this());
std::cout << "send <";
std::cout.write(data_, length);
std::cout << std::endl;
boost::asio::async_write(
socket_, boost::asio::buffer(data_, length),
[this, self](error_code ec, std::size_t /*length*/) {
if (!ec) {
do_read();
}
});
}
ssl::stream<tcp::socket> socket_;
boost::asio::strand<tcp::socket::executor_type> m_strand;
char data_[1024];
};
class server {
public:
server(boost::asio::io_context& io_context, unsigned short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
, context_(ssl::context::sslv23)
{
context_.set_options(ssl::context::default_workarounds |
ssl::context::no_sslv2 |
ssl::context::single_dh_use);
context_.set_password_callback(std::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.pem");
context_.use_private_key_file("server.pem", ssl::context::pem);
context_.use_tmp_dh_file("dh2048.pem");
do_accept();
}
private:
std::string get_password() const
{
return "test";
}
void do_accept()
{
acceptor_.async_accept([this](error_code error, tcp::socket socket) {
if (!error) {
std::make_shared<session>(std::move(socket), context_)->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
ssl::context context_;
};
int main(int argc, char* argv[])
{
try {
if (argc != 2) {
std::cerr << "Usage: server <port>\n";
return 1;
}
boost::asio::io_context io_context;
server s(io_context, std::atoi(argv[1]));
io_context.run();
return 0;
} catch (std::exception const& e) {
std::cerr << "Exception: " << e.what() << "\n";
return 1;
}
}
Other Problems
Lifetime Error
UBSAN/ASAN didn't catch it,but this is wrong:
void send_request(const std::string& msg)
{
async_write(
socket_, boost::asio::buffer(msg),
...
The problem is the lifetime of msg, which disappears before the async operation got a chance to run, let alone complete. So, move the buffer so the lifetime is sufficient (e.g. member msg_).
Concurrent Writes
When the client locks up, it shows
send data success, size : 9
hello ssl
hello ssl
send data success, size : 9
send data success, size : 9
This indicates that a second hello ssl is received before send is initiated. This means that a second send is initiated. Under the hood this cancels a duplex synchronization object inside the ssl stream context. You can see this with -DBOOST_ASIO_ENABLE_HANDLER_TRACKING:
#asio|1630155694.209267|51139|deadline_timer#0x7ffc6fa61e48.cancel
Visualizing with the handlerviz.pl script:
The problem is violating the requirements here:
The program must ensure that the stream performs no other write operations (such as async_write, the stream's async_write_some function, or any other composed operations that perform writes) until this operation completes.
Two easy ways to fix it:
change the IO from full duplex to sequential read/write/read/write just like the server
make an output queue that contains messages still to be written in sequence
Fixed Solution
This uses an outbox as in the second solution for overlapping writes above. I've also taken the liberty to
remove the unnecessary intermediate buffer streambuf buffer_ instead reading directly into a string.
replace io_context + thread_group with the more elegant thread_pool
many minor improvements (some mentioned above)
File client.cpp
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/thread.hpp>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
using boost::asio::ip::tcp;
using boost::system::error_code;
using std::placeholders::_1;
using std::placeholders::_2;
namespace ssl = boost::asio::ssl;
using Executor = boost::asio::thread_pool::executor_type;
class client {
public:
client(Executor ex, ssl::context& context,
const tcp::resolver::results_type& endpoints)
: strand_(ex)
, socket_(strand_, context)
{
socket_.set_verify_mode(ssl::verify_peer);
socket_.set_verify_callback(
std::bind(&client::verify_certificate, this, _1, _2));
connect(endpoints);
}
private:
bool verify_certificate(bool preverified, ssl::verify_context& ctx)
{
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 true;
}
void connect(const tcp::resolver::results_type& endpoints)
{
async_connect( //
socket_.lowest_layer(), endpoints,
bind_executor(
strand_, [this](error_code error, const tcp::endpoint&) {
if (!error) {
handshake();
} else {
std::cout << "Connect failed: " << error.message() << "\n";
}
}));
}
void handshake()
{
socket_.async_handshake(
ssl::stream_base::client,
bind_executor(strand_, [this](error_code error) {
if (!error) {
send_request("hello ssl");
recv(); // already on the strand in this completion handler
} else {
std::cout << "Handshake failed: " << error.message()
<< "\n";
}
}));
}
void send_request(std::string msg)
{
outbox_.push_back(std::move(msg));
if (outbox_.size() == 1)
{
send_loop();
}
}
void send_loop()
{
async_write( //
socket_, boost::asio::buffer(outbox_.back()),
bind_executor(
strand_, [this](error_code error, std::size_t length) {
if (!error) {
std::cout << "send data success, size : " << length << std::endl;
outbox_.pop_back();
} else {
std::cout << "Write failed: " << error.message() << std::endl;
}
if (!outbox_.empty())
send_loop();
}));
}
void recv()
{
async_read(
socket_, boost::asio::dynamic_buffer(buffer_), boost::asio::transfer_exactly(9),
boost::asio::bind_executor(
strand_, [this](error_code error, std::size_t length) {
if (!error) {
std::cout << buffer_ << std::endl;
send_request(std::move(buffer_));
recv(); // already on the strand in this completion handler
} else {
std::cout << "Read failed: " << error.message() << std::endl;
}
}));
}
boost::asio::strand<Executor> strand_;
ssl::stream<tcp::socket> socket_;
std::string buffer_;
std::deque<std::string> outbox_;
};
int main(int argc, char* argv[])
{
try {
if (argc != 3) {
std::cerr << "Usage: client <host> <port>\n";
return 1;
}
ssl::context ctx(ssl::context::sslv23);
ctx.load_verify_file("ca.pem");
boost::asio::thread_pool io;
tcp::resolver resolver(io);
client c(io.get_executor(), ctx, resolver.resolve(argv[1], argv[2]));
io.join();
return 0;
} catch (std::exception const& e) {
std::cerr << "Exception: " << e.what() << "\n";
return 1;
}
}
File server.cpp
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/bind/bind.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
namespace ssl = boost::asio::ssl;
using boost::asio::ip::tcp;
using boost::system::error_code;
class session : public std::enable_shared_from_this<session> {
public:
session(tcp::socket socket, ssl::context& context)
: socket_(std::move(socket), context)
{
}
void start()
{
do_handshake();
}
private:
void do_handshake()
{
auto self(shared_from_this());
socket_.async_handshake(ssl::stream_base::server,
[this, self](error_code error) {
if (!error) {
do_read();
}
});
}
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(
boost::asio::buffer(data_),
[this, self](error_code ec, std::size_t length) {
if (!ec) {
std::cout << "get <";
std::cout.write(data_.data(), length);
std::cout << std::endl;
do_write(length);
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this());
std::cout << "send <";
std::cout.write(data_.data(), length);
std::cout << std::endl;
boost::asio::async_write(
socket_, boost::asio::buffer(data_.data(), length),
[this, self](error_code ec, std::size_t /*length*/) {
if (!ec) {
do_read();
}
});
}
ssl::stream<tcp::socket> socket_;
std::array<char, 1024> data_;
};
class server {
public:
server(boost::asio::io_context& io_context, unsigned short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port))
, context_(ssl::context::sslv23)
{
context_.set_options(ssl::context::default_workarounds |
ssl::context::no_sslv2 |
ssl::context::single_dh_use);
context_.set_password_callback(std::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.pem");
context_.use_private_key_file("server.pem", ssl::context::pem);
context_.use_tmp_dh_file("dh2048.pem");
do_accept();
}
private:
std::string get_password() const
{
return "test";
}
void do_accept()
{
acceptor_.async_accept([this](error_code error, tcp::socket socket) {
if (!error) {
std::make_shared<session>(std::move(socket), context_)->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
ssl::context context_;
};
int main(int argc, char* argv[])
{
try {
if (argc != 2) {
std::cerr << "Usage: server <port>\n";
return 1;
}
boost::asio::io_context io_context;
server s(io_context, std::atoi(argv[1]));
io_context.run();
return 0;
} catch (std::exception const& e) {
std::cerr << "Exception: " << e.what() << "\n";
return 1;
}
}
Live Demo:
As you can see (using a uniq -dc trick to suppress all non-repeating lines) now it happily continues in the case where multiple receives come in before send is initiated.
So I've been trying to write a proxy in C++ using the boost.asio. My initial project includes the client that writes a string message into a socket, a server that receives this message and writes a string message into a socket, and a proxy that works with the two mentioned sockets.
The proxy code looks like this (The future intention is handle multiple connections and to use the transfered data somehow, and the callbacks would perform some actual work other than logging):
#include "commondata.h"
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
using namespace boost::asio;
using ip::tcp;
using std::cout;
using std::endl;
class con_handler : public boost::enable_shared_from_this<con_handler> {
private:
tcp::socket client_socket;
tcp::socket server_socket;
enum { max_length = 1024 };
char client_data[max_length];
char server_data[max_length];
public:
typedef boost::shared_ptr<con_handler> pointer;
con_handler(boost::asio::io_service& io_service):
server_socket(io_service),
client_socket(io_service) {
memset(client_data, 0, max_length);
memset(server_data, 0, max_length);
server_socket.connect( tcp::endpoint( boost::asio::ip::address::from_string(SERVERIP), SERVERPORT ));
}
// creating the pointer
static pointer create(boost::asio::io_service& io_service) {
return pointer(new con_handler(io_service));
}
//socket creation
tcp::socket& socket() {
return client_socket;
}
void start() {
//read the data into the input buffer
client_socket.async_read_some(
boost::asio::buffer(client_data, max_length),
boost::bind(&con_handler::handle_read,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
server_socket.async_write_some(
boost::asio::buffer(client_data, max_length),
boost::bind(&con_handler::handle_write,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
server_socket.async_read_some(
boost::asio::buffer(server_data, max_length),
boost::bind(&con_handler::handle_read,
shared_from_this(),
server_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
client_socket.async_write_some(
boost::asio::buffer(server_data, max_length),
boost::bind(&con_handler::handle_write,
shared_from_this(),
server_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(const char* data, const boost::system::error_code& err, size_t bytes_transferred) {
if (!err) {
cout << "proxy handle_read" << endl;
cout << data << endl;
} else {
std::cerr << "error: " << err.message() << std::endl;
client_socket.close();
}
}
void handle_write(const char* data, const boost::system::error_code& err, size_t bytes_transferred) {
if (!err) {
cout << "proxy handle_write" << endl;
cout << data << endl;
} else {
std::cerr << "error: " << err.message() << endl;
client_socket.close();
}
}
};
class Server {
private:
boost::asio::io_service io_service;
tcp::acceptor acceptor_;
void start_accept() {
// socket
con_handler::pointer connection = con_handler::create(io_service);
// asynchronous accept operation and wait for a new connection.
acceptor_.async_accept(connection->socket(),
boost::bind(&Server::handle_accept, this, connection,
boost::asio::placeholders::error));
}
public:
//constructor for accepting connection from client
Server()
: acceptor_(io_service, tcp::endpoint(tcp::v4(), PROXYPORT)) {
start_accept();
}
void handle_accept(const con_handler::pointer& connection, const boost::system::error_code& err) {
if (!err) {
connection->start();
}
start_accept();
}
boost::asio::io_service& get_io_service() {
return io_service;
}
};
int main(int argc, char *argv[]) {
try {
Server server;
server.get_io_service().run();
} catch(std::exception& e) {
std::cerr << e.what() << endl;
}
return 0;
}
If the messages sent are strings (which I've used initially to test if my code works at all), then all of the callbacks are called the way I wanted them to be called, and the thing seems to be working.
Here's the stdout of the proxy for that case:
user#laptop:$ ./proxy
proxy handle_read
message from the client
proxy handle_write
message from the client
proxy handle_read
message from server
proxy handle_write
message from server
So the client sends the "message from the client" string, which is received and saved by the proxy, the same string is sent to the server, then the server sends back the "message from server" string, which is also received and saved by the proxy and then is sent to the client.
The problem appears when I try to use the actual web server (Apache) and an application like JMeter to talk to each other. This is the stdout for this case:
user#laptop:$ ./proxy
proxy handle_write
proxy handle_write
proxy handle_read
GET / HTTP/1.1
Connection: keep-alive
Host: 127.0.0.1:1337
User-Agent: Apache-HttpClient/4.5.5 (Java/11.0.8)
error: End of file
The JMeter test then fails with a timeout (that is when the proxy gets the EOF error), and no data seems to be sent to the apache webserver. The questions that I have for now are then why the callbacks are called in another order comparing to the case when the string messages are sent and why the data is not being transferred to the server socket, I guess. Thanks in advance for any help!
Abbreviating from start():
client_socket.async_read_some (buffer(client_data), ...);
server_socket.async_write_some (buffer(client_data), ...);
server_socket.async_read_some (buffer(server_data), ...);
client_socket.async_write_some (buffer(server_data), ...);
//read the data into the input
client_socket.async_read_some (buffer(client_data), ...);
server_socket.async_write_some (buffer(client_data), ...);
server_socket.async_read_some (buffer(server_data), ...);
client_socket.async_write_some (buffer(server_data), ...);
That's... not how async operations work. They run asynchronously, meaning that they will all immediately return.
You're simultaneously reading and writing from some buffers, without waiting for valid data. Also, you're writing the full buffer always, regardless of how much was received.
All of this spells Undefined Behaviour.
Start simple
Conceptually you just want to read:
void start() {
//read the data into the input buffer
client_socket.async_read_some(
boost::asio::buffer(client_data, max_length),
boost::bind(&con_handler::handle_read,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
Now, once you received data, you might want to relay that:
void handle_read(const char* data, const boost::system::error_code& err, size_t bytes_transferred) {
if (!err) {
std::cout << "proxy handle_read" << std::endl;
server_socket.async_write_some(
boost::asio::buffer(client_data, bytes_transferred),
boost::bind(&con_handler::handle_write,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else {
std::cerr << "error: " << err.message() << std::endl;
client_socket.close();
}
}
Note that it seems a bit arbitrary to only close one side of the connection on errors. You probably at least want to cancel() any async operations on both, optionally shutdown() and then just let the shared_ptr destruct your con_handler.
Full Duplex
Now, for full-duplex operation you can indeed start the reverse relay at the same time. It gets a little unweildy to maintain the call chains in separate methods (after all you don't just switch the buffers, but also the socket pairs).
It might be instructive to realize that you're doing the same thing twice:
client -> [...buffer...] -> server
server -> [...buffer...] -> client
You can encapsulate each side in a class, and avoid duplicating all the code:
struct relay {
tcp::socket &from, &to;
std::array<char, max_length> buf{};
void run_relay(pointer self) {
from.async_read_some(asio::buffer(buf),
[this, self](error_code ec, size_t n) {
if (ec) return handle(from, ec);
/*
*std::cout
* << "From " << from.remote_endpoint()
* << ": " << std::quoted(std::string_view(buf.data(), n))
* << std::endl;
*/
async_write(to, asio::buffer(buf, n), [this, self](error_code ec, size_t) {
if (ec) return handle(to, ec);
run_relay(self);
});
});
}
void handle(tcp::socket& which, error_code ec = {}) {
if (ec == asio::error::eof) {
// soft "error" - allow write to complete
std::cout << "EOF on " << which.remote_endpoint() << std::endl;
which.shutdown(tcp::socket::shutdown_receive, ec);
}
if (ec) {
from.cancel();
to.cancel();
std::string reason = ec.message();
auto fep = from.remote_endpoint(ec),
tep = to.remote_endpoint(ec);
std::cout << "Stopped relay " << fep << " -> " << tep << " due to " << reason << std::endl;
}
}
} c_to_s {client_socket, server_socket, {0}},
s_to_c {server_socket, client_socket, {0}};
Note
we sidestepped the bind mess by using lambdas
we cancel both ends of the relay on error
we use a std::array buffer - more safe and easier to use
we only write as many bytes as were received, regardless of the size of the buffer
we don't schedule another read until the write has completed to avoid clobbering the data in buf
Let's implement con_handler start again
Using the relay from just above:
void start() {
c_to_s.run_relay(shared_from_this());
s_to_c.run_relay(shared_from_this());
}
That's all. We pass ourselves so the con_handler stays alive until all operations complete.
DEMO Live On Coliru
#define PROXYPORT 8899
#define SERVERIP "173.203.57.63" // coliru IP at the time
#define SERVERPORT 80
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <iomanip>
namespace asio = boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using namespace std::chrono_literals;
class con_handler : public boost::enable_shared_from_this<con_handler> {
public:
con_handler(asio::io_service& io_service):
server_socket(io_service),
client_socket(io_service)
{
server_socket.connect({ asio::ip::address::from_string(SERVERIP), SERVERPORT });
}
// creating the pointer
using pointer = boost::shared_ptr<con_handler>;
static pointer create(asio::io_service& io_service) {
return pointer(new con_handler(io_service));
}
//socket creation
tcp::socket& socket() {
return client_socket;
}
void start() {
c_to_s.run_relay(shared_from_this());
s_to_c.run_relay(shared_from_this());
}
private:
tcp::socket server_socket;
tcp::socket client_socket;
enum { max_length = 1024 };
struct relay {
tcp::socket &from, &to;
std::array<char, max_length> buf{};
void run_relay(pointer self) {
from.async_read_some(asio::buffer(buf),
[this, self](error_code ec, size_t n) {
if (ec) return handle(from, ec);
/*
*std::cout
* << "From " << from.remote_endpoint()
* << ": " << std::quoted(std::string_view(buf.data(), n))
* << std::endl;
*/
async_write(to, asio::buffer(buf, n), [this, self](error_code ec, size_t) {
if (ec) return handle(to, ec);
run_relay(self);
});
});
}
void handle(tcp::socket& which, error_code ec = {}) {
if (ec == asio::error::eof) {
// soft "error" - allow write to complete
std::cout << "EOF on " << which.remote_endpoint() << std::endl;
which.shutdown(tcp::socket::shutdown_receive, ec);
}
if (ec) {
from.cancel();
to.cancel();
std::string reason = ec.message();
auto fep = from.remote_endpoint(ec),
tep = to.remote_endpoint(ec);
std::cout << "Stopped relay " << fep << " -> " << tep << " due to " << reason << std::endl;
}
}
} c_to_s {client_socket, server_socket, {0}},
s_to_c {server_socket, client_socket, {0}};
};
class Server {
asio::io_service io_service;
tcp::acceptor acceptor_;
void start_accept() {
// socket
auto connection = con_handler::create(io_service);
// asynchronous accept operation and wait for a new connection.
acceptor_.async_accept(
connection->socket(),
[connection, this](error_code ec) {
if (!ec) connection->start();
start_accept();
});
}
public:
Server() : acceptor_(io_service, {{}, PROXYPORT}) {
start_accept();
}
void run() {
io_service.run_for(5s); // .run();
}
};
int main() {
Server().run();
}
When run with
printf "GET / HTTP/1.1\r\nHost: coliru.stacked-crooked.com\r\n\r\n" | nc 127.0.0.1 8899
The server prints:
EOF on 127.0.0.1:36452
And the netcat receives reply:
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 8616
Server: WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) OpenSSL/1.0.2g
Date: Sat, 01 Aug 2020 00:25:10 GMT
Connection: Keep-Alive
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<html>
....
</html>
Summary
Thinking clearly about what you are trying to achieve, avoids accidentally complexity. It allowed us to come up with a good building block (relay), evaporating complexity.
I try to write an async message to the server from my client code, the write handler gets called with the correct bytes sent; however, the server receives 0 bytes.
Cliente output:
You are connected
You received the following message from the server:
Sat Aug 20 17:42:01 2016
Sending...
Server output:
Server is online!
127.0.0.1:51973 connected!
Client has received the messaged.
You received the following message from the server:
server source:
using boost::asio::ip::tcp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class tcp_connection: public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket& socket()
{
return socket_;
}
// Call boost::asio::async_write() to serve the data to the client.
// We are using boost::asio::async_write(),
// rather than ip::tcp::socket::async_write_some(),
// to ensure that the entire block of data is sent.
void start()
{
// The data to be sent is stored in the class member m_message
// as we need to keep the data valid
// until the asynchronous operation is complete.
m_message = make_daytime_string();
// When initiating the asynchronous operation,
// and if using boost::bind(),
// we must specify only the arguments
// that match the handler's parameter list.
// In this code, both of the argument placeholders
// (boost::asio::placeholders::error
// and boost::asio::placeholders::bytes_transferred)
// could potentially have been removed,
// since they are not being used in handle_write().
std::cout << socket_.remote_endpoint().address().to_string() << ":" << socket_.remote_endpoint().port() << " connected!" << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(m_message),
boost::bind(&tcp_connection::handle_write, shared_from_this()));
boost::asio::async_read(socket_, boost::asio::buffer(_buffer), boost::bind(&tcp_connection::handle_receive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
private:
tcp_connection(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
// handle_write() is responsible for any further actions
// for this client connection.
void handle_write() // call back.. when it finishes sending, we come here
{
std::cout << "Client has received the messaged. " << std::endl;
}
void handle_receive(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "You received the following message from the server:" << std::endl;
std::cout.write(_buffer.data(), bytes_transferred);
}
tcp::socket socket_;
std::string m_message;
boost::array<char, 126> _buffer;
};
class tcp_server
{
public:
tcp_server(boost::asio::io_service& io_service) : acceptor_(io_service, tcp::endpoint(tcp::v4(), 7171))
{
// start_accept() creates a socket and
// initiates an asynchronous accept operation
// to wait for a new connection.
start_accept();
}
private:
void start_accept()
{
// creates a socket
tcp_connection::pointer new_connection = tcp_connection::create(acceptor_.get_io_service());
// initiates an asynchronous accept operation
// to wait for a new connection.
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
// handle_accept() is called when the asynchronous accept operation
// initiated by start_accept() finishes. It services the client request
void handle_accept(tcp_connection::pointer new_connection,
const boost::system::error_code& error)
{
if (!error)
{
new_connection->start();
}
// Call start_accept() to initiate the next accept operation.
start_accept();
}
tcp::acceptor acceptor_;
};
int main()
{
std::cout << "Server is online!" << std::endl;
try
{
boost::asio::io_service io_service;
tcp_server server(io_service);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
client source:
#include <iostream>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class Connection
{
public:
Connection(boost::asio::io_service& io) : _socket(io){}
void connect(tcp::resolver::iterator& point)
{
boost::asio::async_connect(_socket, point, boost::bind(&Connection::onConnected, this, boost::asio::placeholders::error));
}
void onConnected(const boost::system::error_code& ErrorCode)
{
std::cout << "You are connected" << std::endl;
// receive first message on onReceive
boost::asio::async_read(_socket, boost::asio::buffer(_buffer), boost::bind(&Connection::onReceive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void onSend(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "Sending..." << std::endl;
}
void onReceive(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "You received the following message from the server:" << std::endl;
std::cout.write(_buffer.data(), bytes_transferred);
// send first message on onSend
m_message = make_daytime_string();
boost::asio::async_write(_socket, boost::asio::buffer(m_message), boost::bind(&Connection::onSend, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
tcp::socket& getSocket()
{
return _socket;
}
private:
tcp::socket _socket;
boost::array<char, 126> _buffer;
std::string m_message;
};
int main()
{
try
{
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query("127.0.0.1", "7171");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
Connection conn(io_service);
conn.connect(endpoint_iterator);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
#edit:
new client code:
class Connection : public boost::enable_shared_from_this<Connection>
{
public:
typedef boost::shared_ptr<Connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new Connection(io_service));
}
tcp::socket& socket()
{
return _socket;
}
void connect(tcp::resolver::iterator& point)
{
boost::asio::async_connect(_socket, point, boost::bind(&Connection::connect_handler, this, boost::asio::placeholders::error));
}
void connect_handler(const boost::system::error_code& error)
{
if(error)
{
std::cout << "Error on connect_handler: " << error.message() << std::endl;
return;
}
std::cout << "You are connected to the server." << std::endl;
start();
}
void start()
{
start_write();
start_read();
}
private:
// private ctor
Connection(boost::asio::io_service& io) : _socket(io){}
void start_write()
{
_daymessage = make_daytime_string();
boost::asio::async_write(_socket, boost::asio::buffer(_daymessage),
boost::bind(&Connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_write(const boost::system::error_code& error,
size_t bytes)
{
if(error)
{
std::cout << "Error on handle write: " << error.message() << std::endl;
return;
}
std::cout << "Message has been sent!" << std::endl;
start_write();
}
void start_read()
{
// Start an asynchronous operation to read a newline-delimited message.
boost::asio::async_read_until(_socket, _buffer, '\n',
boost::bind(&Connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(const boost::system::error_code& error, size_t bytes)
{
if(error)
{
std::cout << "Error on handle read: " << error.message() << std::endl;
return;
}
// Extract the newline-delimited message from the buffer.
std::string line;
std::istream is(&_buffer);
std::getline(is, line);
if (!line.empty())
{
std::cout << "Received: " << line << "\n";
}
start_read();
}
tcp::socket _socket;
std::string _daymessage;
boost::asio::streambuf _buffer;
};
int main()
{
std::cout << "Client is running!" << std::endl;
try
{
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query("127.0.0.1", "7171");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
auto connection = Connection::create(io_service);
connection->connect(endpoint_iterator);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
obs: I got a "tr1::bad_weak_ptr" when I use public ctor and instantiate with make_shared. The private ctor and static member func worked fine.
There are many issues with the code you have provided:
In your server connection class, you are using raw this pointer
instead of using shared_from_this. This was resulting in the
operation getting cancelled as your resource was getting out of
scope.
The code is making use of async_read with a buffer of size 126. I
guess the operation would not finish until you receive atleast that
many bytes. Use async_read_until instead. Due to lack of any protocol or predefined byte sequence, I have modified the code
to send '\n' as the delimiter.
Never ignore the error code received.
Modified Server Code:
class tcp_connection: public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket& socket()
{
return socket_;
}
// Call boost::asio::async_write() to serve the data to the client.
// We are using boost::asio::async_write(),
// rather than ip::tcp::socket::async_write_some(),
// to ensure that the entire block of data is sent.
void start()
{
// The data to be sent is stored in the class member m_message
// as we need to keep the data valid
// until the asynchronous operation is complete.
m_message = make_daytime_string();
// When initiating the asynchronous operation,
// and if using boost::bind(),
// we must specify only the arguments
// that match the handler's parameter list.
// In this code, both of the argument placeholders
// (boost::asio::placeholders::error
// and boost::asio::placeholders::bytes_transferred)
// could potentially have been removed,
// since they are not being used in handle_write().
std::cout << socket_.remote_endpoint().address().to_string() << ":" << socket_.remote_endpoint().port() << " connected!" << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(m_message),
boost::bind(&tcp_connection::handle_write, shared_from_this()));
boost::asio::async_read_until(socket_,
_buffer,
'\n',
boost::bind(&tcp_connection::handle_receive,
shared_from_this(),
boost::asio::placeholders::error));
}
private:
tcp_connection(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
// handle_write() is responsible for any further actions
// for this client connection.
void handle_write() // call back.. when it finishes sending, we come here
{
std::cout << "Client has received the messaged. " << std::endl;
}
void handle_receive(const boost::system::error_code& ErrorCode)
{
std::cout << "You received the following message from the server: "<< std::endl;
if (ErrorCode) {
std::cout << "Error occured: " << ErrorCode.message() << std::endl;
return;
}
std::string line;
std::istream is(&_buffer);
std::getline(is, line);
std::cout << line << std::endl;
}
tcp::socket socket_;
std::string m_message;
boost::asio::streambuf _buffer;
};
Modified Client Code:
class Connection: public boost::enable_shared_from_this<Connection>
{
public:
Connection(boost::asio::io_service& io) : _socket(io){}
void connect(tcp::resolver::iterator& point)
{
boost::asio::async_connect(_socket, point, boost::bind(&Connection::onConnected, this, boost::asio::placeholders::error));
}
void onConnected(const boost::system::error_code& ErrorCode)
{
std::cout << "You are connected" << std::endl;
// receive first message on onReceive
boost::asio::async_read_until(_socket,
_buffer,
'\n',
boost::bind(&Connection::onReceive,
this, boost::asio::placeholders::error));
}
void onSend(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "Sending..." << std::endl;
}
void onReceive(const boost::system::error_code& ErrorCode)
{
std::cout << "You received the following message from the server:" << std::endl;
//std::cout.write(_buffer.data(), bytes_transferred);
// send first message on onSend
m_message = make_daytime_string() + '\n';
std::cout << "Sending " << m_message << std::endl;
boost::asio::async_write(_socket, boost::asio::buffer(m_message), boost::bind(&Connection::onSend, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
tcp::socket& getSocket()
{
return _socket;
}
private:
tcp::socket _socket;
boost::asio::streambuf _buffer;
std::string m_message;
};
Currently I'm looking into sending data with Boost ASIO. I understand that io_service is a abstraction of the underlying OS-dependend IO functionality and that the run() call will poll all outstanding handles in the asio queue and finishs afterwards.
But looking at the SSL example of asio (see below for code) I don't know how I can manage to keep the connection (or session, or whatever) open and read and write data over this connection outside of the normal 'workflow' of the client.
That means: if I call my send() method of the client right in handle_handshake the message is send. But if I try to call c.send() from my main method, nothing happens after Client Handshake success.
Why is send() handled different in the descriped scenario? Is io_service already finished?
Note: Please do not scare about the amount of code below. It is the example code provided with ASIO standalone. I only added a custom send() method to the client and a main to execute client and server in one running example.
Edit
I also tried using asio::io_service::work work(io_service); but the client still does nothing after the handshake.
class session{
public:
session(asio::io_service& io_service,
asio::ssl::context& context)
: socket_(io_service, context) { }
ssl_socket::lowest_layer_type& socket(){
return socket_.lowest_layer();
}
void start(){
std::cout << " Session start "<< "\n";
socket_.async_handshake(asio::ssl::stream_base::server,
std::bind(&session::handle_handshake, this,
std::placeholders::_1));
}
void handle_handshake(const asio::error_code& error){
if (!error){
std::cout << " Session handshake success "<< "\n";
socket_.async_read_some(asio::buffer(data_, max_length),
std::bind(&session::handle_read, this,
std::placeholders::_1, std::placeholders::_2));
}
else {
std::cout << " Session handshake failed "<< "\n";
delete this;
}
}
void handle_read(const asio::error_code& error,
size_t bytes_transferred) {
if (!error) {
std::cout << " Session Read success "<< "\n";
asio::async_write(socket_,
asio::buffer(data_, bytes_transferred),
std::bind(&session::handle_write, this,
std::placeholders::_1));
}
else {
std::cout << " Session Read failed "<< "\n";
delete this;
}
}
void handle_write(const asio::error_code& error){
if (!error){
std::cout << " Write success "<< "\n";
socket_.async_read_some(asio::buffer(data_, max_length),
std::bind(&session::handle_read, this,
std::placeholders::_1,
std::placeholders::_2));
}
else{
delete this;
}
}
private:
ssl_socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(asio::io_service& io_service, unsigned short port)
: io_service_(io_service),
acceptor_(io_service,
asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port)),
context_(asio::ssl::context::sslv23) {
context_.set_options(
asio::ssl::context::default_workarounds
| asio::ssl::context::no_sslv2
| asio::ssl::context::single_dh_use);
context_.set_password_callback(std::bind(&server::get_password, this));
context_.use_certificate_chain_file("server.pem");
context_.use_private_key_file("server.pem", asio::ssl::context::pem);
context_.use_tmp_dh_file("dh1024.pem");
start_accept();
}
std::string get_password() const{
return "test";
}
void start_accept(){
std::cout << " Server accept start "<< "\n";
session* new_session = new session(io_service_, context_);
acceptor_.async_accept(new_session->socket(),
std::bind(&server::handle_accept, this, new_session,
std::placeholders::_1));
}
void handle_accept(session* new_session,
const asio::error_code& error){
if (!error){
std::cout << " Server Accept success "<< "\n";
new_session->start();
}
else
{
delete new_session;
}
start_accept();
}
private:
asio::io_service& io_service_;
asio::ip::tcp::acceptor acceptor_;
asio::ssl::context context_;
};
enum { max_length = 1024 };
class client
{
public:
client(asio::io_service& io_service,
asio::ssl::context& context,
asio::ip::tcp::resolver::iterator endpoint_iterator)
: socket_(io_service, context) {
socket_.set_verify_mode(asio::ssl::verify_peer);
socket_.set_verify_callback(
std::bind(&client::verify_certificate, this, std::placeholders::_1, std::placeholders::_2));
asio::async_connect(socket_.lowest_layer(), endpoint_iterator,
std::bind(&client::handle_connect, this,
std::placeholders::_1));
}
bool verify_certificate(bool preverified,
asio::ssl::verify_context& ctx){
return true;
}
void handle_connect(const asio::error_code& error){
if (!error){
std::cout << "Client Connect success "<< "\n";
socket_.async_handshake(asio::ssl::stream_base::client,
std::bind(&client::handle_handshake, this,
std::placeholders::_1));
}
else {
std::cout << "Client Connect failed: " << error.message() << "\n";
}
}
void handle_handshake(const asio::error_code& error){
if (!error) {
std::cout << "Client Handshake success "<< "\n";
//send("test") no send here
}
else{
std::cout << "Client Handshake failed: " << error.message() << "\n";
}
}
void send(char * request_){
std::cout << " Client Sende daten "<< "\n";
size_t request_length = strlen(request_);
asio::async_write(socket_,
asio::buffer(request_, request_length),
std::bind(&client::handle_write, this,
std::placeholders::_1,
std::placeholders::_2));
}
void handle_write(const asio::error_code& error, size_t bytes_transferred){
if (!error)
{
std::cout << " Client Write success "<< "\n";
asio::async_read(socket_,
asio::buffer(reply_, bytes_transferred),
std::bind(&client::handle_read, this,
std::placeholders::_1,
std::placeholders::_2));
}
else {
std::cout << "Client Write failed: " << error.message() << "\n";
}
}
void handle_read(const asio::error_code& error, size_t bytes_transferred) {
if (!error) {
std::cout << "Client Reply: ";
std::cout.write(reply_, bytes_transferred);
std::cout << "\n";
}
else {
std::cout << "Client Read failed: " << error.message() << "\n";
}
}
private:
asio::ssl::stream<asio::ip::tcp::socket> socket_;
char request_[max_length];
char reply_[max_length];
};
using namespace std;
int main(){
std::thread t {
[](){
asio::io_service server_service;
server s(server_service, 8877);
server_service.run();
}
};
std::this_thread::sleep_for(std::chrono::milliseconds(3000));
asio::io_service io_service;
asio::ip::tcp::resolver resolver(io_service);
asio::ip::tcp::resolver::query query("127.0.0.1", std::to_string(8877));
asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
asio::ssl::context ctx(asio::ssl::context::sslv23);
ctx.load_verify_file("ca.pem");
client c(io_service, ctx, iterator);
c.send("test");
io_service.run();
t.join();
return 0;
}
The code bellow is mainly the HTTP client example with very few changes to support a download deadline.
It works as expected, but in rare cases e.g. if the internet is unstable, it doesn't work and the deadline can be more than what I set (20 or more seconds when I set 10). This happens very rarely and I am unable to reproduce this, it happens when I don't expect it.
To avoid posting a ton of lines (because few will read them) here is the place where I believe the error lies:
deadline_.expires_from_now(boost::posix_time::milliseconds(deadline));
tcp::resolver::query query(server, "http");
resolver_.async_resolve(query,
boost::bind(&client::handle_resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
deadline_.async_wait(boost::bind(&client::check_deadline, this));
Is the order of those lines correct?
And here is the check deadline function:
void check_deadline()
{
if(deadline_cancelled)
return;
else if (deadline_.expires_at() <= deadline_timer::traits_type::now())
socket_.close();
else
deadline_.async_wait(boost::bind(&client::check_deadline, this));
}
You should async_wait() on the deadline timer too. If you don't, you won't get notified, you just check (after the fact) whether the time had expired.
Then if it completes (with an ec other than operation_aborted) then you should
cancel() the async operations on the socket
optionally close the socket
PS. Mmm. It /seems/ that you are doing something similar, although it's unclear where
deadline_cancelled comes from
why you don't accept the error_code in the completion handler for deadline_.async_await and
why you are juggling with time comparisons manually, instead of trusting that the completion handler means what it says
Update Here's a full example doing a HTTP request. In fact, it downloads a million digits of PI from http://www.angio.net/pi/digits.html. This takes a while.
At the start of receiving the response I set a deadline timer for 800ms (and so the transfer should be - correctly - aborted).
This works as advertised. Pay special attention to the canceling of the socket and timer. Note that you could call expires_from_now() again after receiving each chunk of data. This is likely what you want. It will implicitly cancel() the timer each time it hadn't yet expired, so be prepared to handle the operatorion_aborted messages.
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/deadline_timer.hpp>
class client
{
public:
client(boost::asio::io_service& io_service,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
: deadline_(io_service),
socket_(io_service)
{
boost::asio::async_connect(socket_.lowest_layer(), endpoint_iterator,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
}
void handle_connect(const boost::system::error_code& error)
{
if (!error)
{
std::cout << "Enter message: ";
static char const raw[] = "GET /pi/digits/pi1000000.txt HTTP/1.1\r\nHost: www.angio.net\r\nConnection: close\r\n\r\n";
static_assert(sizeof(raw)<=sizeof(request_), "too large");
size_t request_length = strlen(raw);
std::copy(raw, raw+request_length, request_);
boost::asio::async_write(socket_,
boost::asio::buffer(request_, request_length),
boost::bind(&client::handle_write, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "Handshake failed: " << error.message() << "\n";
}
}
void deadline_expiration(const boost::system::error_code& error)
{
if (error == boost::asio::error::operation_aborted)
return;
std::cout << "\nDEADLINE REACHED\n";
socket_.cancel();
}
void handle_write(const boost::system::error_code& error,
size_t /*bytes_transferred*/)
{
if (!error)
{
std::cout << "starting read loop\n";
deadline_.expires_from_now(boost::posix_time::millisec(800));
//deadline_.expires_from_now(boost::posix_time::seconds(800));
deadline_.async_wait(boost::bind(&client::deadline_expiration, this, boost::asio::placeholders::error));
boost::asio::async_read_until(socket_,
//boost::asio::buffer(reply_, sizeof(reply_)),
reply_, '\n',
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "Write failed: " << error.message() << "\n";
}
}
void handle_read(const boost::system::error_code& error, size_t /*bytes_transferred*/)
{
if (!error)
{
std::cout << "Reply: " << &reply_ << "\n";
boost::asio::async_read_until(socket_,
//boost::asio::buffer(reply_, sizeof(reply_)),
reply_, '\n',
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "Read failed: " << error.message() << "\n";
deadline_.cancel(); // no need for after transfer completed
}
}
private:
boost::asio::deadline_timer deadline_;
boost::asio::ip::tcp::socket socket_;
char request_[1024];
boost::asio::streambuf reply_;
};
int main()
{
try
{
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query("www.angio.net", "80");
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
client c(io_service, iterator);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
}
Coliru link (Coliru doesn't support internet connectivity)