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.
Related
I'm trying to write server with boost::asio but I want that the boost::asio::async_read operation to be with time out if no data is coming, but i can figure how to do it.
this is my code so far
void do_read_header() {
auto self(shared_from_this());
std::cout << "do_read_header\n";
boost::asio::async_read(
socket_, boost::asio::buffer(res.data(), res.header_length),
[this, self](boost::system::error_code ec,
std::size_t length) {
if (!ec && res.decode_header()) {
do_read_body();
}
});
do_write();
}
void do_read_body() {
auto self(shared_from_this());
Message msg;
std::cout << "do_read_body\n";
boost::asio::async_read(
socket_, boost::asio::buffer(res.body(), res.body_length()),
[this, self](boost::system::error_code ec,
std::size_t length) {
if (!length) {
return;
}
if (!ec) {
try {
std::cout << "read " << res.body() << "\n";
request_queue_.send(res.body(), res.body_length(),
0);
} catch (const std::exception& ex) {
std::cout << ex.what() << "\n";
}
} else {
if (ec) {
std::cerr << "read error:" << ec.value()
<< " message: " << ec.message() << "\n";
}
socket_.close();
}
do_read_header();
});
}
void start() {
post(strand_, [this, self = shared_from_this()] {
do_read_header();
do_write();
});
}
class Server {
public:
Server(boost::asio::io_service& io_service, short port)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
socket_(io_service) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(
socket_, [this](boost::system::error_code ec) {
if (!ec) {
std::cout << "accept connection\n";
std::make_shared<Session>(std::move(socket_))
->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
You can add a deadline timer that cancels the IO operation. You can observe the cancellation because the completion will be called with error::operation_aborted.
deadline_.expires_from_now(1s);
deadline_.async_wait([self, this] (error_code ec) {
if (!ec) socket_.cancel();
});
I spent about 45 minutes making the rest of your code self-contained:
in this example I'll assume that we
want to wait for max 5s for a new header to arrive (so after a new session was started or until the next request arrives on the same session)
after which the fullbody must be received within 1s
Note also that we avoid closing the socket - that's done in the session's destructor. It's better to shutdown gracefully.
Live Demo
#include <boost/asio.hpp>
#include <boost/endian/arithmetic.hpp>
#include <boost/interprocess/ipc/message_queue.hpp>
#include <iomanip>
#include <iostream>
using namespace std::chrono_literals;
namespace bip = boost::interprocess;
using boost::asio::ip::tcp;
using boost::system::error_code;
using Queue = boost::interprocess::message_queue;
static constexpr auto MAX_MESG_LEN = 100;
static constexpr auto MAX_MESGS = 10;
struct Message {
using Len = boost::endian::big_uint32_t;
struct header_t {
Len len;
};
static const auto header_length = sizeof(header_t);
std::array<char, MAX_MESG_LEN + header_length> buf;
char const* data() const { return buf.data(); }
char* data() { return buf.data(); }
char const* body() const { return data() + header_length; }
char* body() { return data() + header_length; }
static_assert(std::is_standard_layout_v<header_t> and
std::is_trivial_v<header_t>);
Len body_length() const { return std::min(h().len, max_body_length()); }
Len max_body_length() const { return buf.max_size() - header_length; }
bool decode_header() { return h().len <= max_body_length(); }
bool set_body(std::string_view value) {
assert(value.length() <= max_body_length());
h().len = value.length();
std::copy_n(value.begin(), body_length(), body());
return (value.length() == body_length()); // not truncated
}
private:
header_t& h() { return *reinterpret_cast<header_t*>(data()); }
header_t const& h() const { return *reinterpret_cast<header_t const*>(data()); }
};
struct Session : std::enable_shared_from_this<Session> {
Session(tcp::socket&& s) : socket_(std::move(s)) {}
void start() {
post(strand_,
[ this, self = shared_from_this() ] { do_read_header(); });
}
private:
using Strand = boost::asio::strand<tcp::socket::executor_type>;
using Timer = boost::asio::steady_timer;
tcp::socket socket_{strand_};
Strand strand_{make_strand(socket_.get_executor())};
Message res;
Queue request_queue_{bip::open_or_create, "SendQueue", MAX_MESGS, MAX_MESG_LEN};
Timer recv_deadline_{strand_};
void do_read_header() {
auto self(shared_from_this());
std::cout << "do_read_header: " << res.header_length << std::endl;
recv_deadline_.expires_from_now(5s);
recv_deadline_.async_wait([ self, this ](error_code ec) {
if (!ec) {
std::cerr << "header timeout" << std::endl;
socket_.cancel();
}
});
boost::asio::async_read(
socket_, boost::asio::buffer(res.data(), res.header_length),
[ this, self ](error_code ec, size_t /*length*/) {
std::cerr << "header: " << ec.message() << std::endl;
recv_deadline_.cancel();
if (!ec && res.decode_header()) {
do_read_body();
} else {
socket_.shutdown(tcp::socket::shutdown_both);
}
});
}
void do_read_body() {
auto self(shared_from_this());
// Message msg;
std::cout << "do_read_body: " << res.body_length() << std::endl;
recv_deadline_.expires_from_now(1s);
recv_deadline_.async_wait([self, this] (error_code ec) {
if (!ec) {
std::cerr << "body timeout" << std::endl;
socket_.cancel();
}
});
boost::asio::async_read(
socket_,
boost::asio::buffer(res.body(), res.body_length()),
boost::asio::transfer_exactly(res.body_length()),
[ this, self ](error_code ec, std::size_t length) {
std::cerr << "body: " << ec.message() << std::endl;
recv_deadline_.cancel();
if (!ec) {
try {
// Not safe to print unless NUL-terminated, see e.g.
// https://stackoverflow.com/questions/66278813/boost-deadline-timer-causes-stack-buffer-overflow/66279497#66279497
if (length)
request_queue_.send(res.body(), res.body_length(), 0);
} catch (const std::exception& ex) {
std::cout << ex.what() << std::endl;
}
do_read_header();
} else {
socket_.shutdown(tcp::socket::shutdown_both);
}
});
}
};
class Server {
public:
Server(boost::asio::io_service& io_service, short port)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
socket_(io_service) {
do_accept();
}
private:
void do_accept() {
acceptor_.async_accept(socket_, [ this ](error_code ec) {
std::cerr << "async_accept: " << ec.message() << std::endl;
if (!ec) {
std::cerr << "session: " << socket_.remote_endpoint() << std::endl;
std::make_shared<Session>(std::move(socket_))->start();
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
int main(int argc, char**) {
Queue queue{bip::open_or_create, "SendQueue", MAX_MESGS, MAX_MESG_LEN}; // ensure it exists
if (argc == 1) {
boost::asio::io_context ioc;
Server s(ioc, 8989);
ioc.run_for(10s);
} else {
while (true) {
using Buf = std::array<char, MAX_MESG_LEN>;
Buf buf;
unsigned prio;
size_t n;
queue.receive(buf.data(), buf.size(), n, prio);
std::cout << "Received: " << std::quoted(std::string_view(buf.data(), n)) << std::endl;
}
}
}
Testable with
./sotest
In another terminal:
./sotest consumer
And somewhere else e.g. some requests that don't timeout:
for msg in '0000 0000' '0000 0001 31' '0000 000c 6865 6c6c 6f20 776f 726c 640a'
do
xxd -r -p <<< "$msg" |
netcat localhost 8989 -w 1
done
Or, multi-request on single session, then session times out (-w 6 exceeds 5s):
msg='0000 0000 0000 0001 31 0000 000c 6865 6c6c 6f20 776f 726c 640a'; xxd -r -p <<< "$msg"| netcat localhost 8989 -w 6
I had to solve a similar issue when implementing the Serial Port wrapper and this is the simplified version of the code:
// Possible outcome of a read. Set by callback
enum class ReadResult
{
ResultInProgress,
ResultSuccess,
ResultError,
ResultTimeout
};
std::streamsize read(char* s, std::streamsize n, boost::posix_time::time_duration timeout)
{
boost::asio::io_service io;
boost::asio::serial_port port(io);
// result is atomic to avoid race condition
std::atomic<ReadResult> result(ReadResult::ResultInProgress);
std::streamsize bytesTransferred = 0;
// Create async timer that fires after duration
boost::asio::deadline_timer timer(io);
timer.expires_from_now(timeout);
timer.async_wait(boost::bind([&result](const boost::system::error_code& error){
// If there wasn't any error and reading is still in progress, set result as timeout
if (!error && result == ReadResult::ResultInProgress)
result = ReadResult::ResultTimeout;
},boost::asio::placeholders::error));
// Read asynchronously
port.async_read_some(boost::asio::buffer(s, n), boost::bind([&result,&bytesTransferred](const boost::system::error_code& error,
const size_t transferred){
// If there wasn't any error on read finish set result as sucess else as error
if (!error){
result = ReadResult::ResultSuccess;
bytesTransferred = transferred;
return;
}
result = ReadResult::ResultError;
},boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
// Run loop until timeout, error or success occurs
for (;;)
{
io.run_one();
switch (result)
{
case ReadResult::ResultSuccess:
// Success, cancel timer and return amount of bytes read
timer.cancel();
return bytesTransferred;
case ReadResult::ResultTimeout:
// Timeout occured, cancel read and throw exception
port.cancel();
throw(TimeoutException("Timeout expired"));
case ReadResult::ResultError:
// Error occured, cancel read and timer and throw exception
port.cancel();
timer.cancel();
throw(std::ios_base::failure("Error while reading"));
default:
//if result is still in progress remain in the loop
break;
}
}
}
So basically, what you have to do is :
initialize timer with io_service
call async_wait on a timer with a callback function that sets result timeout flag
call async_read on your socket with a callback that sets result success flag
loop until result is no longer "InProgress", note that io.run_one() in loop is important
handle result the result
you could use it for any asynchronous function
I try to open a UDP server. A baby example works (I receive what I expect and what wireshark also shows):
Baby example:
int main(int argc, char* const argv[])
{
try
{
boost::asio::io_context io_context;
boost::asio::ip::udp::endpoint ep(boost::asio::ip::udp::v4(), 60001);
boost::asio::ip::udp::socket sock(io_context, ep);
UDPServer server(std::move(sock), callbackUDP);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
UDPServer.hpp:
#include <boost/asio.hpp>
#include <functional>
#include <vector>
#include <thread>
#define BUFFERSIZE 1501
class UDPServer
{
public:
explicit UDPServer(boost::asio::ip::udp::socket socket, std::function<void(const std::vector<char>&)> callbackFunction);
virtual ~UDPServer();
private:
void read();
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint endpoint_;
std::function<void(const std::vector<char>&)> callbackFunction_;
char data_[1500 + 1]; // 1500 bytes is safe limit as it is max of ethernet frame, +1 is for \0 terminator
};
UDPServer.cpp:
#include <iostream>
#include "UDPServer.h"
UDPServer::UDPServer(boost::asio::ip::udp::socket socket, std::function<void(const std::vector<char>&)> callbackFunction):
socket_(std::move(socket)),
callbackFunction_(callbackFunction)
{
read();
}
UDPServer::~UDPServer()
{
}
void UDPServer::read()
{
socket_.async_receive_from(boost::asio::buffer(data_, 1500), endpoint_,
[this](boost::system::error_code ec, std::size_t length)
{
if (ec)
{
return;
}
data_[length] = '\0';
if (strcmp(data_, "\n") == 0)
{
return;
}
std::vector<char> dataVector(data_, data_ + length);
callbackFunction_(dataVector);
read();
}
);
}
Now what I want to convert this to is a class with as constructor only the port and a callback function (let forget about the latter and just print the message for now, adding the callback is normally no problem).
I tried the following, but it doesn't work:
int main(int argc, char* const argv[])
{
UDPServer server(60001);
}
UDPServer.h:
#include <boost/asio.hpp>
#include <functional>
#include <vector>
#include <thread>
#define BUFFERSIZE 1501
class UDPServer
{
public:
explicit UDPServer(uint16_t port);
virtual ~UDPServer();
private:
boost::asio::io_context io_context_;
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint endpoint_;
std::array<char, BUFFERSIZE> recv_buffer_;
std::thread thread_;
void run();
void start_receive();
void handle_reply(const boost::system::error_code& error, std::size_t bytes_transferred);
};
UDPServer.cpp:
#include <iostream>
#include "UDPServer.h"
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <iostream>
UDPServer::UDPServer(uint16_t port):
endpoint_(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
io_context_(),
socket_(io_context_, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
thread_(&UDPServer::run, this)
{
start_receive();
}
UDPServer::~UDPServer()
{
io_context_.stop();
thread_.join();
}
void UDPServer::start_receive()
{
socket_.async_receive_from(boost::asio::buffer(recv_buffer_), endpoint_,
boost::bind(&UDPServer::handle_reply, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void UDPServer::handle_reply(const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (!error)
{
try {
std::string string(recv_buffer_.data(), recv_buffer_.data() + bytes_transferred);
std::cout << "Message received: " << std::to_string(bytes_transferred) << ", " << string << std::endl;
}
catch (std::exception ex) {
std::cout << "handle_reply: Error parsing incoming message:" << ex.what() << std::endl;
}
catch (...)
{
std::cout << "handle_reply: Unknown error while parsing incoming message" << std::endl;
}
}
else
{
std::cout << "handle_reply: error: " << error.message() << std::endl;
}
start_receive();
}
void UDPServer::run()
{
try {
io_context_.run();
} catch( const std::exception& e )
{
std::cout << "Server network exception: " << e.what() << std::endl;
}
catch(...)
{
std::cout << "Unknown exception in server network thread" << std::endl;
}
std::cout << "Server network thread stopped" << std::endl;
};
When running I get "Server network thread stopped". io_context doesn't seem to start and doesn't block. Someone an idea what I do wrong? Thanks a lot!
EDIT tried this after comment, same result (except that message comes after 1 second)
UDPServer::UDPServer(uint16_t port):
endpoint_(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
io_context_(),
socket_(io_context_, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port))
{
start_receive();
std::this_thread::sleep_for (std::chrono::seconds(1));
thread_ = std::thread(&UDPServer::run, this);
}
Your destructor explicitly tells the service to stop:
UDPServer::~UDPServer() {
io_context_.stop();
thread_.join();
}
That's part of your problem. The other part is as pointed out in the comment: you have a race condition where the thread exits before you even post your first async operation.
Solve it by adding a work guard:
boost::asio::io_context io_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work_ {io_.get_executor()};
Now the destructor can be:
UDPServer::~UDPServer() {
work_.reset(); // allow service to run out of work
thread_.join();
}
Other notes:
avoid chaining back to start_receive when there was an error
std::to_string was redundant
the order of initialization for members is defined by the order of their declaration, not their initializers in the initializer list. Catch these bug sources with -Wall -Wextra -pedantic
= handle exceptions in your service thread (see Should the exception thrown by boost::asio::io_service::run() be caught?)
I'd suggest std::bind over boost::bind:
std::bind(&UDPServer::handle_reply, this,
std::placeholders::_1,
std::placeholders::_2));
Or just use a lambda:
[this](error_code ec, size_t xfer) { handle_reply(ec, xfer); });
LIVE DEMO
Compiler Explorer
#include <boost/asio.hpp>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <thread>
#include <vector>
using boost::asio::ip::udp;
using boost::system::error_code;
using boost::asio::io_context;
#define BUFFERSIZE 1501
class UDPServer {
public:
explicit UDPServer(uint16_t port);
virtual ~UDPServer();
private:
io_context io_;
boost::asio::executor_work_guard<io_context::executor_type> work_ {io_.get_executor()};
udp::endpoint endpoint_;
udp::socket socket_;
std::array<char, BUFFERSIZE> recv_buffer_;
std::thread thread_;
void run();
void start_receive();
void handle_reply(const error_code& error, size_t transferred);
};
UDPServer::UDPServer(uint16_t port)
: endpoint_(udp::endpoint(udp::v4(), port)),
socket_(io_, endpoint_),
thread_(&UDPServer::run, this) {
start_receive();
}
UDPServer::~UDPServer() {
work_.reset(); // allow service to run out of work
thread_.join();
}
void UDPServer::start_receive() {
socket_.async_receive_from(boost::asio::buffer(recv_buffer_), endpoint_,
#if 0
std::bind(&UDPServer::handle_reply, this,
std::placeholders::_1,
std::placeholders::_2));
#else
[this](error_code ec, size_t xfer) { handle_reply(ec, xfer); });
#endif
}
void UDPServer::handle_reply(const error_code& error, size_t transferred) {
if (!error) {
try {
std::string_view s(recv_buffer_.data(), transferred);
std::cout << "Message received: " << transferred << ", "
<< std::quoted(s) << "\n";
} catch (std::exception const& ex) {
std::cout << "handle_reply: Error parsing incoming message:"
<< ex.what() << "\n";
} catch (...) {
std::cout
<< "handle_reply: Unknown error while parsing incoming message\n";
}
start_receive();
} else {
std::cout << "handle_reply: error: " << error.message() << "\n";
}
}
void UDPServer::run() {
while (true) {
try {
if (io_.run() == 0u) {
break;
}
} catch (const std::exception& e) {
std::cout << "Server network exception: " << e.what() << "\n";
} catch (...) {
std::cout << "Unknown exception in server network thread\n";
}
}
std::cout << "Server network thread stopped\n";
}
int main() {
std::cout << std::unitbuf;
UDPServer server(60001);
}
Testing with random words:
sort -R /etc/dictionaries-common/words | while read w; do sleep 1; netcat -u localhost 60001 -w 0 <<<"$w"; done
Live output:
I have this piece of code that I've put together from various Boost sources to try and create something that will connect to a host and download the root page/file via SSL. The program runs but returns an empty reply. Could someone suggest why this is the case and suggest an improvement? Link with -l pthread -l ssl -l crypto.
//
// client.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2015 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 <iostream>
#include <asio.hpp>
#include <asio/ssl.hpp>
#include <functional>
static const char *HOSTNAME { "google.com" };
static const char *HOSTPORT { "https" };
std::string file { "/" };
std::string request {
"GET " + file + " "
+ "HTTP/1.1\r\nHost: " + std::string(HOSTNAME) + "\r\n\r\n" };
enum { max_length = 1024 };
class Client
{
asio::ssl::stream<asio::ip::tcp::socket> ssock;
char reply[max_length];
std::size_t bytes_transferred;
std::error_code ec;
public:
Client(asio::io_service &io_service, asio::ssl::context &context, asio::ip::tcp::resolver::iterator endpoint_iterator) :
ssock(io_service, context)
{
auto handle_connect { std::bind(&Client::handle_handshake, this, ec) };
asio::async_connect(ssock.lowest_layer(), endpoint_iterator, handle_connect);
}
void handle_connect(const std::error_code &ec)
{
if (!ec)
{
auto handle_handshake { std::bind(&Client::handle_handshake, this, ec) };
ssock.async_handshake(asio::ssl::stream_base::client, handle_handshake);
}
else std::cout << "Connect failed: " << ec.message() << "\n";
}
void handle_handshake(const std::error_code &ec)
{
if (!ec)
{
std::cout << request << std::endl;
auto handle_write { std::bind(&Client::handle_write, this, ec, bytes_transferred) };
asio::async_write(ssock, asio::buffer(request), handle_write);
}
else std::cout << "Handshake failed: " << ec.message() << "\n";
}
void handle_write(const std::error_code &ec, std::size_t bytes_transferred)
{
if (!ec)
{
auto handle_read { std::bind(&Client::handle_read, this, ec, bytes_transferred) };
asio::async_read(ssock, asio::buffer(reply, bytes_transferred), handle_read);
}
else std::cout << "Write failed: " << ec.message() << "\n";
}
void handle_read(const std::error_code &ec, std::size_t bytes_transferred)
{
if (!ec)
{
std::cout << "Reply: ";
std::cout.write(reply, bytes_transferred);
std::cout << "\n";
}
else std::cout << "Read failed: " << ec.message() << "\n";
}
};
int main(int argc, char* argv[])
{
try
{
asio::io_service io_service;
asio::ip::tcp::resolver resolver(io_service);
asio::ip::tcp::resolver::query query(HOSTNAME, HOSTPORT);
asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
asio::ssl::context ctx(asio::ssl::context::sslv23);
Client client(io_service, ctx, iterator);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
I think the problem is with these callbacks. The operations within the callbacks themselves are correct as they're taken straight from the example. Is there a better way to rewrite by using Lambdas for callbacks? I don't like the way these callbacks are bound by bind.
auto handle_connect { std::bind(&Client::handle_handshake, this, ec) };
in the constructor should be:
auto handle_connect { std::bind(&Client::handle_connect, this, ec) };
I am using Boost ASIO for my TCP network communication. Here is my code:
Server.h:
#ifndef VIBRANIUM_CORE_SERVER_H
#define VIBRANIUM_CORE_SERVER_H
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
#include <deque>
#include "Logger.h"
#include "Client.h"
#include "Protocol/ServerOpcode.h"
using boost::asio::ip::tcp;
namespace Vibranium {
class Server {
public:
Server(boost::asio::io_service &io_service, short port)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), port)),
socket_(io_service) {
do_accept();
Logger::Log("Server Started! Listening on Port("+std::to_string(port)+")", Logger::Success, true);
}
static std::deque<std::shared_ptr<Client>> Clients;
private:
void do_accept();
int incrementor;
tcp::acceptor acceptor_;
tcp::socket socket_;
};
}
#endif //VIBRANIUM_CORE_SERVER_H
Server.cpp:
#include "Server.h"
#include "Client.h"
using namespace Vibranium;
std::deque<std::shared_ptr<Client>> Server::Clients;
void Server::do_accept()
{
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec)
{
if (!ec)
{
incrementor++;
Logger::Log("New Connection (ID: " + std::to_string(incrementor) + ")",Logger::Success);
std::shared_ptr<Client> c = std::make_shared<Client>(std::move(socket_));
c->start();
c->connectionId = incrementor;
Server::Clients.push_back(c);
}
do_accept();
});
}
Client.h:
#ifndef VIBRANIUM_CORE_CLIENT_H
#define VIBRANIUM_CORE_CLIENT_H
#include <cstdlib>
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
#include "Protocol/ServerOpcode.h"
using boost::asio::ip::tcp;
namespace Vibranium{
class Client: public std::enable_shared_from_this<Client>
{
public:
Client(tcp::socket socket)
: socket(std::move(socket))
{
}
void start();
int connectionId;
tcp::socket socket;
void Send(ServerOpcode serverOpcode, const std::string& message);
private:
void do_read();
void do_write(std::size_t length);
enum { max_length = 1024 };
char data_[max_length];
};
}
#endif //VIBRANIUM_CORE_CLIENT_H
Client.cpp:
#include "Client.h"
#include "Server.h"
void Vibranium::Client::start() {
do_read();
}
void Vibranium::Client::do_read() {
auto self(shared_from_this());
socket.async_read_some(boost::asio::buffer(data_, max_length),
[this, self](boost::system::error_code ec, std::size_t length)
{
if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec))
{
Logger::Log("Disconnected ID: " + std::to_string(connectionId),Logger::Error, true);
for (int i = 0; i < Server::Clients.size(); ++i) {
if(Server::Clients[i]->connectionId == connectionId)
Server::Clients.erase(Server::Clients.begin()+i);
}
}
else
{
std::cout.write(data_, length);
std::cout << "\n";
//do_write(length);
Send(ServerOpcode::SMSG_AUTH_CONNECTION_RESPONSE,"How are you, mate?");
}
});
}
void Vibranium::Client::do_write(std::size_t length) {
auto self(shared_from_this());
boost::asio::async_write(socket, boost::asio::buffer(data_, length),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec)
{
do_read();
}
});
}
void Vibranium::Client::Send(ServerOpcode serverOpcode, const std::string& message) {
auto self(shared_from_this());
std::cout << "HERE!" << std::endl;
size_t request_length = std::strlen(message.c_str());
boost::asio::async_write(socket, boost::asio::buffer(message.c_str(), request_length),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec)
do_read();
else
Logger::Log("Message sent failed to " + std::to_string(connectionId),Logger::Error);
});
}
Here is how I simulate several connections to the server:
Config config("AuthServer");
std::string defaultIP = "127.0.0.1";
std::string defaultPort = "8080";
int connectionsNumber = CommandQuestion<int>::AskQuestion("How many connections do you want established?");
std::cout << "Initializing " << std::to_string(connectionsNumber) << " connection/s." << std::endl;
std::cout << "Trying to connect to " << defaultIP << " on port: " << config.GetConfigValue("AuthServerPort", defaultPort) << std::endl;
boost::asio::io_context io_context;
std::vector<tcp::socket> sockets;
for (int i = 0; i < connectionsNumber; ++i) {
try
{
sockets.emplace_back(io_context);
tcp::socket& s{sockets.back()};
tcp::resolver resolver(io_context);
boost::asio::connect(s, resolver.resolve( defaultIP,config.GetConfigValue("AuthServerPort", defaultPort)));
std::string message = "I am testing here!!!";
size_t request_length = std::strlen(message.c_str());
boost::asio::write(s, boost::asio::buffer(message, request_length));
char reply[max_length];
size_t reply_length = boost::asio::read(s,boost::asio::buffer(reply, request_length));
Logger::Log(std::to_string(i) + " Connected!",Logger::Success);
std::cout << "Reply is: ";
std::cout.write(reply, reply_length);
std::cout << "\n";
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
}
So basically I am establishing connection and then the clients sends a message to the server saying I am testing here!!! than I would like to use my function Send to send back to the client another message How are you, mate?.
However it does not work. It seems the message can't be seen back from the client. On server side I see the output of HERE! which is located in Send function. That means I reach that point.
On another hand if I uncomment //do_write(length); from function do_read() and comment back send() it seems to return back the message I am testing here!!!. So by my understanding communication works that way.
My question is where is my mistake with Send function? Why I can't make it send another message instead of just replying back with what the client sent in first place.
I found myself where the error was, here is how I solved it:
void Vibranium::Client::Send(ServerOpcode serverOpcode, const std::string& message) {
auto self(shared_from_this());
std::cout << "HERE!" << std::endl;
strcpy(data_, message.c_str());
size_t request_length = sizeof(data_)/sizeof(*data_);
boost::asio::async_write(socket, boost::asio::buffer(data_, request_length),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (ec)
Logger::Log("Message sent failed to " + std::to_string(connectionId),Logger::Error);
});
}
I was doing an echo-server example in boost::asio. But using boost::make_shard will cause "unknown exception" while std::make_shared will not. See the commented line.
I am using visual studio 2017 and boost 1.67.The commented part will crash while the uncommented part will not. It seems the boost lib is complied with vs141 in the boost/stage/lib.
#include <iostream>
#include <boost/asio.hpp>
#include <boost/make_shared.hpp>
using boost::asio::ip::tcp;
using std::cout;
using std::endl;
using std::size_t;
class session :public std::enable_shared_from_this<session> {
public:
session(tcp::socket socket) :socket_(std::move(socket)) {
}
void start() {
do_read();
}
private:
void do_read() {
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(buffer_, maxlen),
[this, self](boost::system::error_code error, size_t read_len) {
if (!error) {
do_write(read_len);
}
else {
cout << error.message() << endl;
}
});
}
void do_write(size_t len_write) {
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(buffer_, len_write),
[this, self](boost::system::error_code error, size_t byte_transferred) {
if (!error) {
do_read();
}
else {
cout << "error 3,only " << byte_transferred << " bytes writen" << endl;
}
});
}
tcp::socket socket_;
enum { maxlen = 1024 };
char buffer_[maxlen];
};
class server {
public:
server(boost::asio::io_context& io_context, short port)
:acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
start();
}
void start() {
acceptor_.async_accept(
[this](boost::system::error_code ec, tcp::socket socket)
{
if (!ec)
{
std::make_shared<session>(std::move(socket))->start();
// boost::make_shared<session>(std::move(socket))->start();
}
start();
});
}
private:
tcp::acceptor acceptor_;
};
int main(int argc, char **argv) {
if (argc != 2) {
cout << "usage: server.exe Port" << endl;
}
try {
boost::asio::io_context io_context;
server server(io_context, std::atoi(argv[1]));
io_context.run();
}
catch (std::exception e) {
cout << e.what() << endl;
}
}
When you want to use boost shared pointers
boost::make_shared<session>(std::move(socket))->start();
You must inherit your class from boost adapter class like below:
class session :public boost::enable_shared_from_this<session> {