Reputation: 4948
I have been reading some Boost ASIO tutorials. So far, my understanding is that the entire send and receive is a loop that can be iterated only once. Please have a look at the following simple code:
client.cpp:
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <iostream>
#include <string>
boost::asio::io_service io_service;
boost::asio::ip::tcp::resolver resolver(io_service);
boost::asio::ip::tcp::socket sock(io_service);
boost::array<char, 4096> buffer;
void read_handler(const boost::system::error_code &ec, std::size_t bytes_transferred)
{
if (!ec)
{
std::cout << std::string(buffer.data(), bytes_transferred) << std::endl;
sock.async_read_some(boost::asio::buffer(buffer), read_handler);
}
}
void connect_handler(const boost::system::error_code &ec)
{
if (!ec)
{
sock.async_read_some(boost::asio::buffer(buffer), read_handler);
}
}
void resolve_handler(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator it)
{
if (!ec)
{
sock.async_connect(*it, connect_handler);
}
}
int main()
{
boost::asio::ip::tcp::resolver::query query("localhost", "2013");
resolver.async_resolve(query, resolve_handler);
io_service.run();
}
the program resolves
an address, connects
to server and reads
the data, and finally ends
when there is no data.
My question: How can i continue this loop? I mean, How can I keep this connection between a client and server during the entire lifetime of my application so that the server sends data whenever it has something to send?
I tried to break this circle but everything seams trapped inside io_service.run()
Same question holds in case of the my sever also:
server.cpp :
#include <boost/asio.hpp>
#include <string>
boost::asio::io_service io_service;
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 2013);
boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint);
boost::asio::ip::tcp::socket sock(io_service);
std::string data = "Hello, world!";
void write_handler(const boost::system::error_code &ec, std::size_t bytes_transferred)
{
}
void accept_handler(const boost::system::error_code &ec)
{
if (!ec)
{
boost::asio::async_write(sock, boost::asio::buffer(data), write_handler);
}
}
int main()
{
acceptor.listen();
acceptor.async_accept(sock, accept_handler);
io_service.run();
}
This is just an example. In a real application, I may like to keep the socket open and reuse it for other data exchanges(both read and write). How may I do that.
I value your kind comments. If you have references to some easy solutions addressing this issue, I appreciate if you mention it. Thank you
Update (server sample code)
Based on the answer given below(update 2), I wrote the server code. Please note that the code is simplified (able to compile&run though). Also note that the io_service will never return coz it is always is waiting for a new connection. And that is how the io_service.run
never returns and runs for ever. whenever you want io_service.run to return, just make the acceptor not to accept anymore. please do this in one of the many ways that i don't currently remember.(seriously, how do we do that in a clean way? :) )
enjoy:
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <string>
#include <iostream>
#include <vector>
#include <time.h>
boost::asio::io_service io_service;
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 2013);
boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint);
//boost::asio::ip::tcp::socket sock(io_service);
std::string data = "Hello, world!";
class Observer;
std::vector<Observer*> observers;
class Observer
{
public:
Observer(boost::asio::ip::tcp::socket *socket_):socket_obs(socket_){}
void notify(std::string data)
{
std::cout << "notify called data[" << data << "]" << std::endl;
boost::asio::async_write(*socket_obs, boost::asio::buffer(data) , boost::bind(&Observer::write_handler, this,boost::asio::placeholders::error));
}
void write_handler(const boost::system::error_code &ec)
{
if (!ec) //no error: done, just wait for the next notification
return;
socket_obs->close(); //client will get error and exit its read_handler
observers.erase(std::find(observers.begin(), observers.end(),this));
std::cout << "Observer::write_handler returns as nothing was written" << std::endl;
}
private:
boost::asio::ip::tcp::socket *socket_obs;
};
class server
{
public:
void CreatSocketAndAccept()
{
socket_ = new boost::asio::ip::tcp::socket(io_service);
observers.push_back(new Observer(socket_));
acceptor.async_accept(*socket_,boost::bind(&server::handle_accept, this,boost::asio::placeholders::error));
}
server(boost::asio::io_service& io_service)
{
acceptor.listen();
CreatSocketAndAccept();
}
void handle_accept(const boost::system::error_code& e)
{
CreatSocketAndAccept();
}
private:
boost::asio::ip::tcp::socket *socket_;
};
class Agent
{
public:
void update(std::string data)
{
if(!observers.empty())
{
// std::cout << "calling notify data[" << data << "]" << std::endl;
observers[0]->notify(data);
}
}
};
Agent agent;
void AgentSim()
{
int i = 0;
sleep(10);//wait for me to start client
while(i++ < 10)
{
std::ostringstream out("");
out << data << i ;
// std::cout << "calling update data[" << out.str() << "]" << std::endl;
agent.update(out.str());
sleep(1);
}
}
void run()
{
io_service.run();
std::cout << "io_service returned" << std::endl;
}
int main()
{
server server_(io_service);
boost::thread thread_1(AgentSim);
boost::thread thread_2(run);
thread_2.join();
thread_1.join();
}
Upvotes: 2
Views: 745
Reputation: 24626
You can simplify the logic of asio based porgrams like follows: each function that calls an async_X function provides a handler. This is a bit like transitions between states of a state machine, where the handlers are the states and the async-calls are transitions between states. Just exiting a handler without calling a async_* function is like a transition to an end state. Everything the program "does" (sending data, receiving data, connecting sockets etc.) occurs during the transitions.
If you see it that way, your client looks like this (only "good path", i.e. without errors):
<start> --(resolve)----> resolve_handler
resolve_handler --(connect)----> connect_handler
connect_handler --(read data)--> read_handler
read_handler --(read data)--> read_handler
Your server loks like this:
<start> --(accept)-----> accept handler
accept_handler --(write data)-> write_handler
write_handler ---------------> <end>
Since your write_handler
does not do anything, it makes a transition to the end state, meaning ioservice::run
returns. The question now is, what do you want to do, after the data has been written to the socket? Depending on that, you will have to define a corresponding transition, i.e. an async-call that does what you want to do.
Update: from your comment I see you want to wait for the next data to be ready i.e. for the next tick. The transitions then look like this:
write_handler --(wait for tick/data)--> dataready
dataready --(write data)----------> write_handler
You see, this introduces a new state (handler), I called it dataready
, you could as well call it tick_handler
or something else. The transition back to the write_handler is easy:
void dataready()
{
// get the new data...
async_write(sock, buffer(data), write_handler);
}
The transition from the write_handler
can be a simple async_wait
on some timer. If the data come from "outside" and you don't know exactly when they will be ready, wait for some time, check if the data are there, and if they are not, wait some more time:
write_handler --(wait some time)--> checkForData
checkForData:no --(wait some time)--> checkForData
checkForData:yes --(write data)------> write_handler
or, in (pseudo)code:
void write_handler(const error_code &ec, size_t bytes_transferred)
{
//...
async_wait(ticklenght, checkForData);
}
void checkForData(/*insert wait handler signature here*/)
{
if (dataIsReady())
{
async_write(sock, buffer(data), write_handler);
}
else
{
async_wait(shortTime, checkForData):
}
}
Update 2: According to your comment, you already have an agent that does the time management somehow (calling update every tick). Here's how I would solve that:
I am not very firm in the exact syntax of ASIO, so this will be handwavy pseudocode:
Server:
void Server::accept_handler()
{
obs = new Observer(socket);
agent.register(obs);
new socket; //observer takes care of the old one
async_accept(..., accept_handler);
}
Agent:
void Agent::update()
{
if (newDataAvailable())
{
for (auto& obs : observers)
{
obs->notify(data);
}
}
}
Observer:
void Observer::notify(data)
{
async_write(sock, data, write_handler);
}
void Observer::write_handler(error_code ec, ...)
{
if (!ec) //no error: done, just wait for the next notification
return;
//on error: close the connection and unregister
agent.unregister(this);
socket.close(); //client will get error and exit its read_handler
}
Upvotes: 5