libboost ASIO. Simple asynchronous client server - c++

I'm trying to implement a simple client/server in ASIO.
I'd like the following on the serverside:
onConnect()
onDisconnect()
onMessageRecieved(char* data)
sendMessage(char* data)
and on the client side:
onConnect()
onDisconnect()
onMessageRecieved(char* data)
sendMessage(char* data)
I didn't realise things would be so complicated.
Here's the simple echo server which I'm working off of:
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
class session
{
public:
session(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
}
else
{
delete this;
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
delete this;
}
}
private:
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
session* new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
else
{
delete new_session;
}
}
private:
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
boost::asio::io_service io_service;
using namespace std; // For atoi.
server s(io_service, atoi(argv[1]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
I can telnet into this server and everything is echoed.
Now I'd like to wrap up this code in onConnect(), onDisconnect(), onMessageReceived(char* data), etc. Similar to the way things are done in Node.js!
Has anyone got any pointers in this regard?

onMessageReceived() can be called from handle_read.
onConnect() can be called from start.
onDisconnect() can be called in the destructor of the session class.
For the bounty questions:
The io_service.run() can be placed in its own thread.
As per the documentation
Certain guarantees are made on when the handler may be invoked, in particular that a handler can only be invoked from a thread that is currently calling run() on the corresponding io_service object.
Asynchronous sending and receiving can be handled by this single thread. This simplifies thread safety because all the callbacks will be running in succession. This is probably the simplest way of using boost asio.
For calls coming from outside of the run() thread, you can schedule a callback (e.g. deadline_timer), from the 'outside thread' for immediate calling to simplify your thread safety handling. e.g.
boost::asio::deadline_timer timer(io_service);
timer.expires_from_now(boost::posix_time::seconds(0));
timer.async_wait(boost::bind(&MyClass::MyCallback, this, boost::asio::placeholders::error);
The io_service object will call the handler for you in a thread-safe fashion as soon as it has a chance. This way, your asio code can behave as if there was only a single thread in the entire system.
If multiple threads are required or preferred (e.g. Take advantage of multi-core) you may call run() on multiple thread. Handlers will have to be re-entrant. You may also want to use a strand for certain operations.
Otherwise, regular thread safety rules applies.

Related

How can I add an async timer to a boost UDP server?

I got this code online and have been trying to add a timer to it so that it reads a packet every so often. I can't seem to figure out how to pass a callback function to the boost::async_wait command because I am getting this error:
server1.cpp: In member function ‘void UDPClient::handle_receive(const boost::system::error_code&, size_t)’:
server1.cpp:51:66: error: invalid use of non-static member function ‘void UDPClient::time_to_receive(const boost::system::error_code&)’
boost::asio::placeholders::error));
^
server1.cpp:33:6: note: declared here
void UDPClient::time_to_receive(const boost::system::error_code& error)
^~~~~~~~~
UDPClient Class:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/array.hpp>
#include <iostream>
#include <boost/date_time/posix_time/posix_time.hpp>
using boost::asio::ip::udp;
class UDPClient
{
public:
boost::asio::io_service io_service;
udp::socket socket;
udp::endpoint receiver_endpoint;
boost::asio::deadline_timer timer;
boost::array<char, 1024> recv_buffer;
UDPClient();
void time_to_receive(const boost::system::error_code& error);
void do_receive();
void handle_receive(const boost::system::error_code& error, size_t);
};
UDPClient::UDPClient()
: io_service(),
socket(io_service, {udp::v4(), 3643}),
timer(io_service, boost::posix_time::seconds(2))
{
do_receive();
io_service.run();
}
void UDPClient::time_to_receive(const boost::system::error_code& error)
{
do_receive();
}
void UDPClient::do_receive()
{
socket.async_receive_from(boost::asio::buffer(recv_buffer), receiver_endpoint,
boost::bind(&UDPClient::handle_receive, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void UDPClient::handle_receive(const boost::system::error_code& error, size_t bytes_transferred)
{
std::cout << "Received: '" << std::string(recv_buffer.begin(), recv_buffer.begin()+bytes_transferred) << "'\n";
timer.async_wait(boost::bind(time_to_receive,
boost::asio::placeholders::error));
}
int main()
{
UDPClient updclient;
}
One question I'm trying to answer with this code is if I spam the server from a client with a bunch of UDP packets will the server ignore all the packets during the async_wait?
Also, my main goal is to put this code into a quadcopter code I have. Will it work the way it is written to instantiate this class and have it read packets from a ground station to get user input?
The way you use bind with member function is wrong. Use it like shown below:
timer.async_wait(boost::bind(&UDPClient::time_to_receive, this,
boost::asio::placeholders::error));
As to why it is like that, I would recommend you to read the boost docs for that.
Also, I have modified the code to make it actually run like a server without exiting. For that I made following 2 changes:
Initialzation of io_service in main function and pass its reference to the class.
Initialize a io_service_work object. This acts as a perennial source of work to the io_service. Thus, io_service never returns from the run function unless the work object is destroyed.
The complete source:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/array.hpp>
#include <iostream>
#include <boost/date_time/posix_time/posix_time.hpp>
using boost::asio::ip::udp;
class UDPClient
{
public:
boost::asio::io_service& io_service;
udp::socket socket;
udp::endpoint receiver_endpoint;
boost::asio::deadline_timer timer;
boost::array<char, 1024> recv_buffer;
UDPClient(boost::asio::io_service&);
void time_to_receive(const boost::system::error_code& error);
void do_receive();
void handle_receive(const boost::system::error_code& error, size_t);
};
UDPClient::UDPClient(boost::asio::io_service& ios)
: io_service(ios),
socket(io_service, {udp::v4(), 3643}),
timer(io_service, boost::posix_time::seconds(2))
{
do_receive();
}
void UDPClient::time_to_receive(const boost::system::error_code& error)
{
do_receive();
}
void UDPClient::do_receive()
{
socket.async_receive_from(boost::asio::buffer(recv_buffer), receiver_endpoint,
boost::bind(&UDPClient::handle_receive, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void UDPClient::handle_receive(const boost::system::error_code& error, size_t bytes_transferred)
{
std::cout << "Received: '" << std::string(recv_buffer.begin(), recv_buffer.begin()+bytes_transferred) << "'\n";
timer.expires_from_now(boost::posix_time::seconds(2));
timer.async_wait(boost::bind(&UDPClient::time_to_receive, this,
boost::asio::placeholders::error));
}
int main()
{
boost::asio::io_service ios;
boost::asio::io_service::work wrk(ios);
UDPClient updclient(ios);
ios.run();
}
NOTE: Even though it is a server, the class is name Client. I am ignoring that :)

io_service.run() isn't blocking. Server is created and then closes instantly

All of the boost examples work until I try to implement the exact same thing myself. I'm starting to think there must be an order of creation or io_service ownership for things to block properly.
My server structure is as follows:
class Server {
public:
Server(unsigned short port)
: ioService_(), acceptor_(ioService_), socket_(ioService_) {
acceptClient(); // begin async accept
}
void start(); // runs ioService_.run();
private:
void acceptClient();
asio::io_service ioService_;
tcp::acceptor acceptor_;
tcp::socket socket_;
Cluster cluster_; // essentially just a connection manager
};
The acceptClient() function works like this:
void Server::acceptClient() {
acceptor_.async_accept(socket_, [this](const system::error_code& e){
if(!acceptor_.is_open()) return;
if(!e) {
cluster_.add(std::make_shared<Client>(std::move(socket_), cluster_));
}
acceptClient();
});
}
I'm not sure if you need an outline of the Client class since the server should run and block even with no clients.
The creation of the server goes as follows:
try {
Server server(port);
server.start(); // this calls the server's member io_service's run();
} catch (const std::exception& e) {
std::cerr << e.what(); << std::endl;
}
The problem is the server instantly closes after that call. The program starts and then exits with no errors. Is there something that io_service.run() relies on? e.g. some form of asynchronous link that I've forgotten? My learned this design from boost asio's http server design but I've worked it to fit my basic purposes. The problem is some boost examples establish a new member boost tcp::socket in the client itself rather than moving the server's to the client so I'm quite confused. They also tend to use boost's versions of std::bind instead of lambdas which etc.
So, can anyone give me a brief rundown on how to create a basic, stripped, async server since the boost examples are really confusing since the code conventions differ per example. I was wondering if anybody noticed anything straight away that would cause my server to instantly close.
Thanks.
I tested async_accept with the following code which sends Hello to clients connecting to the port. At least there is the creation of endpoint object, acceptor.open(endpoint.protocol()), acceptor.bind(endpoint) and acceptor.listen() calls that seem to be missing from your code.
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <string>
using namespace boost::asio;
void handle_accept(
io_service * ios,
ip::tcp::acceptor * acceptor,
ip::tcp::socket * socket,
const boost::system::error_code & error)
{
if (!error) {
std::string msg("Hello\n");
socket->send(buffer(msg, msg.length()));
ip::tcp::socket * temp = new ip::tcp::socket(*ios);
acceptor->async_accept(*temp,
boost::bind(handle_accept,
ios, acceptor, temp,
placeholders::error));
}
}
int main(void)
{
io_service ios;
ip::tcp::socket socket(ios);
ip::tcp::acceptor acceptor(ios);
ip::tcp::endpoint endpoint(ip::tcp::v4(), 1500);
acceptor.open(endpoint.protocol());
acceptor.set_option(ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.listen();
acceptor.async_accept(socket,
boost::bind(handle_accept,
&ios, &acceptor, &socket,
placeholders::error));
ios.run();
/*
acceptor.accept(socket);
std::string msg("Hello\n");
socket.send(buffer(msg, msg.length()));
*/
}
A version with a Server class and a lambda as a argument for async_accept:
#include <boost/asio.hpp>
#include <functional>
#include <string>
using namespace boost::asio;
class Server {
public:
Server(unsigned short port) : ios(), acceptor(ios), socket(ios),
endpoint(ip::tcp::v4(), port) {
acceptor.open(endpoint.protocol());
acceptor.set_option(ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.listen();
nsocket = &socket;
}
void run() {
std::function<void (const boost::system::error_code &)> f;
f = [&f, this] (const boost::system::error_code & error) {
if (!error) {
std::string msg("Hello\n");
nsocket->send(buffer(msg, msg.length()));
nsocket = new ip::tcp::socket(ios);
acceptor.async_accept(*nsocket, f);
}
};
acceptor.async_accept(socket, f);
ios.run();
}
protected:
io_service ios;
ip::tcp::acceptor acceptor;
ip::tcp::socket socket;
ip::tcp::endpoint endpoint;
ip::tcp::socket * nsocket;
};
int main(void)
{
Server srv(1500);
srv.run();
}

How can I setup a deadline_timer in this environment?

I am a bit lost in a construct of libraries, which I have to tangle together. I need help to indtroduce some timers into this construct.
I have the following:
com.cpp which has main and includes com.hpp
com.hpp which includes a host.h and needed boost includes and defines a class comClient
host.c with included host.h
wrapper.cpp with included com.hpp and some needed boost includes
Now, my com.cpp is creating a comClient and uses it for asynch communication on the com-port. Using boost::asio::serial_port and boost::asio::io_service.
I need to work with some timers, in order to catch when a paket needed too long to transmit.
When creating an instance of comClient, the paket-timer should be initialised.
Using asynch_read_some in a private function of comClient, I call a private handler of comClient, then this handler calls a function of host.c, which calls to the wrapper.cpp a function to restart the timer.
This is the function to init the timer:
//wrapper.cpp
void IniPacketTimer(void *pCHandle){
boost::asio::io_service io;
boost::asio::deadline_timer t(io, boost::posix_time::milliseconds(25));
t.async_wait(&hostOnTimeout(pCHandle));
io.run();
}
This would be the command chain in short:
//comClient.cpp
main{
comClient cc();
}
//comClient.hpp
class comClient(boost::asio::io_service& io_service){
comClient(){
hostInit();
aread();
}
private:
aread( call aread_done)
areaddone(call hostNewData())
}
//host.c
hostInit(){
IniPacketTimer()
}
hostNewData(){
resetTimer
}
//wrapper.cpp
resetTimer(){
t.expires_from_now
}
Questions:
How can I provide an asynchronous timer, which does not affect the asynch read/write operations on my serial port, but triggers execution of a function when the deadline is hit?
Should I use the already existing io_service or is it ok, if I just create another?
Why do I get an error C2102 '&' expects L-Value for my line t.async_wait?
You problem is not clear and since you don't post real code it is quite hard to guess what your problem is.
Especially your threading is not clear but for asio very important.
Below is an example that will compile but not run. I hope it gives you an hint on how to proceed.
It will open a serial port and a timer. Whenever the timer expires it will start a new one. It is a stripped version of code I used some time ago so maybe it will help you.
#include <boost/asio.hpp>
#include <boost/asio/serial_port.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <vector>
class SerialCommunication
{
public:
SerialCommunication(boost::asio::io_service& io_service, const std::string& serialPort)
: m_io_service(io_service)
, m_serialPort(m_io_service)
, m_timeoutTimer(m_io_service, boost::posix_time::milliseconds(5))
{
configureSerialPort(serialPort);
}
void configureSerialPort(const std::string& serialPort)
{
if(m_serialPort.is_open())
{
m_serialPort.close();
m_timeoutTimer.cancel();
}
boost::system::error_code ec;
m_serialPort.open(serialPort, ec);
if(m_serialPort.is_open())
{
// start Timer
m_timeoutTimer.async_wait(boost::bind(&SerialCommunication::TimerExpired, this, _1));
header_sync();
}
}
void header_sync()
{
m_serialPort.async_read_some(boost::asio::buffer(&m_header.back(), 1),
boost::bind(&SerialCommunication::header_sync_complete, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void header_sync_complete(
const boost::system::error_code& error, size_t bytes_transferred)
{
// stripped
read_payload(&m_payload[0], 0);
}
void read_payload(uint8_t* buffer, uint8_t length)
{
m_serialPort.async_read_some(boost::asio::buffer(buffer, length),
boost::bind(&SerialCommunication::payload_read_complete, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void payload_read_complete(
const boost::system::error_code& error, size_t bytes_transferred)
{
// stripped
// timer cancel and reset
m_timeoutTimer.cancel();
m_timeoutTimer.expires_at(boost::posix_time::microsec_clock::local_time() +
boost::posix_time::milliseconds(5));
m_timeoutTimer.async_wait(boost::bind(&SerialCommunication::TimerExpired, this, _1));
memset(&m_header[0], 0, 3);
header_sync();
}
void TimerExpired(const boost::system::error_code& e)
{
m_timeoutTimer.expires_at(m_timeoutTimer.expires_at() + boost::posix_time::milliseconds(5));
m_timeoutTimer.async_wait(boost::bind(&SerialCommunication::TimerExpired, this, _1));
}
boost::asio::io_service& m_io_service;
boost::asio::deadline_timer m_timeoutTimer;
boost::asio::serial_port m_serialPort;
std::vector<uint8_t> m_header;
std::vector<uint8_t> m_payload;
};
int main()
{
boost::asio::io_service io_service;
SerialCommunication cc(io_service, "/dev/ttyS0");
io_service.run();
return 0;
}

boost::asio::async_write from outside class

If programming a tcp server using boost.asio using the example of the echo server , i have modified some of its codes to meet my requirements where i want to process the incoming data and send back the results, i used a class for socket handling "socket.h" and i want to make another handler in for the data in the file "handler.h" , my problem now is how can i pass data to the function in handler.h and send data back from this function passing through socket.h ??
socket.h
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <json/json.h>
#include "handler.h"
using namespace std;
using boost::asio::ip::tcp;
class session {
public:
session(boost::asio::io_service& io_service) : socket_(io_service) {}
tcp::socket& socket() { return socket_; }
/* listen for first input data after connection established */
void start() {
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handleIncome,this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)); }
/* handle incoming data */
void handleIncome(const boost::system::error_code& error, size_t bytes_transferred) {
/* data is recieved in var data_ */
if (!error) {
/********************* Data Handler ****************************/
callHandler(data_); //this is in handler.cpp
/**************************************************************/
} else { delete this; } }
/* get more input */
void getIncome(const boost::system::error_code& error) {
if (!error) {
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handleIncome, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)); }
else { delete this; } }
/* send outcome back to client */
void sendOutcome(const std::string dout, size_t bytes_out) {
boost::asio::async_write(socket_,boost::asio::buffer(dout, bytes_out),
boost::bind(&session::getIncome, this,boost::asio::placeholders::error)); }
private:
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class DServer {
public:
DServer(boost::asio::io_service& io_service, short port)
:io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
session* new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&DServer::handle_accept,this,new_session,boost::asio::placeholders::error));
}
void handle_accept(session* new_session,const boost::system::error_code& error) {
if (!error) {
new_session->start();
new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),boost::bind(&DServer::handle_accept, this, new_session,boost::asio::placeholders::error));}
else { delete new_session; } }
private:
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
handler.cpp
void callHandler(string data) {
/* here i want to process data and after that i want to send back the result to the same client ofcourse using the function sendOutcome() in the socket.h file */
}
The most common way to return data from a function is by returning it:
string callHandler(string data);
sendOutcome(callHandler(data_));
If you need more flexibility (e.g. to send multiple responses, or to do something else with the socket), then either pass a reference to the socket, or pass a reference to the session object (perhaps using an abstract interface to decouple it from the class implementation).
First of all, you must make sure, that you have received all the data you need. Your handler should cope with a scenario where the handleIncome() callback is called with bytes_transferred being 1 even if the entire request is much larger.
And of cause, you should not use the read callback as parameter to the async_write() function.

Does boost::asio makes excessive small heap allocations or am I wrong?

#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
class session
{
public:
session(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
socket_.async_read_some(boost::asio::buffer(data_, max_length - 1),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
data_[bytes_transferred] = '\0';
if(NULL != strstr(data_, "quit"))
{
this->socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both);
this->socket().close(); // how to make this dispatch "handle_read()" with a "disconnected" flag?
}
else
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
socket_.async_read_some(boost::asio::buffer(data_, max_length - 1),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
else
{
delete this;
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
//
}
else
{
delete this;
}
}
private:
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
session* new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
else
{
delete new_session;
}
}
private:
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
boost::asio::io_service io_service;
using namespace std; // For atoi.
server s(io_service, atoi(argv[1]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
While experimenting with boost::asio I've noticed that within the calls to async_write()/async_read_some() there is a usage of the C++ "new" keyword.
Also, when stressing this echo server with a client (1 connection) that sends for example 100,000 times some data, the memory usage of this program is getting higher and higher.
What's going on? Will it allocate memory for every call? Or am I wrong? Asking because it doesn't seem right that a server app will allocate, anything. Can I handle it, say with a memory pool?
Another side-question:
See the "this->socket().close();" ?
I want it, as the comment right to it says, to dispatch that same function one last time, with a disconnection error. Need that to do some clean-up. How do I do that?
Thank you all gurus (:
In the hope that someone will contribute something...
Further in my experiments at boost::asio I've decided that right after the server app is up & running I'll put a breakpoint at C++'s 'new' code, i.e at "new.cpp" # function "void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)". Note, I'm using MSVC 2008.
Using the code above, of the original post:
Now that the BP is on I'm connecting one client.
Allocation is done (several times) (as expected) (I know it because the debugger stops at the 'new' keyword as I set) and the new client is now ready to send/receive data.
I send "hi" from the client to the server.
The BP at 'new' is hit at handle_read().
The source to is was the call to async_write() (I stack trace with MSVC).
Hitting F5 (continue) generates another breakpoint at 'new' - this time the async_read_some() call generated it.
Conclusion:
Each such operation generates a call to 'new' !!!!!! Worst case a real server might have!
So, further on looking for some way to use some sort of memory pool so these 'new' calls won't exist brought me to the example: "allocation".
Path to it: ".......\boost_1_43_0\libs\asio\example\allocation\".
Doing the same with this new code (written below) gave me cheering results;
Calls to async_write() and async_read_some() do not generate a call to 'new'.
So far it's nice, but to be honest I can't say I understand exactly how this is done; The allocator is broke down into several pieces as you can see, and that makes things a bit confusing to me.
make_custom_alloc_handler() <--- what exactly does it do?
What's shared_from_this()??
I see that a "session" object has the member "handler_allocator allocator_". Does every "session" object holds a pool of these objects?! Can I have one of this, at the "server" class level which will be shared or something?
"allocator" example code:
//
// server.cpp
// ~~~~~~~~~~
//
// Copyright (c) 2003-2010 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <cstdlib>
#include <iostream>
#include <boost/aligned_storage.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
// Class to manage the memory to be used for handler-based custom allocation.
// It contains a single block of memory which may be returned for allocation
// requests. If the memory is in use when an allocation request is made, the
// allocator delegates allocation to the global heap.
class handler_allocator
: private boost::noncopyable
{
public:
handler_allocator()
: in_use_(false)
{
}
void* allocate(std::size_t size)
{
if (!in_use_ && size < storage_.size)
{
in_use_ = true;
return storage_.address();
}
else
{
return ::operator new(size);
}
}
void deallocate(void* pointer)
{
if (pointer == storage_.address())
{
in_use_ = false;
}
else
{
::operator delete(pointer);
}
}
private:
// Storage space used for handler-based custom memory allocation.
boost::aligned_storage<1024> storage_;
// Whether the handler-based custom allocation storage has been used.
bool in_use_;
};
// Wrapper class template for handler objects to allow handler memory
// allocation to be customised. Calls to operator() are forwarded to the
// encapsulated handler.
template <typename Handler>
class custom_alloc_handler
{
public:
custom_alloc_handler(handler_allocator& a, Handler h)
: allocator_(a),
handler_(h)
{
}
template <typename Arg1>
void operator()(Arg1 arg1)
{
handler_(arg1);
}
template <typename Arg1, typename Arg2>
void operator()(Arg1 arg1, Arg2 arg2)
{
handler_(arg1, arg2);
}
friend void* asio_handler_allocate(std::size_t size,
custom_alloc_handler<Handler>* this_handler)
{
return this_handler->allocator_.allocate(size);
}
friend void asio_handler_deallocate(void* pointer, std::size_t /*size*/,
custom_alloc_handler<Handler>* this_handler)
{
this_handler->allocator_.deallocate(pointer);
}
private:
handler_allocator& allocator_;
Handler handler_;
};
// Helper function to wrap a handler object to add custom allocation.
template <typename Handler>
inline custom_alloc_handler<Handler> make_custom_alloc_handler(
handler_allocator& a, Handler h)
{
return custom_alloc_handler<Handler>(a, h);
}
class session
: public boost::enable_shared_from_this<session>
{
public:
session(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
socket_.async_read_some(boost::asio::buffer(data_),
make_custom_alloc_handler(allocator_,
boost::bind(&session::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_transferred)
{
if (!error)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
make_custom_alloc_handler(allocator_, boost::bind(&session::handle_write, shared_from_this(), boost::asio::placeholders::error))
);
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_),
make_custom_alloc_handler(allocator_,
boost::bind(&session::handle_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
}
private:
// The socket used to communicate with the client.
tcp::socket socket_;
// Buffer used to store data received from the client.
boost::array<char, 1024> data_;
// The allocator to use for handler-based custom memory allocation.
handler_allocator allocator_;
};
typedef boost::shared_ptr<session> session_ptr;
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
session_ptr new_session(new session(io_service_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session_ptr new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
new_session.reset(new session(io_service_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
}
private:
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: server <port>\n";
return 1;
}
boost::asio::io_service io_service;
using namespace std; // For atoi.
server s(io_service, atoi(argv[1]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
To answer your second question, you could use io_service::post by giving it a boost::bind parameter bound to session::handle_read and whatever error code you want.