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 try to write an async message to the server from my client code, the write handler gets called with the correct bytes sent; however, the server receives 0 bytes.
Cliente output:
You are connected
You received the following message from the server:
Sat Aug 20 17:42:01 2016
Sending...
Server output:
Server is online!
127.0.0.1:51973 connected!
Client has received the messaged.
You received the following message from the server:
server source:
using boost::asio::ip::tcp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class tcp_connection: public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket& socket()
{
return socket_;
}
// Call boost::asio::async_write() to serve the data to the client.
// We are using boost::asio::async_write(),
// rather than ip::tcp::socket::async_write_some(),
// to ensure that the entire block of data is sent.
void start()
{
// The data to be sent is stored in the class member m_message
// as we need to keep the data valid
// until the asynchronous operation is complete.
m_message = make_daytime_string();
// When initiating the asynchronous operation,
// and if using boost::bind(),
// we must specify only the arguments
// that match the handler's parameter list.
// In this code, both of the argument placeholders
// (boost::asio::placeholders::error
// and boost::asio::placeholders::bytes_transferred)
// could potentially have been removed,
// since they are not being used in handle_write().
std::cout << socket_.remote_endpoint().address().to_string() << ":" << socket_.remote_endpoint().port() << " connected!" << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(m_message),
boost::bind(&tcp_connection::handle_write, shared_from_this()));
boost::asio::async_read(socket_, boost::asio::buffer(_buffer), boost::bind(&tcp_connection::handle_receive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
private:
tcp_connection(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
// handle_write() is responsible for any further actions
// for this client connection.
void handle_write() // call back.. when it finishes sending, we come here
{
std::cout << "Client has received the messaged. " << std::endl;
}
void handle_receive(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "You received the following message from the server:" << std::endl;
std::cout.write(_buffer.data(), bytes_transferred);
}
tcp::socket socket_;
std::string m_message;
boost::array<char, 126> _buffer;
};
class tcp_server
{
public:
tcp_server(boost::asio::io_service& io_service) : acceptor_(io_service, tcp::endpoint(tcp::v4(), 7171))
{
// start_accept() creates a socket and
// initiates an asynchronous accept operation
// to wait for a new connection.
start_accept();
}
private:
void start_accept()
{
// creates a socket
tcp_connection::pointer new_connection = tcp_connection::create(acceptor_.get_io_service());
// initiates an asynchronous accept operation
// to wait for a new connection.
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
// handle_accept() is called when the asynchronous accept operation
// initiated by start_accept() finishes. It services the client request
void handle_accept(tcp_connection::pointer new_connection,
const boost::system::error_code& error)
{
if (!error)
{
new_connection->start();
}
// Call start_accept() to initiate the next accept operation.
start_accept();
}
tcp::acceptor acceptor_;
};
int main()
{
std::cout << "Server is online!" << std::endl;
try
{
boost::asio::io_service io_service;
tcp_server server(io_service);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
client source:
#include <iostream>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
std::string make_daytime_string()
{
using namespace std; // For time_t, time and ctime;
time_t now = time(0);
return ctime(&now);
}
class Connection
{
public:
Connection(boost::asio::io_service& io) : _socket(io){}
void connect(tcp::resolver::iterator& point)
{
boost::asio::async_connect(_socket, point, boost::bind(&Connection::onConnected, this, boost::asio::placeholders::error));
}
void onConnected(const boost::system::error_code& ErrorCode)
{
std::cout << "You are connected" << std::endl;
// receive first message on onReceive
boost::asio::async_read(_socket, boost::asio::buffer(_buffer), boost::bind(&Connection::onReceive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void onSend(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "Sending..." << std::endl;
}
void onReceive(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "You received the following message from the server:" << std::endl;
std::cout.write(_buffer.data(), bytes_transferred);
// send first message on onSend
m_message = make_daytime_string();
boost::asio::async_write(_socket, boost::asio::buffer(m_message), boost::bind(&Connection::onSend, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
tcp::socket& getSocket()
{
return _socket;
}
private:
tcp::socket _socket;
boost::array<char, 126> _buffer;
std::string m_message;
};
int main()
{
try
{
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query("127.0.0.1", "7171");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
Connection conn(io_service);
conn.connect(endpoint_iterator);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
#edit:
new client code:
class Connection : public boost::enable_shared_from_this<Connection>
{
public:
typedef boost::shared_ptr<Connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new Connection(io_service));
}
tcp::socket& socket()
{
return _socket;
}
void connect(tcp::resolver::iterator& point)
{
boost::asio::async_connect(_socket, point, boost::bind(&Connection::connect_handler, this, boost::asio::placeholders::error));
}
void connect_handler(const boost::system::error_code& error)
{
if(error)
{
std::cout << "Error on connect_handler: " << error.message() << std::endl;
return;
}
std::cout << "You are connected to the server." << std::endl;
start();
}
void start()
{
start_write();
start_read();
}
private:
// private ctor
Connection(boost::asio::io_service& io) : _socket(io){}
void start_write()
{
_daymessage = make_daytime_string();
boost::asio::async_write(_socket, boost::asio::buffer(_daymessage),
boost::bind(&Connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_write(const boost::system::error_code& error,
size_t bytes)
{
if(error)
{
std::cout << "Error on handle write: " << error.message() << std::endl;
return;
}
std::cout << "Message has been sent!" << std::endl;
start_write();
}
void start_read()
{
// Start an asynchronous operation to read a newline-delimited message.
boost::asio::async_read_until(_socket, _buffer, '\n',
boost::bind(&Connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(const boost::system::error_code& error, size_t bytes)
{
if(error)
{
std::cout << "Error on handle read: " << error.message() << std::endl;
return;
}
// Extract the newline-delimited message from the buffer.
std::string line;
std::istream is(&_buffer);
std::getline(is, line);
if (!line.empty())
{
std::cout << "Received: " << line << "\n";
}
start_read();
}
tcp::socket _socket;
std::string _daymessage;
boost::asio::streambuf _buffer;
};
int main()
{
std::cout << "Client is running!" << std::endl;
try
{
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query("127.0.0.1", "7171");
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
auto connection = Connection::create(io_service);
connection->connect(endpoint_iterator);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
obs: I got a "tr1::bad_weak_ptr" when I use public ctor and instantiate with make_shared. The private ctor and static member func worked fine.
There are many issues with the code you have provided:
In your server connection class, you are using raw this pointer
instead of using shared_from_this. This was resulting in the
operation getting cancelled as your resource was getting out of
scope.
The code is making use of async_read with a buffer of size 126. I
guess the operation would not finish until you receive atleast that
many bytes. Use async_read_until instead. Due to lack of any protocol or predefined byte sequence, I have modified the code
to send '\n' as the delimiter.
Never ignore the error code received.
Modified Server Code:
class tcp_connection: public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket& socket()
{
return socket_;
}
// Call boost::asio::async_write() to serve the data to the client.
// We are using boost::asio::async_write(),
// rather than ip::tcp::socket::async_write_some(),
// to ensure that the entire block of data is sent.
void start()
{
// The data to be sent is stored in the class member m_message
// as we need to keep the data valid
// until the asynchronous operation is complete.
m_message = make_daytime_string();
// When initiating the asynchronous operation,
// and if using boost::bind(),
// we must specify only the arguments
// that match the handler's parameter list.
// In this code, both of the argument placeholders
// (boost::asio::placeholders::error
// and boost::asio::placeholders::bytes_transferred)
// could potentially have been removed,
// since they are not being used in handle_write().
std::cout << socket_.remote_endpoint().address().to_string() << ":" << socket_.remote_endpoint().port() << " connected!" << std::endl;
boost::asio::async_write(socket_, boost::asio::buffer(m_message),
boost::bind(&tcp_connection::handle_write, shared_from_this()));
boost::asio::async_read_until(socket_,
_buffer,
'\n',
boost::bind(&tcp_connection::handle_receive,
shared_from_this(),
boost::asio::placeholders::error));
}
private:
tcp_connection(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
// handle_write() is responsible for any further actions
// for this client connection.
void handle_write() // call back.. when it finishes sending, we come here
{
std::cout << "Client has received the messaged. " << std::endl;
}
void handle_receive(const boost::system::error_code& ErrorCode)
{
std::cout << "You received the following message from the server: "<< std::endl;
if (ErrorCode) {
std::cout << "Error occured: " << ErrorCode.message() << std::endl;
return;
}
std::string line;
std::istream is(&_buffer);
std::getline(is, line);
std::cout << line << std::endl;
}
tcp::socket socket_;
std::string m_message;
boost::asio::streambuf _buffer;
};
Modified Client Code:
class Connection: public boost::enable_shared_from_this<Connection>
{
public:
Connection(boost::asio::io_service& io) : _socket(io){}
void connect(tcp::resolver::iterator& point)
{
boost::asio::async_connect(_socket, point, boost::bind(&Connection::onConnected, this, boost::asio::placeholders::error));
}
void onConnected(const boost::system::error_code& ErrorCode)
{
std::cout << "You are connected" << std::endl;
// receive first message on onReceive
boost::asio::async_read_until(_socket,
_buffer,
'\n',
boost::bind(&Connection::onReceive,
this, boost::asio::placeholders::error));
}
void onSend(const boost::system::error_code& ErrorCode, std::size_t bytes_transferred)
{
std::cout << "Sending..." << std::endl;
}
void onReceive(const boost::system::error_code& ErrorCode)
{
std::cout << "You received the following message from the server:" << std::endl;
//std::cout.write(_buffer.data(), bytes_transferred);
// send first message on onSend
m_message = make_daytime_string() + '\n';
std::cout << "Sending " << m_message << std::endl;
boost::asio::async_write(_socket, boost::asio::buffer(m_message), boost::bind(&Connection::onSend, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
tcp::socket& getSocket()
{
return _socket;
}
private:
tcp::socket _socket;
boost::asio::streambuf _buffer;
std::string m_message;
};
Just to test my server, I've created 100 requests using a for loop from my client side and while my server is writing response for the Nth request I deliberately pressed control+c from the client, Thats it. The server stops and has gone unresponsive although I try connecting it using a new connection, can any one advice me how to make my server stable and immune to such interrupt.
here is my server:
class tcp_server
{
public:
tcp_server(boost::asio::io_service& io_service)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), 2020))
{
start_accept();
}
private:
void start_accept()
{
tcp_connection::pointer new_connection =
tcp_connection::create(acceptor_.get_io_service());
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
void handle_user_read(const boost::system::error_code& err,
std::size_t bytes_transferred)
{
}
void handle_accept(tcp_connection::pointer new_connection,
const boost::system::error_code& error)
{
if (!error)
{
new_connection->start();
start_accept();
}
}
tcp::acceptor acceptor_;
};
here is my tcp connection:
class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
// Start reading messages from the server
start_read();
}
public:
tcp_connection(boost::asio::io_service& io_service) : socket_(io_service),timer_(io_service), io(io_service),timer2_(io_service)
{
}
// Reading messages from the server
void start_read()
{
boost::asio::async_read(socket_, input_buffer_,
boost::asio::transfer_at_least(1),
boost::bind(&tcp_connection::handle_read, shared_from_this(),
boost::asio::placeholders::error));
timer_.expires_from_now(boost::posix_time::seconds(120));
timer_.async_wait(boost::bind(&tcp_connection::close, shared_from_this()));
}
void close()
{
cout<<"I didn't hear the client yet:closing the socket......"<<endl;
socket_.close();
}
// When stream is received, handle the message from the client
int handle_read(const boost::system::error_code& ec)
{
if (!ec)
{
std::istream is(&input_buffer_);
std::string line;
std::getline(is, line);
messageFromClient_+=line;
messageFromClient_.erase(std::remove(messageFromClient_.begin(), messageFromClient_.end(), '\n'), messageFromClient_.end());
std::size_t found = messageFromClient_.find('\0');
if(found==std::string::npos)
{
boost::asio::async_read(socket_, input_buffer_,
boost::asio::transfer_at_least(1),
boost::bind(&tcp_connection::handle_read, shared_from_this(),
boost::asio::placeholders::error));
}
else{
performmaj();--my logic never mind.
std::cout << "Request: "<<i<<" is on process......"<<"\n";--mylogic
boost::asio::ip::tcp::no_delay option(true);
socket_.set_option(option);
write();
messageToClient_="";--my logic.
boost::system::error_code tc;
socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_send, tc);
}
std::cout << "Request: "<<i<<" completed"<<"\n";
++i;
(boost::asio::io_service io);
}else
{
std::cout << "Error on receive: " << ec.message() << "\n";
}
}
void write()
{
try{
boost::asio::write(socket_,boost::asio::buffer(messageToClient_), boost::asio::transfer_at_least(messageToClient_.size()));
}catch(exception e)
{
cout<<e.what()<<endl;
socket_.close();
io.run();
}
}
please see my below code where i have used async_write; Note the string i have intende to write is of size:11279204. but using async_write in the below code let my client to recieve the meaasage that is more of partial but not complete.
class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
// Start reading messages from the server
start_read();
}
public:
tcp_connection(boost::asio::io_service& io_service) : socket_(io_service),timer_(io_service), io(io_service),timer2_(io_service)
{
//io=io_service;
}
// Reading messages from the server
void start_read()
{
boost::asio::async_read(socket_, input_buffer_,
boost::asio::transfer_at_least(1),
boost::bind(&tcp_connection::handle_read, shared_from_this(),
boost::asio::placeholders::error));
timer_.expires_from_now(boost::posix_time::seconds(120));
timer_.async_wait(boost::bind(&tcp_connection::close, shared_from_this()));
}
void close()
{
cout<<"I didn't hear the client yet:closing the socket......"<<endl;
socket_.close();
}
// When stream is received, handle the message from the client
int handle_read(const boost::system::error_code& ec)
{
if (!ec)
{
std::istream is(&input_buffer_);
std::string line;
std::getline(is, line);
messageFromClient_+=line;
messageFromClient_.erase(std::remove(messageFromClient_.begin(), messageFromClient_.end(), '\n'), messageFromClient_.end());
std::size_t found = messageFromClient_.find('\0');
if(found==std::string::npos)
{
boost::asio::async_read(socket_, input_buffer_,
boost::asio::transfer_at_least(1),
boost::bind(&tcp_connection::handle_read, shared_from_this(),
boost::asio::placeholders::error));
}
else{
performmaj();
cout<<messageToClient_.size()<<endl;--11279204
try{
boost::asio::async_write(socket_, boost::asio::buffer(messageToClient_.data(),messageToClient_.size()),
// boost::asio::transfer_at_least(messageToClient_.size()),
boost::bind(&tcp_connection::handle_write, shared_from_this(),
boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}catch(exception e)
{
Shutdown();
}
}
std::cout << "Request: "<<i<<" completed"<<"\n";
++i;
return 0;
}else
{
std::cout << "Error on receive: " << ec.message() << "\n";
}
}
void Shutdown()
{
try {
socket_.shutdown(socket_.shutdown_both);
socket_.close();
} catch (std::exception &e)
{
std::cout << "Error Closing Socket" << e.what() << std::endl;
}
}
void performmaj()
{
std::size_t found = messageFromClient_.find('\0');
if (found!=std::string::npos)
{
std::cout << "Request: "<<i<<" Recieved"<<"\n";
std::cout << "Request: "<<i<<" is on process......"<<"\n";
if (messageFromClient_.size () > 0) messageFromClient_.resize (messageFromClient_.size () - 1);
messageToClient_=test(messageFromClient_);
messageFromClient_="";
messageToClient_.erase(std::remove(messageToClient_.begin(), messageToClient_.end(), '\n'), messageToClient_.end());
}
}
void handle_write(const boost::system::error_code& ec,
size_t bytes_transferred)
{
boost::asio::async_write(socket_,boost::asio::buffer(messageToClient_.data(),bytes_transferred),
// boost::asio::transfer_at_least(bytes_transferred),
boost::bind(&tcp_connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
bytes_transferred));
boost::system::error_code tc;
socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_send, tc);
}
tcp::socket socket_;
std::string messageToClient_;
boost::asio::streambuf input_buffer_;
std::string messageFromClient_;
boost::asio::io_service& io;
boost::asio::deadline_timer timer_,timer2_;
};
the above unpredicted behaviour of async_write have caused me to use asio::write.
The boost::asio::write() blocks until the data is written or an exception is thrown. Your write() function catches the exception, closes the socket, and returns, but does not indicate the socket is closed. You then call shutdown on a closed socket. Create a Shutdown function. Catch the drop exception error in write() but wait to call Shutdown after Write() call. Your logic always calls Shutdown() on good or bad writes. Also do not call io.run(). Your io_service() is already running.
Shutdown()
{
try {
socket_.shutdown(socket_.shutdown_both);
socket_->close();
} catch (std::exception &e)
{
std::cout << "Error Closing Socket" << e.what() << std::endl;
}
}
I'm having an issue with boost::asio::io_service.post() not calling my method handler.
I have a simple client and server c++ app both using the same code in the TCPClient class. The client side works fine, but the instance of the class populated using accept doesn't work.
I have put my whole project up here but I've put the relevant bits of code below.
In the TCPClient::Write method this line
io_service.post(boost::bind(&TCPClient::DoWrite, this, msg));
gets called but the handler (TCPCLient::DoWrite) doesn't get called on the server side.
I know the IO_Service is running because my async_reads in the same TCPClient work fine.
This is my TCPClient class
.hpp file
class TCPClient
: public boost::enable_shared_from_this<TCPClient>
{
public:
typedef boost::shared_ptr<TCPClient> pointer;
private:
boost::asio::io_service io_service;
bool m_IsConnected;
bool m_HeartbeatEnabled;
boost::asio::ip::tcp::socket m_Socket;
boost::asio::ip::tcp::endpoint m_Endpoint;
boost::asio::steady_timer m_HeartBeatTimer;
boost::asio::streambuf m_Buffer;
std::string m_Delimiter;
std::deque<std::string> m_Messages;
bool m_HeartBeatEnabled;
int m_HeartBeatTime;
private:
void HandleConnect(const boost::system::error_code& error);
void DoHeartBeat(const boost::system::error_code& error);
void DoWrite(const std::string &msg);
void HandleWrite(const boost::system::error_code& error);
void HandleRead(const boost::system::error_code& error);
public:
TCPClient(boost::asio::io_service &io_service);
TCPClient(bool enableHeartbeat);
~TCPClient();
void Close();
void ConnectToServer(boost::asio::ip::tcp::endpoint& endpoint);
void ConnectToServer(const std::string &ip, const std::string &protocol);
void ConnectToServer(const std::string &ip, unsigned short port);
void Write(const std::string &msg);
void StartRead();
void SetHeartBeatTime(int time);
boost::asio::ip::tcp::socket& Socket();
boost::asio::io_service& Service();
static pointer Create(boost::asio::io_service& io_service);
public:
// signals
boost::signals2::signal<void(const boost::asio::ip::tcp::endpoint&)> sConnected;
boost::signals2::signal<void(const boost::asio::ip::tcp::endpoint&)> sDisconnected;
boost::signals2::signal<void(const std::string&)> sMessage;
};
.cpp file
using boost::asio::ip::tcp;
TCPClient::pointer TCPClient::Create(boost::asio::io_service& io)
{
return pointer(new TCPClient(io));
}
TCPClient::TCPClient(boost::asio::io_service& io)
: m_IsConnected(true), m_Socket(io), m_HeartBeatTimer(io), m_Delimiter(), m_HeartBeatTime(10)
{
m_Delimiter = "\n";
m_HeartbeatEnabled = false;
// start heartbeat timer (optional)
if(m_HeartBeatEnabled)
{
m_HeartBeatTimer.expires_from_now(boost::chrono::seconds(m_HeartBeatTime));
m_HeartBeatTimer.async_wait(boost::bind(&TCPClient::DoHeartBeat, this, boost::asio::placeholders::error));
}
}
TCPClient::TCPClient(bool enableHeartBeat)
: m_IsConnected(false), m_Socket(io_service), m_HeartBeatTimer(io_service), m_Delimiter(), m_HeartBeatTime(10)
{
m_Delimiter = "\n";
m_HeartbeatEnabled = enableHeartBeat;
}
TCPClient::TCPClient::~TCPClient()
{
}
void TCPClient::Close()
{
io_service.stop();
m_Socket.close();
}
boost::asio::ip::tcp::socket& TCPClient::Socket()
{
return m_Socket;
}
boost::asio::io_service& TCPClient::Service()
{
return io_service;
}
void TCPClient::ConnectToServer(const std::string &ip, unsigned short port)
{
try {
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::address::from_string(ip), port);
ConnectToServer(endpoint);
}
catch(const std::exception &e) {
std::cout << "Error: " << e.what() << std::endl;
}
}
void TCPClient::ConnectToServer(const std::string &url, const std::string &protocol)
{
// You can also explicitly pass a port, like "8080"
boost::asio::ip::tcp::resolver::query query( url, protocol );
boost::asio::ip::tcp::resolver resolver( io_service );
try {
boost::asio::ip::tcp::resolver::iterator destination = resolver.resolve(query);
boost::asio::ip::tcp::endpoint endpoint;
while ( destination != boost::asio::ip::tcp::resolver::iterator() )
endpoint = *destination++;
ConnectToServer(endpoint);
}
catch(const std::exception &e) {
std::cout << "Error: " << e.what() << std::endl;
}
}
void TCPClient::ConnectToServer(boost::asio::ip::tcp::endpoint& endpoint)
{
m_Endpoint = endpoint;
std::cout << "Trying to connect to port " << endpoint << std::endl;
// try to connect, then call handle_connect
m_Socket.async_connect(m_Endpoint,
boost::bind(&TCPClient::HandleConnect, this, boost::asio::placeholders::error));
//start processing messages
io_service.run();
}
void TCPClient::Write(const std::string &msg)
{
if(!m_IsConnected) return;
std::cout << "write: " << msg << std::endl;
// safe way to request the client to write a message
io_service.post(boost::bind(&TCPClient::DoWrite, this, msg));
}
void TCPClient::StartRead()
{
if(!m_IsConnected) return;
// wait for a message to arrive, then call handle_read
boost::asio::async_read_until(m_Socket, m_Buffer, m_Delimiter,
boost::bind(&TCPClient::HandleRead, this, boost::asio::placeholders::error));
}
void TCPClient::HandleRead(const boost::system::error_code& error)
{
if (!error)
{
std::string msg;
std::istream is(&m_Buffer);
std::getline(is, msg);
if(msg.empty()) return;
//cout << "Server message:" << msg << std::endl;
// TODO: you could do some message processing here, like breaking it up
// into smaller parts, rejecting unknown messages or handling the message protocol
// create signal to notify listeners
sMessage(msg);
// restart heartbeat timer (optional)
if(m_HeartBeatEnabled)
{
m_HeartBeatTimer.expires_from_now(boost::chrono::seconds(m_HeartBeatTime));
m_HeartBeatTimer.async_wait(boost::bind(&TCPClient::DoHeartBeat, this, boost::asio::placeholders::error));
}
// wait for the next message
StartRead();
}
else
{
// try to reconnect if external host disconnects
if(error.value() != 0) {
m_IsConnected = false;
// let listeners know
sDisconnected(m_Endpoint);
// cancel timers
m_HeartBeatTimer.cancel();
}
//else
//do_close();
}
}
void TCPClient::HandleWrite(const boost::system::error_code& error)
{
if(!error)
{
// write next message
m_Messages.pop_front();
if (!m_Messages.empty())
{
std::cout << "Client message:" << m_Messages.front() << std::endl;
boost::asio::async_write(m_Socket,
boost::asio::buffer(m_Messages.front()),
boost::bind(&TCPClient::HandleWrite, this, boost::asio::placeholders::error));
}
if(m_HeartBeatEnabled)
{
// restart heartbeat timer (optional)
m_HeartBeatTimer.expires_from_now(boost::chrono::seconds(m_HeartBeatTime));
m_HeartBeatTimer.async_wait(boost::bind(&TCPClient::DoHeartBeat, this, boost::asio::placeholders::error));
}
}
else
{
std::cout << "HandleWrite Error: " << error << std::endl;
}
}
void TCPClient::DoWrite(const std::string &msg)
{
if(!m_IsConnected) return;
bool write_in_progress = !m_Messages.empty();
m_Messages.push_back(msg + m_Delimiter);
if (!write_in_progress)
{
std::cout << "Client message2: " << m_Messages.front() << std::endl;
boost::asio::async_write(m_Socket,
boost::asio::buffer(m_Messages.front()),
boost::bind(&TCPClient::HandleWrite, this, boost::asio::placeholders::error));
}
else
{
std::cout << "DoWrite write_in_progress: " << msg << std::endl;
}
}
void TCPClient::HandleConnect(const boost::system::error_code& error)
{
if (!error) {
// we are connected!
m_IsConnected = true;
// let listeners know
sConnected(m_Endpoint);
// start heartbeat timer (optional)
if(m_HeartBeatEnabled)
{
m_HeartBeatTimer.expires_from_now(boost::chrono::seconds(m_HeartBeatTime));
m_HeartBeatTimer.async_wait(boost::bind(&TCPClient::DoHeartBeat, this, boost::asio::placeholders::error));
}
// await the first message
StartRead();
}
else {
// there was an error :(
m_IsConnected = false;
std::cout << "Server error:" << error.message() << std::endl;
}
}
void TCPClient::DoHeartBeat(const boost::system::error_code& error)
{
// here you can regularly send a message to the server to keep the connection alive,
// I usualy send a PING and then the server replies with a PONG
if(!error) Write( "PING" );
}
void TCPClient::SetHeartBeatTime(int time)
{
m_HeartBeatTime = time;
m_HeartBeatEnabled = true;
m_HeartBeatTimer.expires_from_now(boost::chrono::seconds(m_HeartBeatTime));
m_HeartBeatTimer.async_wait(boost::bind(&TCPClient::DoHeartBeat, this, boost::asio::placeholders::error));
}
I accept the connections using my TCPServer
.hpp file
class TCPServer
{
private:
boost::asio::io_service io_service;
boost::asio::ip::tcp::acceptor m_acceptor;
public:
TCPServer(int port);
~TCPServer();
void Close();
void StartAccept();
private:
void HandleAccept(TCPClient::pointer new_connection, const boost::system::error_code& error);
public:
boost::signals2::signal<void(const TCPClient::pointer&)> sig_NewClient;
};
.cpp file
TCPServer::TCPServer(int port)
: m_acceptor(io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
}
TCPServer::TCPServer::~TCPServer()
{
}
void TCPServer::Close()
{
m_acceptor.close();
io_service.stop();
}
void TCPServer::StartAccept()
{
TCPClient::pointer new_connection = TCPClient::Create(io_service);
m_acceptor.async_accept(new_connection->Socket(),
boost::bind(&TCPServer::HandleAccept, this, new_connection, boost::asio::placeholders::error));
io_service.run();
std::cout << "Run ended for server " << std::endl;
}
void TCPServer::HandleAccept(TCPClient::pointer new_connection, const boost::system::error_code& error)
{
if (!error)
{
sig_NewClient(new_connection);
StartAccept();
}
}
I am very new to boost and don't do too much work with c++ (normal c#, java etc) so I assume I am missing something fundamental but I can't find the issue.
Sudo flow
Server
Create TCPServer
server - StartAccept()
On new connection call StartRead on the TCPClient instance that is generated
When receive ello write olle
when receive PING write PONG
Client
Connect to server
send ello
Send PING every 10 seconds
client receives and write to the network fine
server received fine but the write never make it to DoWrite or HandleWrite method
Any additional information required please let me know.
Thanks in advance
There is few issues, some of them which i can see:
Since you dont have io_service::worker your io_service.run() will stop when there is no active handlers.
Your TCPClient::Write is trying to post() a job for socket write, but it passing reference to std::string, so when your TCPClient::DoWrite will be called, your data can be already destroyed.
There is some fundamental C++ and boost::asio usage problems, so i think its worth to start with more simple realisations.
The TCPServer accept call chain is violating an io_service requirement resulting in undefined behavior when it invokes io_service.run() within a thread that is currently servicing the event loop on the same io_service object. The documentation states:
The run() function must not be called from a thread that is currently calling one of run(), run_one(), poll() or poll_one() on the same io_service object.
In the TCPServer code, the requirement is violated when the HandleAccept() completion handler, invoked by a thread within io_service.run(), calls StartAccept(), which will then invoke io_service.run() on the same io_service:
.------------------------------------.
V |
void TCPServer::StartAccept() |
{ |
m_acceptor.async_accept(..., &HandleAccept); --. |
io_service.run(); | |
} | |
.---------------------------------' |
V |
void TCPServer::HandleAccept(...) |
{ |
if (!error) |
{ |
StartAccept(); ---------------------------------'
}
}
To resolve this, do not invoke io_service.run() within a completion handler. Instead, consider adding an entry point that initiates the accept call-chain and runs the io_service, but is not part of the asynchronous call-chain loop:
void TCPServer::StartAccept()
{
DoStartAccept(); ---------------------------------.
io_service.run(); |
} |
.---------------------------------------------'
| .------------------------------------.
V V |
void TCPServer::DoStartAccept() |
{ |
m_acceptor.async_accept(..., &HandleAccept); --. |
} | |
.---------------------------------' |
V |
void TCPServer::HandleAccept(...) |
{ |
if (!error) |
{ |
DoStartAccept(); -------------------------------'
}
}
I am trying to make my first TCP server using boost::asio. The server will listen to clients and if it receives message "MESSAGE_SEND_A:", it should send the following message back to the client: "A|A|A|A|A|A". If it receives message "MESSAGE_SEND_B:", then accordingly it should send another message to the client: "B|B|B|B|B|B".
Now, I have been studying the Boost TCP server tutorial and it is more or less clear:
EDIT: Code is rewritten based on comments
#include <ctime>
#include <iostream>
#include <string>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
{
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service& io_service)
{
return pointer(new tcp_connection(io_service));
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
// Start reading messages from the server
start_read();
if (messageFromClient_ == "MESSAGEA:")
{
messageToClient_ = "A|A|A|A|A|A|A|A|A|A";
}
else if (messageFromClient_ == "MESSAGEB:")
{
messageToClient_ = "B|B|B|B|B|B|B|B|B|B";
}
boost::asio::async_write(socket_, boost::asio::buffer(messageToClient_),
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)
{
}
// Reading messages from the server
void start_read()
{
// Start an asynchronous operation to read a newline-delimited message.
// When read, handle_read should kick in
boost::asio::async_read(socket_, input_buffer_,
boost::asio::transfer_at_least(1),
boost::bind(&tcp_connection::handle_read, this,
boost::asio::placeholders::error));
}
// When stream is received, handle the message from the client
void handle_read(const boost::system::error_code& ec)
{
//if (stopped_)
// return;
// Making the message nil every time the function starts. Do I need it???????
messageFromClient_ = "";
if (!ec)
{
// Extract the newline-delimited message from the buffer.
std::string line;
std::istream is(&input_buffer_);
std::getline(is, line);
// Empty messages are heartbeats and so ignored.
if (!line.empty())
{
messageFromClient_ = line;
std::cout << "Received: " << line << "\n";
}
start_read();
}
else
{
std::cout << "Error on receive: " << ec.message() << "\n";
}
}
void handle_write(const boost::system::error_code& /*error*/,
size_t /*bytes_transferred*/)
{
}
tcp::socket socket_;
std::string messageToClient_;
boost::asio::streambuf input_buffer_;
std::string messageFromClient_;
};
class tcp_server
{
public:
tcp_server(boost::asio::io_service& io_service)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), 7767))
{
start_accept();
}
private:
void start_accept()
{
tcp_connection::pointer new_connection =
tcp_connection::create(acceptor_.get_io_service());
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
void handle_user_read(const boost::system::error_code& err,
std::size_t bytes_transferred)
{
}
void handle_accept(tcp_connection::pointer new_connection,
const boost::system::error_code& error)
{
if (!error)
{
new_connection->start();
start_accept();
}
}
tcp::acceptor acceptor_;
};
int main()
{
try
{
boost::asio::io_service io_service;
tcp_server server(io_service);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
The code is compiled well on Qt, but gives me:
Error on receive: Operation canceled
every time I am trying to send a message from my client (iPad).
Thank you!
the function start() should asynchronously read from the socket, while invoking handle_read() function when data received.
Please review this example:
http://www.boost.org/doc/libs/1_45_0/doc/html/boost_asio/example/timeouts/async_tcp_client.cpp