The code bellow is mainly the HTTP client example with very few changes to support a download deadline.
It works as expected, but in rare cases e.g. if the internet is unstable, it doesn't work and the deadline can be more than what I set (20 or more seconds when I set 10). This happens very rarely and I am unable to reproduce this, it happens when I don't expect it.
To avoid posting a ton of lines (because few will read them) here is the place where I believe the error lies:
deadline_.expires_from_now(boost::posix_time::milliseconds(deadline));
tcp::resolver::query query(server, "http");
resolver_.async_resolve(query,
boost::bind(&client::handle_resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
deadline_.async_wait(boost::bind(&client::check_deadline, this));
Is the order of those lines correct?
And here is the check deadline function:
void check_deadline()
{
if(deadline_cancelled)
return;
else if (deadline_.expires_at() <= deadline_timer::traits_type::now())
socket_.close();
else
deadline_.async_wait(boost::bind(&client::check_deadline, this));
}
You should async_wait() on the deadline timer too. If you don't, you won't get notified, you just check (after the fact) whether the time had expired.
Then if it completes (with an ec other than operation_aborted) then you should
cancel() the async operations on the socket
optionally close the socket
PS. Mmm. It /seems/ that you are doing something similar, although it's unclear where
deadline_cancelled comes from
why you don't accept the error_code in the completion handler for deadline_.async_await and
why you are juggling with time comparisons manually, instead of trusting that the completion handler means what it says
Update Here's a full example doing a HTTP request. In fact, it downloads a million digits of PI from http://www.angio.net/pi/digits.html. This takes a while.
At the start of receiving the response I set a deadline timer for 800ms (and so the transfer should be - correctly - aborted).
This works as advertised. Pay special attention to the canceling of the socket and timer. Note that you could call expires_from_now() again after receiving each chunk of data. This is likely what you want. It will implicitly cancel() the timer each time it hadn't yet expired, so be prepared to handle the operatorion_aborted messages.
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/asio/deadline_timer.hpp>
class client
{
public:
client(boost::asio::io_service& io_service,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
: deadline_(io_service),
socket_(io_service)
{
boost::asio::async_connect(socket_.lowest_layer(), endpoint_iterator,
boost::bind(&client::handle_connect, this,
boost::asio::placeholders::error));
}
void handle_connect(const boost::system::error_code& error)
{
if (!error)
{
std::cout << "Enter message: ";
static char const raw[] = "GET /pi/digits/pi1000000.txt HTTP/1.1\r\nHost: www.angio.net\r\nConnection: close\r\n\r\n";
static_assert(sizeof(raw)<=sizeof(request_), "too large");
size_t request_length = strlen(raw);
std::copy(raw, raw+request_length, request_);
boost::asio::async_write(socket_,
boost::asio::buffer(request_, request_length),
boost::bind(&client::handle_write, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "Handshake failed: " << error.message() << "\n";
}
}
void deadline_expiration(const boost::system::error_code& error)
{
if (error == boost::asio::error::operation_aborted)
return;
std::cout << "\nDEADLINE REACHED\n";
socket_.cancel();
}
void handle_write(const boost::system::error_code& error,
size_t /*bytes_transferred*/)
{
if (!error)
{
std::cout << "starting read loop\n";
deadline_.expires_from_now(boost::posix_time::millisec(800));
//deadline_.expires_from_now(boost::posix_time::seconds(800));
deadline_.async_wait(boost::bind(&client::deadline_expiration, this, boost::asio::placeholders::error));
boost::asio::async_read_until(socket_,
//boost::asio::buffer(reply_, sizeof(reply_)),
reply_, '\n',
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "Write failed: " << error.message() << "\n";
}
}
void handle_read(const boost::system::error_code& error, size_t /*bytes_transferred*/)
{
if (!error)
{
std::cout << "Reply: " << &reply_ << "\n";
boost::asio::async_read_until(socket_,
//boost::asio::buffer(reply_, sizeof(reply_)),
reply_, '\n',
boost::bind(&client::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
std::cout << "Read failed: " << error.message() << "\n";
deadline_.cancel(); // no need for after transfer completed
}
}
private:
boost::asio::deadline_timer deadline_;
boost::asio::ip::tcp::socket socket_;
char request_[1024];
boost::asio::streambuf reply_;
};
int main()
{
try
{
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::resolver::query query("www.angio.net", "80");
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
client c(io_service, iterator);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
}
Coliru link (Coliru doesn't support internet connectivity)
Related
So I've been trying to write a proxy in C++ using the boost.asio. My initial project includes the client that writes a string message into a socket, a server that receives this message and writes a string message into a socket, and a proxy that works with the two mentioned sockets.
The proxy code looks like this (The future intention is handle multiple connections and to use the transfered data somehow, and the callbacks would perform some actual work other than logging):
#include "commondata.h"
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
using namespace boost::asio;
using ip::tcp;
using std::cout;
using std::endl;
class con_handler : public boost::enable_shared_from_this<con_handler> {
private:
tcp::socket client_socket;
tcp::socket server_socket;
enum { max_length = 1024 };
char client_data[max_length];
char server_data[max_length];
public:
typedef boost::shared_ptr<con_handler> pointer;
con_handler(boost::asio::io_service& io_service):
server_socket(io_service),
client_socket(io_service) {
memset(client_data, 0, max_length);
memset(server_data, 0, max_length);
server_socket.connect( tcp::endpoint( boost::asio::ip::address::from_string(SERVERIP), SERVERPORT ));
}
// creating the pointer
static pointer create(boost::asio::io_service& io_service) {
return pointer(new con_handler(io_service));
}
//socket creation
tcp::socket& socket() {
return client_socket;
}
void start() {
//read the data into the input buffer
client_socket.async_read_some(
boost::asio::buffer(client_data, max_length),
boost::bind(&con_handler::handle_read,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
server_socket.async_write_some(
boost::asio::buffer(client_data, max_length),
boost::bind(&con_handler::handle_write,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
server_socket.async_read_some(
boost::asio::buffer(server_data, max_length),
boost::bind(&con_handler::handle_read,
shared_from_this(),
server_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
client_socket.async_write_some(
boost::asio::buffer(server_data, max_length),
boost::bind(&con_handler::handle_write,
shared_from_this(),
server_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(const char* data, const boost::system::error_code& err, size_t bytes_transferred) {
if (!err) {
cout << "proxy handle_read" << endl;
cout << data << endl;
} else {
std::cerr << "error: " << err.message() << std::endl;
client_socket.close();
}
}
void handle_write(const char* data, const boost::system::error_code& err, size_t bytes_transferred) {
if (!err) {
cout << "proxy handle_write" << endl;
cout << data << endl;
} else {
std::cerr << "error: " << err.message() << endl;
client_socket.close();
}
}
};
class Server {
private:
boost::asio::io_service io_service;
tcp::acceptor acceptor_;
void start_accept() {
// socket
con_handler::pointer connection = con_handler::create(io_service);
// asynchronous accept operation and wait for a new connection.
acceptor_.async_accept(connection->socket(),
boost::bind(&Server::handle_accept, this, connection,
boost::asio::placeholders::error));
}
public:
//constructor for accepting connection from client
Server()
: acceptor_(io_service, tcp::endpoint(tcp::v4(), PROXYPORT)) {
start_accept();
}
void handle_accept(const con_handler::pointer& connection, const boost::system::error_code& err) {
if (!err) {
connection->start();
}
start_accept();
}
boost::asio::io_service& get_io_service() {
return io_service;
}
};
int main(int argc, char *argv[]) {
try {
Server server;
server.get_io_service().run();
} catch(std::exception& e) {
std::cerr << e.what() << endl;
}
return 0;
}
If the messages sent are strings (which I've used initially to test if my code works at all), then all of the callbacks are called the way I wanted them to be called, and the thing seems to be working.
Here's the stdout of the proxy for that case:
user#laptop:$ ./proxy
proxy handle_read
message from the client
proxy handle_write
message from the client
proxy handle_read
message from server
proxy handle_write
message from server
So the client sends the "message from the client" string, which is received and saved by the proxy, the same string is sent to the server, then the server sends back the "message from server" string, which is also received and saved by the proxy and then is sent to the client.
The problem appears when I try to use the actual web server (Apache) and an application like JMeter to talk to each other. This is the stdout for this case:
user#laptop:$ ./proxy
proxy handle_write
proxy handle_write
proxy handle_read
GET / HTTP/1.1
Connection: keep-alive
Host: 127.0.0.1:1337
User-Agent: Apache-HttpClient/4.5.5 (Java/11.0.8)
error: End of file
The JMeter test then fails with a timeout (that is when the proxy gets the EOF error), and no data seems to be sent to the apache webserver. The questions that I have for now are then why the callbacks are called in another order comparing to the case when the string messages are sent and why the data is not being transferred to the server socket, I guess. Thanks in advance for any help!
Abbreviating from start():
client_socket.async_read_some (buffer(client_data), ...);
server_socket.async_write_some (buffer(client_data), ...);
server_socket.async_read_some (buffer(server_data), ...);
client_socket.async_write_some (buffer(server_data), ...);
//read the data into the input
client_socket.async_read_some (buffer(client_data), ...);
server_socket.async_write_some (buffer(client_data), ...);
server_socket.async_read_some (buffer(server_data), ...);
client_socket.async_write_some (buffer(server_data), ...);
That's... not how async operations work. They run asynchronously, meaning that they will all immediately return.
You're simultaneously reading and writing from some buffers, without waiting for valid data. Also, you're writing the full buffer always, regardless of how much was received.
All of this spells Undefined Behaviour.
Start simple
Conceptually you just want to read:
void start() {
//read the data into the input buffer
client_socket.async_read_some(
boost::asio::buffer(client_data, max_length),
boost::bind(&con_handler::handle_read,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
Now, once you received data, you might want to relay that:
void handle_read(const char* data, const boost::system::error_code& err, size_t bytes_transferred) {
if (!err) {
std::cout << "proxy handle_read" << std::endl;
server_socket.async_write_some(
boost::asio::buffer(client_data, bytes_transferred),
boost::bind(&con_handler::handle_write,
shared_from_this(),
client_data,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
} else {
std::cerr << "error: " << err.message() << std::endl;
client_socket.close();
}
}
Note that it seems a bit arbitrary to only close one side of the connection on errors. You probably at least want to cancel() any async operations on both, optionally shutdown() and then just let the shared_ptr destruct your con_handler.
Full Duplex
Now, for full-duplex operation you can indeed start the reverse relay at the same time. It gets a little unweildy to maintain the call chains in separate methods (after all you don't just switch the buffers, but also the socket pairs).
It might be instructive to realize that you're doing the same thing twice:
client -> [...buffer...] -> server
server -> [...buffer...] -> client
You can encapsulate each side in a class, and avoid duplicating all the code:
struct relay {
tcp::socket &from, &to;
std::array<char, max_length> buf{};
void run_relay(pointer self) {
from.async_read_some(asio::buffer(buf),
[this, self](error_code ec, size_t n) {
if (ec) return handle(from, ec);
/*
*std::cout
* << "From " << from.remote_endpoint()
* << ": " << std::quoted(std::string_view(buf.data(), n))
* << std::endl;
*/
async_write(to, asio::buffer(buf, n), [this, self](error_code ec, size_t) {
if (ec) return handle(to, ec);
run_relay(self);
});
});
}
void handle(tcp::socket& which, error_code ec = {}) {
if (ec == asio::error::eof) {
// soft "error" - allow write to complete
std::cout << "EOF on " << which.remote_endpoint() << std::endl;
which.shutdown(tcp::socket::shutdown_receive, ec);
}
if (ec) {
from.cancel();
to.cancel();
std::string reason = ec.message();
auto fep = from.remote_endpoint(ec),
tep = to.remote_endpoint(ec);
std::cout << "Stopped relay " << fep << " -> " << tep << " due to " << reason << std::endl;
}
}
} c_to_s {client_socket, server_socket, {0}},
s_to_c {server_socket, client_socket, {0}};
Note
we sidestepped the bind mess by using lambdas
we cancel both ends of the relay on error
we use a std::array buffer - more safe and easier to use
we only write as many bytes as were received, regardless of the size of the buffer
we don't schedule another read until the write has completed to avoid clobbering the data in buf
Let's implement con_handler start again
Using the relay from just above:
void start() {
c_to_s.run_relay(shared_from_this());
s_to_c.run_relay(shared_from_this());
}
That's all. We pass ourselves so the con_handler stays alive until all operations complete.
DEMO Live On Coliru
#define PROXYPORT 8899
#define SERVERIP "173.203.57.63" // coliru IP at the time
#define SERVERPORT 80
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <iomanip>
namespace asio = boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using namespace std::chrono_literals;
class con_handler : public boost::enable_shared_from_this<con_handler> {
public:
con_handler(asio::io_service& io_service):
server_socket(io_service),
client_socket(io_service)
{
server_socket.connect({ asio::ip::address::from_string(SERVERIP), SERVERPORT });
}
// creating the pointer
using pointer = boost::shared_ptr<con_handler>;
static pointer create(asio::io_service& io_service) {
return pointer(new con_handler(io_service));
}
//socket creation
tcp::socket& socket() {
return client_socket;
}
void start() {
c_to_s.run_relay(shared_from_this());
s_to_c.run_relay(shared_from_this());
}
private:
tcp::socket server_socket;
tcp::socket client_socket;
enum { max_length = 1024 };
struct relay {
tcp::socket &from, &to;
std::array<char, max_length> buf{};
void run_relay(pointer self) {
from.async_read_some(asio::buffer(buf),
[this, self](error_code ec, size_t n) {
if (ec) return handle(from, ec);
/*
*std::cout
* << "From " << from.remote_endpoint()
* << ": " << std::quoted(std::string_view(buf.data(), n))
* << std::endl;
*/
async_write(to, asio::buffer(buf, n), [this, self](error_code ec, size_t) {
if (ec) return handle(to, ec);
run_relay(self);
});
});
}
void handle(tcp::socket& which, error_code ec = {}) {
if (ec == asio::error::eof) {
// soft "error" - allow write to complete
std::cout << "EOF on " << which.remote_endpoint() << std::endl;
which.shutdown(tcp::socket::shutdown_receive, ec);
}
if (ec) {
from.cancel();
to.cancel();
std::string reason = ec.message();
auto fep = from.remote_endpoint(ec),
tep = to.remote_endpoint(ec);
std::cout << "Stopped relay " << fep << " -> " << tep << " due to " << reason << std::endl;
}
}
} c_to_s {client_socket, server_socket, {0}},
s_to_c {server_socket, client_socket, {0}};
};
class Server {
asio::io_service io_service;
tcp::acceptor acceptor_;
void start_accept() {
// socket
auto connection = con_handler::create(io_service);
// asynchronous accept operation and wait for a new connection.
acceptor_.async_accept(
connection->socket(),
[connection, this](error_code ec) {
if (!ec) connection->start();
start_accept();
});
}
public:
Server() : acceptor_(io_service, {{}, PROXYPORT}) {
start_accept();
}
void run() {
io_service.run_for(5s); // .run();
}
};
int main() {
Server().run();
}
When run with
printf "GET / HTTP/1.1\r\nHost: coliru.stacked-crooked.com\r\n\r\n" | nc 127.0.0.1 8899
The server prints:
EOF on 127.0.0.1:36452
And the netcat receives reply:
HTTP/1.1 200 OK
Content-Type: text/html;charset=utf-8
Content-Length: 8616
Server: WEBrick/1.4.2 (Ruby/2.5.1/2018-03-29) OpenSSL/1.0.2g
Date: Sat, 01 Aug 2020 00:25:10 GMT
Connection: Keep-Alive
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Frameset//EN">
<html>
....
</html>
Summary
Thinking clearly about what you are trying to achieve, avoids accidentally complexity. It allowed us to come up with a good building block (relay), evaporating complexity.
I 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 am working on a simple TCP server that reads and writes it's messages to thread safe queue. The application can then use those queue to safely read and write to the socket even from different threads.
The problem I am facing is that I cannot async_read. My queue has the pop operation which returns the next element to be processed but it blocks if nothing is available. So once I call pop the async_read callback of course isn't fired anymore. Is there a way I can integrate such a queue into boost asio or do I have to completely rewrite?
Below is a short example I made to show the problem I am having. Once a TCP connection is estabilished I create a new thread that will run the application under that tcp_connection. Afterwards I want to start async_read and async_write. I have been breaking my head on this for a couple of hours and I really don't know how to solve this.
class tcp_connection : public std::enable_shared_from_this<tcp_connection>
{
public:
static std::shared_ptr<tcp_connection> create(boost::asio::io_service &io_service) {
return std::shared_ptr<tcp_connection>(new tcp_connection(io_service));
}
boost::asio::ip::tcp::socket& get_socket()
{
return this->socket;
}
void app_start()
{
while(1)
{
// Pop is a blocking call.
auto inbound_message = this->inbound_messages.pop();
std::cout << "Got message in app thread: " << inbound_message << ". Sending it back to client." << std::endl;
this->outbound_messages.push(inbound_message);
}
}
void start() {
this->app_thread = std::thread(&tcp_connection::app_start, shared_from_this());
boost::asio::async_read_until(this->socket, this->input_stream, "\r\n",
strand.wrap(boost::bind(&tcp_connection::handle_read, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
// Start async writing here. The message to send are in the outbound_message queue. But a Pop operation blocks
// empty() is also available to check whether the queue is empty.
// So how can I async write without blocking the read.
// block...
auto message = this->outbound_messages.pop();
boost::asio::async_write(this->socket, boost::asio::buffer(message),
strand.wrap(boost::bind(&tcp_connection::handle_write, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
}
void handle_read(const boost::system::error_code& e, size_t bytes_read)
{
std::cout << "handle_read called" << std::endl;
if (e)
{
std::cout << "Error handle_read: " << e.message() << std::endl;
return;
}
if (bytes_read != 0)
{
std::istream istream(&this->input_stream);
std::string message;
message.resize(bytes_read);
istream.read(&message[0], bytes_read);
std::cout << "Got message: " << message << std::endl;
this->inbound_messages.push(message);
}
boost::asio::async_read_until(this->socket, this->input_stream, "\r\n",
strand.wrap(boost::bind(&tcp_connection::handle_read, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
}
void handle_write(const boost::system::error_code& e, size_t /*bytes_transferred*/)
{
if (e)
{
std::cout << "Error handle_write: " << e.message() << std::endl;
return;
}
// block...
auto message = this->outbound_messages.pop();
boost::asio::async_write(this->socket, boost::asio::buffer(message),
strand.wrap(boost::bind(&tcp_connection::handle_write, shared_from_this(), boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
}
private:
tcp_connection(boost::asio::io_service& io_service) : socket(io_service), strand(io_service)
{
}
boost::asio::ip::tcp::socket socket;
boost::asio::strand strand;
boost::asio::streambuf input_stream;
std::thread app_thread;
concurrent_queue<std::string> inbound_messages;
concurrent_queue<std::string> outbound_messages;
};
class tcp_server
{
public:
tcp_server(boost::asio::io_service& io_service)
: acceptor(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 9001))
{
start_accept();
}
private:
void start_accept()
{
std::shared_ptr<tcp_connection> new_connection =
tcp_connection::create(acceptor.get_io_service());
acceptor.async_accept(new_connection->get_socket(),
boost::bind(&tlcp_tcp_server::handle_accept, this, new_connection, boost::asio::placeholders::error));
}
void handle_accept(std::shared_ptr<tcp_connection> new_connection,
const boost::system::error_code& error)
{
if (!error)
{
new_connection->start();
}
start_accept();
}
boost::asio::ip::tcp::acceptor acceptor;
};
It looks to me as if you want an async_pop method which takes an error message placeholder and callback handler. When you receive a message, check whether there is an outstanding handler and if so, pop the message, deregister the handler and call it. Similarly when registering the async_pop, if there is already a message waiting, pop the message and post a call to the handler without registering it.
You might want to derive the async_pop class from a polymorphic base base of type pop_operation or similar.
I am writing simple socket class. I am trying to write socket
according to example http://www.boost.org/doc/libs/1_53_0/doc/html/boost_asio/example/chat/chat_client.cpp
I can connect to server (I see it in logs) but I can not send or receive message. In wireshark I do not see outgoing messages but I can see incoming messages.
As I understand io_service is running in different thread and it should be possible to receive message or send message.
(It is incomplete class because I stopped at this described problem)
Ioservice is defined outside class (main)
#include <iostream>
#include <thread>
#include <boost/thread/thread.hpp>
#include "TCP/TCPSocket.h"
using namespace std;
int main()
{
Traces::SetTraceFolder("trace");
boost::asio::io_service io_service;
boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));
TCPSocket socket("192.168.0.7", "6000", io_service);
char *c = new char[100];
while (true) {
std::cin >> *c;
socket.WriteMessage(c);
}
delete [] c;
return 0;
}
My class:
include "TCPSocket.h"
TCPSocket::TCPSocket(const std::string &adress, const std::string &port, boost::asio::io_service & serviceio)
: io_service(serviceio),
socket_(io_service),
resolver(io_service)
{
Traces() << "\n" << "LOG: TCPSocket::TCPSocket(const std::string &adress, const std::string &port)";
tcp::resolver::query query(adress, port);
querywsk = new tcp::resolver::query(" ", " ");
*querywsk = query;
iterator = resolver.resolve(*querywsk);
boost::asio::async_connect(socket_, iterator,
boost::bind(&TCPSocket::HandleConnect, this,
boost::asio::placeholders::error));
}
void TCPSocket::HandleConnect(const boost::system::error_code& error)
{
Traces() << "\n" << "LOG: void TCPSocket::HandleConnect(const boost::system::error_code& error)";
if (!error)
{
boost::asio::async_read(socket_,
boost::asio::buffer(data, std::strlen(data)),
boost::bind(&TCPSocket::HandleConnect, this,
boost::asio::placeholders::error));
} else
{
Traces() << "\n" << "ERR:";
}
}
void TCPSocket::WriteMessage(char *dataToSend)
{
io_service.post(boost::bind(&TCPSocket::Write, this, dataToSend));
}
void TCPSocket::Write(char *dataToSend)
{
Traces() << "\n" << "LOG: void TCPSocket::Write(char *dataToSend)";
data = dataToSend;
boost::asio::async_write(socket_,
boost::asio::buffer(data, std::strlen(data)),
boost::bind(&TCPSocket::HandleWrite, this,
boost::asio::placeholders::error));
}
void TCPSocket::HandleWrite(const boost::system::error_code& error)
{
Traces() << "\n" << "LOG: void TCPSocket::HandleWrite(const boost::system::error_code& error)";
if (!error)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data,
100),
boost::bind(&TCPSocket::HandleWrite, this,
boost::asio::placeholders::error));
}
else
{
Traces() << "\n" << "ERR:";
}
}
boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));
At this early point in your code, the io_service has no work to do.
So the thread starts up, the io_service starts up in the thread, sees there is nothing to do and so stops immediatly.
Thus nothing happens.
You must start the io_service AFTER you have setup the work.
There are other serious problems with your code e.g. you seem a bit confused between a socket server, which listens for connection requests, and a connection which receives and transmits messages.
I suggest that you start fresh by working through the tutorial examples at http://www.boost.org/doc/libs/1_60_0/doc/html/boost_asio/tutorial.html Once you have worked your way through these, you will have a much better understanding of what goes on.
I'm having an issue creating a really simple TCP based server-client connection using boost asio. When I get a connection from a client on my server and get into the method that handles the async_read_some I check for an error, and am always getting error 1236, which gives the message "The network connection was aborted by the local system."
I've just started working with boost, so I'm not really familiar with how the libraries work and what I could have done wrong. I've provided a cut down version of my code below:
/*Client connection code*/
ClientConnection::ClientConnection(boost::asio::io_service& io_service) : m_Socket(io_service)
{
}
ClientConnection::ClientConnectionPointer ClientConnection::Create(boost::asio::io_service& io_service)
{
return ClientConnection::ClientConnectionPointer(new ClientConnection(io_service));
}
void ClientConnection::handle_write(const boost::system::error_code& error, size_t bytes_transferred)
{
//once we've written our packet, just wait for more
m_Socket.async_read_some(boost::asio::buffer(m_IncomingBytesBuffer, MAX_BYTES_LENGTH),
boost::bind(&ClientConnection::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void ClientConnection::handle_read(const boost::system::error_code& error, size_t bytes_transferred)
{
if(!error)
{
//deal with the data that comes in here
}
else
{
std::cout << "Error reading port data" << std::endl;
std::cout << error.message() << std::endl;
}
}
tcp::socket& ClientConnection::GetSocket(void)
{
return m_Socket;
}
void ClientConnection::RunClient(void)
{
std::cout << "Client connected." << std::endl;
//start by reading data from the connection
m_Socket.async_read_some(boost::asio::buffer(m_IncomingBytesBuffer, MAX_BYTES_LENGTH),
boost::bind(&ClientConnection::handle_read, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
/*Listener server code here*/
BarcodeServer::BarcodeServer(boost::asio::io_service& io_service) : m_acceptor(io_service, tcp::endpoint(tcp::v4(), SERVER_PORT_NUMBER))
{
start_accepting_connections();
}
void BarcodeServer::start_accepting_connections(void)
{
std::cout << "Waiting for a connection." << std::endl;
ClientConnection::ClientConnectionPointer new_connection = ClientConnection::Create(m_acceptor.get_io_service());
m_acceptor.async_accept(new_connection->GetSocket(), boost::bind(&BarcodeServer::handle_accepted_connection, this, new_connection, boost::asio::placeholders::error));
}
void BarcodeServer::handle_accepted_connection(ClientConnection::ClientConnectionPointer new_connection, const boost::system::error_code& error)
{
if(!error)
{
new_connection->RunClient();
}
start_accepting_connections();
}
/*main code here*/
try
{
boost::asio::io_service io_service;
BarcodeServer server(io_service);
io_service.run();
}
catch(std::exception& e)
{
cout << "Error when running server:" << endl;
cout << e.what() << endl;
return RETURN_CODE_SERVER_RUN_ERROR;
}
return RETURN_CODE_SUCCESS;
Most of this code is prety much just lifted straight from examples on the boost website, so I'm guessing I've just done something silly somewhere, but I've looked over the code a few times and can't figure out where.
Any help would be much appreciated.
The lifetime of ClientConnection ends after handle_accepted_connection() exits, because all the instances of shared_ptr<ClientConnection> go out of scope and get destroyed.
To avoid this situation, you can either use shared_from_this idiom within ClientConnection member-functions or store 1 shared_ptr<ClientConnection> in some "connection manager".