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.
I need to implement a class which handle connect to ssl server. Pretty much
based on this. However. it doesn't have reconnect feature. So I
modify it like this:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
to
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> *mpSocket_;
and refactor everything related to ->
But it leads to errors like this:
/usr/include/boost/asio/impl/read.hpp:271: error: request for member 'async_read_some' in '((boost::asio::detail::read_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*, boost::asio::mutable_buffers_1, boost::asio::detail::transfer_all_t, boost::_bi::bind_t<void, boost::_mfi::mf2<void, SSLHandler, const boost::system::error_code&, long unsigned int>, boost::_bi::list3<boost::_bi::value<SSLHandler*>, boost::arg<1> (*)(), boost::arg<2> (*)()> > >*)this)->boost::asio::detail::read_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*, boost::asio::mutable_buffers_1, boost::asio::detail::transfer_all_t, boost::_bi::bind_t<void, boost::_mfi::mf2<void, SSLHandler, const boost::system::error_code&, long unsigned int>, boost::_bi::list3<boost::_bi::value<SSLHandler*>, boost::arg<1> (*)(), boost::arg<2> (*)()> > >::stream_', which is of pointer type 'boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*' (maybe you meant to use '->' ?)
stream_.async_read_some(
^
/usr/include/boost/asio/impl/write.hpp:258: error: request for member 'async_write_some' in '((boost::asio::detail::write_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*, boost::asio::mutable_buffers_1, boost::asio::detail::transfer_all_t, boost::_bi::bind_t<void, boost::_mfi::mf2<void, SSLHandler, const boost::system::error_code&, long unsigned int>, boost::_bi::list3<boost::_bi::value<SSLHandler*>, boost::arg<1> (*)(), boost::arg<2> (*)()> > >*)this)->boost::asio::detail::write_op<boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*, boost::asio::mutable_buffers_1, boost::asio::detail::transfer_all_t, boost::_bi::bind_t<void, boost::_mfi::mf2<void, SSLHandler, const boost::system::error_code&, long unsigned int>, boost::_bi::list3<boost::_bi::value<SSLHandler*>, boost::arg<1> (*)(), boost::arg<2> (*)()> > >::stream_', which is of pointer type 'boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*' (maybe you meant to use '->' ?)
stream_.async_write_some(
^
Then I tried to dereference the pointer to keep the old structure but there's new error :(
boost::asio::async_connect(*socket_.lowest_layer(), mEndpointIterator, boost::bind(&SSLHandler::handle_connect, this, boost::asio::placeholders::error));
error: request for member 'lowest_layer' in '((SSLHandler*)this)->SSLHandler::socket_', which is of pointer type 'boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >*' (maybe you meant to use '->' ?)
boost::asio::async_connect(*socket_.lowest_layer(), mEndpointIterator, boost::bind(&SSLHandler::handle_connect, this, boost::asio::placeholders::error));`
Please help, I'm coming from java so it's quite complicated with me this thing.
This is my minimal change to that demo in Boost 1.66.0. See the patch in isolation on github: https://github.com/boostorg/asio/compare/develop...sehe:so-q49122521
Note: I moved the address resolve into the connect sequence, because if the network configuration has been changed, the result may differ, or another one of the endpoints should be preferred.
For this end, we store a resolver::query query_ member so we can repeat the query on reconnect.
//
// client.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2018 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)
//
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
enum { max_length = 1024 };
class client
{
public:
client(boost::asio::io_context& io_context,
boost::asio::ssl::context& context,
boost::asio::ip::tcp::resolver::query query)
: socket_(io_context, context), query_(query), timer_(io_context)
{
socket_.set_verify_mode(boost::asio::ssl::verify_peer);
socket_.set_verify_callback(
boost::bind(&client::verify_certificate, this, _1, _2));
start_connect();
}
void start_connect() {
boost::asio::ip::tcp::resolver r(socket_.get_io_context());
boost::asio::async_connect(socket_.lowest_layer(), r.resolve(query_),
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
}
void do_reconnect() {
timer_.expires_from_now(boost::posix_time::millisec(500));
timer_.async_wait(boost::bind(&client::handle_reconnect_timer, this, boost::asio::placeholders::error));
}
void handle_reconnect_timer(boost::system::error_code ec) {
if (!ec) {
start_connect();
}
}
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& error)
{
if (!error)
{
socket_.async_handshake(boost::asio::ssl::stream_base::client,
boost::bind(&client::handle_handshake, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Connect failed: " << error.message() << "\n";
do_reconnect();
}
}
void accept_message() {
std::cout << "Enter message: ";
std::cin.getline(request_, max_length);
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));
}
void handle_handshake(const boost::system::error_code& error)
{
if (!error)
{
accept_message();
}
else
{
std::cout << "Handshake failed: " << error.message() << "\n";
do_reconnect();
}
}
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.message() << "\n";
do_reconnect();
}
}
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";
accept_message(); // continue using the same socket_ until fail
}
else
{
std::cout << "Read failed: " << error.message() << "\n";
do_reconnect();
}
}
private:
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
boost::asio::ip::tcp::resolver::query query_;
boost::asio::deadline_timer timer_;
char request_[max_length];
char reply_[max_length];
};
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;
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
ctx.load_verify_file("ca.pem");
client c(io_context, ctx, {argv[1], argv[2]});
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
Here it is live demo-ed:
Further Thoughts
Depending on your level of paranoia you could feel better actually closing the ssl stream in do_reconnect():
boost::system::error_code ec;
socket_.shutdown(ec);
if (ec) std::cout << "shutdown error: " << ec.message() << std::endl;
That also works. You could even decide to kill off any lowest level connection just in case:
auto& ll = socket_.lowest_layer();
if (ll.is_open())
{
ll.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
//if (ec) std::cout << "socket.shutdown error: " << ec.message() << std::endl;
ll.close(ec);
//if (ec) std::cout << "socket.close error: " << ec.message() << std::endl;
}
Using a dynamically allocated socket
As mentioned, the purest solution would be to not reuse the stream/socket objects:
boost::optional<stream> socket_;
Now, updating all references to indirect socket_, do_reconnect() can become:
void do_reconnect() {
auto& io_context = socket_->get_io_context();
{
boost::system::error_code ec;
socket_->shutdown(ec);
if (ec) std::cout << "shutdown error: " << ec.message() << std::endl;
}
socket_.emplace(io_context, context_);
timer_.expires_from_now(boost::posix_time::millisec(500));
timer_.async_wait(boost::bind(&client::handle_reconnect_timer, this, boost::asio::placeholders::error));
}
Which obviously also works.
Here's the corresponding patch: https://github.com/boostorg/asio/compare/develop...sehe:so-q49122521-dynamic
//
// client.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2018 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)
//
#include <cstdlib>
#include <iostream>
#include <boost/optional.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
enum { max_length = 1024 };
namespace ssl = boost::asio::ssl;
using tcp = boost::asio::ip::tcp;
class client
{
using stream = ssl::stream<tcp::socket>;
public:
client(boost::asio::io_context& io_context, ssl::context& context, tcp::resolver::query query)
: context_(context), socket_(boost::in_place_init, io_context, context_), query_(query), timer_(io_context)
{
socket_->set_verify_mode(ssl::verify_peer);
socket_->set_verify_callback(
boost::bind(&client::verify_certificate, this, _1, _2));
start_connect();
}
void start_connect() {
tcp::resolver r(socket_->get_io_context());
boost::asio::async_connect(socket_->lowest_layer(), r.resolve(query_),
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
}
void do_reconnect() {
auto& io_context = socket_->get_io_context();
{
boost::system::error_code ec;
socket_->shutdown(ec);
if (ec) std::cout << "shutdown error: " << ec.message() << std::endl;
}
socket_.emplace(io_context, context_);
timer_.expires_from_now(boost::posix_time::millisec(500));
timer_.async_wait(boost::bind(&client::handle_reconnect_timer, this, boost::asio::placeholders::error));
}
void handle_reconnect_timer(boost::system::error_code ec) {
if (!ec) {
start_connect();
}
}
bool verify_certificate(bool preverified,
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& error)
{
if (!error)
{
socket_->async_handshake(ssl::stream_base::client,
boost::bind(&client::handle_handshake, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Connect failed: " << error.message() << "\n";
do_reconnect();
}
}
void accept_message() {
std::cout << "Enter message: ";
std::cin.getline(request_, max_length);
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));
}
void handle_handshake(const boost::system::error_code& error)
{
if (!error)
{
accept_message();
}
else
{
std::cout << "Handshake failed: " << error.message() << "\n";
do_reconnect();
}
}
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.message() << "\n";
do_reconnect();
}
}
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";
accept_message(); // continue using the same socket_ until fail
}
else
{
std::cout << "Read failed: " << error.message() << "\n";
do_reconnect();
}
}
private:
ssl::context& context_;
boost::optional<stream> socket_;
tcp::resolver::query query_;
boost::asio::deadline_timer timer_;
char request_[max_length];
char reply_[max_length];
};
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;
ssl::context ctx(ssl::context::sslv23);
ctx.load_verify_file("ca.pem");
client c(io_context, ctx, {argv[1], argv[2]});
io_context.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
I'm encapsulating the boost-asio socket, but I got an issue with it, but neither async_read nor async_write calls their callback function and I don't understand why.
I've tried using async_read_some but had the same issue.
Here's the code I've written so far
#include <iostream>
#include "socket.hpp"
Socket::Socket()
{
boost::asio::ip::tcp::endpoint ep_tmp(boost::asio::ip::tcp::v4(), 4242);
endpoint = ep_tmp;
acceptor = new boost::asio::ip::tcp::acceptor(ios, endpoint);
tcp_socket = new boost::asio::ip::tcp::socket(ios);
acceptor->listen();
}
Socket::~Socket()
{
delete(acceptor);
delete(tcp_socket);
}
void Socket::get_connection()
{
acceptor->async_accept(*tcp_socket, [](const boost::system::error_code &ec)
{
std::cout << "Connection received." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
});
this->exec();
}
void Socket::send(std::string &message)
{
async_write(*tcp_socket, boost::asio::buffer(message),
[](const boost::system::error_code &ec,
std::size_t bytes_transferred)
{
std::cout << "Sending datas." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
else
std::cout << bytes_transferred << " bytes transferred." << std::endl;
});
}
void Socket::receive(void)
{
char *buf;
buf = (char *)malloc(sizeof(char) * 50);
buf = (char *)memset(buf, 0, 50);
async_read(*tcp_socket, boost::asio::buffer(buf, 50),
[](const boost::system::error_code &ec,
std::size_t bytes_transferred)
{
std::cout << "Receiving datas." << std::endl;
if (ec)
std::cout << "Error " << ec << std::endl;
else
std::cout << bytes_transferred
<< " bytes transferred." << std::endl;
});
}
void Socket::exec(void)
{
ios.run();
}
int main()
{
Socket serv;
std::string data_test;
data_test = "Test\n";
serv.get_connection();
serv.send(data_test);
serv.exec();
serv.receive();
serv.exec();
return (0);
}
The malloc bit is temporary until I find a way to do it without using C.
I'd be really thankful if someone could enlighten me on that issue
You have to call io_service::reset before second and later calls to io_service::run. And you probably want to use synchronous API instead, as your current approach absolutely defeats the purpose of asynchronicity.
I'm with yuri: prefer non-async unless you know what you're doing.
It could look like this: http://coliru.stacked-crooked.com/a/523a7828a9aee4b2
#include <boost/asio.hpp>
#include <iostream>
namespace ba = boost::asio;
using ba::ip::tcp;
class Socket {
public:
Socket() { acceptor.listen(); }
void get_connection();
void exec();
void send(std::string const &message);
void receive(void);
private:
ba::io_service ios;
tcp::endpoint endpoint{ tcp::v4(), 4242 };
tcp::acceptor acceptor{ ios, endpoint };
tcp::socket tcp_socket{ ios };
};
void Socket::get_connection() {
acceptor.accept(tcp_socket);
std::cout << "Connection received.\n";
}
void Socket::send(std::string const &message) {
std::cout << "Sending datas.\n";
auto bytes_transferred = ba::write(tcp_socket, ba::buffer(message));
std::cout << bytes_transferred << " bytes transferred.\n";
}
void Socket::receive(void) {
std::cout << "Receiving datas.\n";
char buf[50] = { 0 };
auto bytes_transferred = ba::read(tcp_socket, ba::buffer(buf));
std::cout << bytes_transferred << " bytes transferred.\n";
}
int main() {
Socket serv;
serv.get_connection();
serv.send("Test\n");
serv.receive();
}
If you want async behaviour, you have to manage the lifetimes of each buffer/connection-specific resource. There are many examples of that, e.g. in the docs or here: http://coliru.stacked-crooked.com/a/95e2000e49b4db1d
On the perils of buffer lifetime: client server simple example nonblocking
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.
I'm looking at this example for making HTTP POST requests.
I'm interested about making an HTTPS POST request. How do I provide the location of .crt and .key file? Is there any example, possibly showing exception handling as well?
Here's the groundwork for a simple POST request.
If you define DEMO_USING_SSL you'll get SSL, otherwise no SSL
The line
ctx.set_default_verify_paths();
sets the verification paths so you should (normally/usually) pick up the system root certificates as trusted. Alternatively there are
ctx.add_verify_path(...);
ctx.add_certificate_authority(...);
Be sure to look at man c_rehash if you intend to use the (powerful) add_verify_path approach.
Now, you can even disable/tweak certificate verification. Or, indeed, add error handling as you mentioned in the OP:
ctx.set_verify_mode(...);
ctx.set_verify_depth(...);
ctx.set_password_callback(...); // for passphrases of private keys
ctx.set_verify_callback(VerifyCallback);
In the sample below, I show the latter
#define DEMO_USING_SSL
#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
#include <iostream>
#include <iomanip>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ssl.hpp>
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
#ifdef DEMO_USING_SSL
, context)
{
socket_.set_verify_mode(boost::asio::ssl::verify_peer);
socket_.set_verify_callback(
boost::bind(&client::verify_certificate, this, _1, _2));
#else
)
{
(void) context;
#endif
boost::asio::async_connect(socket_.lowest_layer(), endpoint_iterator,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
}
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& error)
{
#ifdef DEMO_USING_SSL
if (!error)
{
socket_.async_handshake(boost::asio::ssl::stream_base::client,
boost::bind(&client::handle_handshake, this,
boost::asio::placeholders::error));
}
else
{
std::cout << "Connect failed: " << error.message() << "\n";
}
#else
handle_handshake(error);
#endif
}
void handle_handshake(const boost::system::error_code& error)
{
if (!error)
{
std::cout << "Enter message: ";
static char const raw[] = "POST / HTTP/1.1\r\nHost: www.example.com\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_);
{
// used this for debugging:
std::ostream hexos(std::cout.rdbuf());
for(auto it = raw; it != raw+request_length; ++it)
hexos << std::hex << std::setw(2) << std::setfill('0') << std::showbase << ((short unsigned) *it) << " ";
std::cout << "\n";
}
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 handle_write(const boost::system::error_code& error,
size_t /*bytes_transferred*/)
{
if (!error)
{
std::cout << "starting read loop\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 << "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";
}
else
{
std::cout << "Read failed: " << error.message() << "\n";
}
}
private:
#ifdef DEMO_USING_SSL
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket_;
#else
boost::asio::ip::tcp::socket socket_;
#endif
char request_[1024];
boost::asio::streambuf reply_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 3)
{
std::cerr << "Usage: client <host> <port>\n";
return 1;
}
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query(argv[1], argv[2]);
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
ctx.set_default_verify_paths();
client c(io_service, ctx, iterator);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}