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(); -------------------------------'
}
}
Related
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 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 am using boost asio in my application. I have created two ioservices. one for handling UDP sockets async operations, other to handle tcp async operations. on receiving data over udp socket, based on the data i will open a tcp connection to some other server. I am using async function calls in all places.
below is high level pseudo code.
int g_requestID ;
// boost async_recv handler
class UDPSocket
{
UDPSocket::OnDataReceived(const boost::system::error_code& error, std::size_t bytes_transferred
{
TCPSession *pSesson = new TCPSession() ;
g_requestID = pSesson->sendDataOverTCP(data);
}
}
// TCP Response Callback
void tcpResponse(int reqId)
{
if (g_requestID == reqId)
{
// received response for the request
}
}
class TCPSession
{
boost::asio::streambuf request_;
static int requestId ;
boost::tcp::socket m_socket ;
int currentsessionId = requestId ++ ;
int sendDataOverTCP(char* address, char* data)
{
m_socket.async_resolve()
return currentsessionId++ ;
}
void handle_resolve(const boost::system::error_code& err, tcp::resolver::iterator endpoint_iterator)
{
if (!err)
{
boost::asio::async_connect(m_socket, endpoint_iterator, boost::bind(&client::handle_connect, this, boost::asio::placeholders::error));
}
else
{
std::cout << "Error: " << err.message() << "\n";
}
}
void handle_connect(const boost::system::error_code& err)
{
if (!err)
{
// The connection was successful. Send the request.
boost::asio::async_write(m_socket, request_, boost::bind(&client::handle_write, this, boost::asio::placeholders::error));
}
else
{
std::cout << "Error: " << err.message() << "\n";
}
}
void handle_write(const boost::system::error_code& err)
{
if (!err)
{
boost::asio::async_read_until(socket_, response_, "\r\n", boost::bind(&client::handle_receive, this, boost::asio::placeholders::error));
}
else
{
std::cout << "Error: " << err.message() << "\n";
}
}
void handle_receive(const boost::system::error_code& err)
{
if (!err)
{
tcpResponse(currentsessionId) ;
}
else
{
std::cout << "Error: " << err << "\n";
}
}
}
Now Coming to my problem. in tcpResponse function, g_requestID contains garbage value. when i debugged by adding log statements, i found that sendDataOverTCP is returned after receiving tcpResponse callback. inside sendDataOverTcp, i am calling async_resolve only. it should return immediately. but it is not.
After debugging, I found following behaviour. async_resolve is working as expected. it is returning immediately. but sendDataOverTCP returns only after tcpResponse callback.
can anybody provide me solution, is it because of some thread scheduling ?
same code is working fine on windows. on linux, i am facing this issue.
I am using boost 1.53.0 on ubuntu 13.04 using virtual box
I have a problem, i have a TCP connection between a client and a server , when the client initialize he send a message to the server and the serveur answer by a welcom message .
All this work fine on a local network.
So my problem is that I use async_write and async_read ( because I need my server to be asynchronous )
My client send the message to the server , the server see it and answer but my client never get the welcom message .
Otherwise when I close my server , the client received the welcome message .
here is my server code :
main.cpp
int main()
{
try
{
boost::asio::io_service io_service;
tcp_server server(io_service, 7171);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
tcp_server
class tcp_server
{
public:
tcp_server(boost::asio::io_service& io_service, int port) // (1)
: m_acceptor(io_service, tcp::endpoint(tcp::v4(), port))
{
std::cout << "Port : " << port << std::endl;
start_accept();
}
private:
void start_accept()
{
tcp_connection::pointer new_connection = tcp_connection::create(m_acceptor.io_service());
m_acceptor.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
void handle_accept(tcp_connection::pointer new_connection, const boost::system::error_code& error) // (4)
{
if (!error)
{
std::cout << "Get one client!" << std::endl;
new_connection->start();
start_accept(); // (5)
}
}
tcp::acceptor m_acceptor;
};
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& ios)
{
pointer new_connection(new tcp_connection(ios) );
return new_connection;
}
tcp::socket& socket()
{
return m_socket;
}
void do_read() // (1)
{
boost::asio::async_read(m_socket, boost::asio::buffer(m_buffer), // (3)
boost::bind(&tcp_connection::handle_read, shared_from_this(),
boost::asio::placeholders::error)
);
}
void start()
{
m_message = "Welcome on the server \n";
boost::asio::async_write(m_socket, boost::asio::buffer(m_message),
boost::bind(&tcp_connection::handle_write, shared_from_this(),
boost::asio::placeholders::error)
);
}
private:
tcp_connection(boost::asio::io_service& io_service)
: m_socket(io_service)
{ }
void handle_write(const boost::system::error_code& error)
{
std::cout << "handle_write : "<< m_message << std::endl;
if (!error)
do_read(); // (2)
else
std::cout << error.message() << std::endl;
}
void handle_read(const boost::system::error_code& error) // (6)
{
std::cout << "handle read" << m_buffer.data() <<std::endl;
if (!error)
do_read();
else
close();
}
void close() // (7)
{
m_socket.close();
}
tcp::socket m_socket;
std::string m_message;
boost::array<char, 128> m_buffer;
};
I don't understand why ?
And How can I avoid this ?
Please, the manual of async_read:
This function is used to asynchronously read a certain number of bytes
of data from a stream. The function call always returns immediately.
The asynchronous operation will continue until one of the following
conditions is true:
The supplied buffers are full. That is, the bytes transferred is equal to the sum of the buffer sizes.
An error occurred.
In your case, none of the 2 conditions are satisfied - until the peer closes the socket.
You should use async_read_some instead (or async_read_until, but it might be a bit more complicated).