Persistent ASIO connections - c++

I am working on a project where I need to be able to use a few persistent to talk to different servers over long periods of time. This server will have a fairly high throughput. I am having trouble figuring out a way to setup the persistent connections correctly. The best way I could think of to do this is create a persistent connection class. Ideally I would connect to the server one time, and do async_writes as information comes into me. And read information as it comes back to me. I don't think I am structuring my class correctly though.
Here is what I have built right now:
persistent_connection::persistent_connection(std::string ip, std::string port):
io_service_(), socket_(io_service_), strand_(io_service_), is_setup_(false), outbox_()
{
boost::asio::ip::tcp::resolver resolver(io_service_);
boost::asio::ip::tcp::resolver::query query(ip,port);
boost::asio::ip::tcp::resolver::iterator iterator = resolver.resolve(query);
boost::asio::ip::tcp::endpoint endpoint = *iterator;
socket_.async_connect(endpoint, boost::bind(&persistent_connection::handler_connect, this, boost::asio::placeholders::error, iterator));
io_service_.poll();
}
void persistent_connection::handler_connect(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
if(ec)
{
std::cout << "Couldn't connect" << ec << std::endl;
return;
}
else
{
boost::asio::socket_base::keep_alive option(true);
socket_.set_option(option);
boost::asio::async_read_until(socket_, buf_ ,"\r\n\r\n", boost::bind(&persistent_connection::handle_read_headers, this, boost::asio::placeholders::error));
}
}
void persistent_connection::write(const std::string &message)
{
write_impl(message);
//strand_.post(boost::bind(&persistent_connection::write_impl, this, message));
}
void persistent_connection::write_impl(const std::string &message)
{
outbox_.push_back(message);
if(outbox_.size() > 1)
{
return;
}
this->write_to_socket();
}
void persistent_connection::write_to_socket()
{
std::string message = "GET /"+ outbox_[0] +" HTTP/1.0\r\n";
message += "Host: 10.1.10.120\r\n";
message += "Accept: */*\r\n";
boost::asio::async_write(socket_, boost::asio::buffer(message.c_str(), message.size()), strand_.wrap(
boost::bind(&persistent_connection::handle_write, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)));
}
void persistent_connection::handle_write(const boost::system::error_code& ec, std::size_t bytes_transfered)
{
outbox_.pop_front();
if(ec)
{
std::cout << "Send error" << boost::system::system_error(ec).what() << std::endl;
}
if(!outbox_.empty())
{
this->write_to_socket();
}
boost::asio::async_read_until(socket_, buf_ ,"\r\n\r\n",boost::bind(&persistent_connection::handle_read_headers, this, boost::asio::placeholders::error));
}
The first message I will send from this seems to send out fine, the server gets it, and responds with a valid response. I see two problem unfortunately:
1) My handle_write is never called after doing the async_write command, I have no clue why.
2) The program never reads the response, I am guessing this is related to #1, since asyn_read_until is not called until that function happens.
3) I was also wondering if someone could tell me why my commented out strand_.post call would not work.
I am guessing most of this has to due with my lack of knowledge of how I should be using my io_service, so if somebody could give me any pointer that would be greatly appreciated. And if you need any additional information, I would be glad to provide some more.
Thank you
Edit call to write:
int main()
{
persistent_connection p("10.1.10.220", "80");
p.write("100");
p.write("200");
barrier b(1,30000); //Timed mutex, waits for 300 seconds.
b.wait();
}
and
void persistent_connection::handle_read_headers(const boost::system::error_code &ec)
{
std::istream is(&buf_);
std::string read_stuff;
std::getline(is,read_stuff);
std::cout << read_stuff << std::endl;
}

The behavior described is the result of the io_service_'s event loop no longer being processed.
The constructor invokes io_service::poll() which will run handlers that are ready to run and will not block waiting for work to finish, where as io_service::run() will block until all work has finished. Thus, when polling, if the other side of the connection has not written any data, then no handlers may be ready to run, and execution will return from poll().
With regards to threading, if each connection will have its own thread, and the communication is a half-duplex protocol, such as HTTP, then the application code may be simpler if it is written synchronously. On the other hand, if it each connection will have its own thread, but the code is written asynchronously, then consider handling exceptions being thrown from within the event loop. It may be worth reading Boost.Asio's
effect of exceptions thrown from handlers.
Also, persistent_connection::write_to_socket() introduces undefined behavior. When invoking boost::asio::async_write(), it is documented that the caller retains ownership of the buffer and must guarantee that the buffer remains valid until the handler is called. In this case, the message buffer is an automatic variable, whose lifespan may end before the persistent_connection::handle_write handler is invoked. One solution could be to change the lifespan of message to match that of persistent_connection by making it a member variable.

Related

Cancelling boost::asio::async_read gracefully

I have a class that looks like this:
class MyConnector : public boost::noncopyable, public boost::enable_shared_from_this<MyConnector>
{
public:
typedef MyConnector this_type;
boost::asio::ip::tcp::socket _plainSocket;
boost::shared_ptr<std::vector<uint8_t>> _readBuffer;
// lot of obvious stuff removed....
void readProtocol()
{
_readBuffer = boost::make_shared<std::vector<uint8_t>>(12, 0);
boost::asio::async_read(_plainSocket, boost::asio::buffer(&_readBuffer->at(0), 12),
boost::bind(&this_type::handleReadProtocol, shared_from_this(),
boost::asio::placeholders::bytes_transferred, boost::asio::placeholders::error));
}
void handleReadProtocol(size_t bytesRead,const boost::system::error_code& error)
{
// handling code removed
}
};
This class instance is generally waiting to receive 12 bytes protocol, before trying to read the full message. However, when I try to cancel this read operation and destroy the object, it doesn't happen. When I call _plainSocket.cancel(ec), it doesn't call handleReadProtocol with that ec. Socket disconnects, but the handler is not called.
boost::system::error_code ec;
_plainSocket.cancel(ec);
And the shared_ptr of MyConnector object that was passed using shared_from_this() is not released. The object remains like a zombie in the heap memory. How do I cancel the async_read() in such a way that the MyConnector object reference count is decremented, allowing the object to destroy itself?
Two things: one, in handleReadProtocol, make sure that, if there is an error, that readProtocol is not called. Canceled operations still call the handler, but with an error code set.
Second, asio recommends shutting down and closing the socket if you're finished with the connection. For example:
asio::post([this] {
if (_plainSocket.is_open()) {
asio::error_code ec;
/* For portable behaviour with respect to graceful closure of a connected socket, call
* shutdown() before closing the socket. */
_plainSocket.shutdown(asio::ip::tcp::socket::shutdown_both, ec);
if (ec) {
Log(fmt::format("Socket shutdown error {}.", ec.message()));
ec.clear();
}
_plainSocket.close(ec);
if (ec)
Log(fmt::format("Socket close error {}.", ec.message()));
}
});

Standalone asio async_connect not firing off bound handler

I have followed the documentation and examples provided by the boost asio implementation but not having any luck after connecting my client to the server. Regardless of success or failure, the handler is never called. I have verified that the server is receiving and accepting the connection from the client but nothing happens on the clients end to indicate success.
void ssl_writer::main_thread() {
using namespace std::placeholders;
using namespace asio::ip;
tcp::resolver resolver(io_context);
tcp::resolver::query query("192.168.170.115", "8591");
tcp::resolver::iterator endpointer_iterator = resolver.resolve(query);
io_context.run();
std::cout << "connecting...";
asio::async_connect(socket.lowest_layer(), endpointer_iterator, std::bind(&ssl_writer::handle_connect, this, _1));
}
//...
void ssl_writer::handle_connect(const std::error_code& error) {
if (!error) {
std::cout << "connected!";
}
else {
std::cout << "failed!";
}
}
io_context::run() processes handlers until there are no more handlers to process. As you haven't yet run any asynchronous calls there are no handlers and run returns immediately.
In this simple example you need to call io_context::run() after async_connect, in more complex programs you would normally create a worker thread to call io_context::run() and create an instance of boost::asio::executor_work_guard to prevent the io_context running out of work.

How to make a multi-client server with synchronous dataread/write functions?

Okay, so I might have got myself a big problem here. All this time, I've been basing my code in something I might not have wanted, that is, I'm using synchronous boost::asio functions with a server that can have multiple clients at the same time. Here it is:
void session(tcp::socket socket, std::vector<Player>* pl)
{
debug("New connection! Reading username...\n");
/* ...Username verification code removed... */
debug("Client logged in safely as ");
debug(u->name);
debug("\n");
for (;;)
{
boost::array<unsigned char, 128> buf;
size_t len = socket.read_some(boost::asio::buffer(buf), error);
if (error == boost::asio::error::eof)
{
debug("Connection ended.\n");
break; // Connection closed cleanly by peer.
}
else if (error)
throw boost::system::system_error(error); // Some other error.
DataHeader ins = static_cast<DataHeader>(buf.data()[0]);
std::vector<unsigned char> response;
/* ... Get appropiate response... */
// send response
boost::system::error_code ignored_error;
boost::asio::write(socket, boost::asio::buffer(response), ignored_error);
//debug("Sent ");
//debug(response.size());
//debug("B to client.\n");
}
}
As you can see from the code, I'm using read_some and write functions in a non-ideal scenario. Now, the question is, how did I make this code usable for multiple clients at the same time? Well, I used threads:
int main()
{
try
{
boost::asio::io_context io_context;
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 13));
debug("Ready.\n");
for (;;)
{
std::thread(session, acceptor.accept(), &players).detach(); // Accept incoming clients
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
Now, I've never had a problem with this setup until recently, that I started testing multiple clients at the same time on one server. This made the server crash many times, and just until now, I thought the problem were just connection issues. However, now I've started to wonder, "Might the problem be the synchronous functions?"
All the examples I've seen until now of multi-client servers use async functions, and maybe it's because they are needed. So, my final question is, do I really need async functions? Is there anything wrong with this code to make it crash? And finally, if async functions are needed, how could I implement them? Many thanks in advance!
As user VTT has pointed out, although this approach may work for a little bit, it's just better to switch to async functions due to resource exhaustion, so, I'll just redo the entire server to implement them.

How to safely cancel a Boost ASIO asynchronous accept operation?

Everything I've read in the Boost ASIO docs and here on StackOverflow suggests I can stop an async_accept operation by calling close on the acceptor socket. However, I get an intermittent not_socket error in the async_accept handler when I try to do this. Am I doing something wrong or does Boost ASIO not support this?
(Related questions: here and here.)
(Note: I'm running on Windows 7 and using the Visual Studio 2015 compiler.)
The core problem I face is a race condition between the async_accept operation accepting an incoming connection and my call to close. This happens even when using a strand, explicit or implicit.
Note my call to async_accept strictly happens before my call to close. I conclude the race condition is between my call to close and the under-the-hood code in Boost ASIO that accepts the incoming connection.
I've included code demonstrating the problem. The program repeatedly creates an acceptor, connects to it, and immediately closes the acceptor. It expects the async_accept operation to either complete successfully or else be canceled. Any other error causes the program to abort, which is what I'm seeing intermittently.
For synchronization the program uses an explicit strand. Nevertheless, the call to close is unsynchronized with the effect of the async_accept operation, so sometimes the acceptor closes before it accepts the incoming connection, sometimes it closes afterward, sometimes neither—hence the problem.
Here's the code:
#include <algorithm>
#include <boost/asio.hpp>
#include <cstdlib>
#include <future>
#include <iostream>
#include <memory>
#include <thread>
int main()
{
boost::asio::io_service ios;
auto work = std::make_unique<boost::asio::io_service::work>(ios);
const auto ios_runner = [&ios]()
{
boost::system::error_code ec;
ios.run(ec);
if (ec)
{
std::cerr << "io_service runner failed: " << ec.message() << '\n';
abort();
}
};
auto thread = std::thread{ios_runner};
const auto make_acceptor = [&ios]()
{
boost::asio::ip::tcp::resolver resolver{ios};
boost::asio::ip::tcp::resolver::query query{
"localhost",
"",
boost::asio::ip::resolver_query_base::passive |
boost::asio::ip::resolver_query_base::address_configured};
const auto itr = std::find_if(
resolver.resolve(query),
boost::asio::ip::tcp::resolver::iterator{},
[](const boost::asio::ip::tcp::endpoint& ep) { return true; });
assert(itr != boost::asio::ip::tcp::resolver::iterator{});
return boost::asio::ip::tcp::acceptor{ios, *itr};
};
for (auto i = 0; i < 1000; ++i)
{
auto acceptor = make_acceptor();
const auto saddr = acceptor.local_endpoint();
boost::asio::io_service::strand strand{ios};
boost::asio::ip::tcp::socket server_conn{ios};
// Start accepting.
std::promise<void> accept_promise;
strand.post(
[&]()
{
acceptor.async_accept(
server_conn,
strand.wrap(
[&](const boost::system::error_code& ec)
{
accept_promise.set_value();
if (ec.category() == boost::asio::error::get_system_category()
&& ec.value() == boost::asio::error::operation_aborted)
return;
if (ec)
{
std::cerr << "async_accept failed (" << i << "): " << ec.message() << '\n';
abort();
}
}));
});
// Connect to the acceptor.
std::promise<void> connect_promise;
strand.post(
[&]()
{
boost::asio::ip::tcp::socket client_conn{ios};
{
boost::system::error_code ec;
client_conn.connect(saddr, ec);
if (ec)
{
std::cerr << "connect failed: " << ec.message() << '\n';
abort();
}
connect_promise.set_value();
}
});
connect_promise.get_future().get(); // wait for connect to finish
// Close the acceptor.
std::promise<void> stop_promise;
strand.post([&acceptor, &stop_promise]()
{
acceptor.close();
stop_promise.set_value();
});
stop_promise.get_future().get(); // wait for close to finish
accept_promise.get_future().get(); // wait for async_accept to finish
}
work.reset();
thread.join();
}
Here's the output from a sample run:
async_accept failed (5): An operation was attempted on something that is not a socket
The number in parentheses denotes how many successfully iterations the program ran.
UPDATE #1: Based on Tanner Sansbury's answer, I've added a std::promise for signaling the completion of the async_accept handler. This has no effect on the behavior I'm seeing.
UPDATE #2: The not_socket error originates from a call to setsockopt, from call_setsockopt, from socket_ops::setsockopt in the file boost\asio\detail\impl\socket_ops.ipp (Boost version 1.59). Here's the full call:
socket_ops::setsockopt(new_socket, state,
SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
&update_ctx_param, sizeof(SOCKET), ec);
Microsoft's documentation for setsockopt says about SO_UPDATE_ACCEPT_CONTEXT:
Updates the accepting socket with the context of the listening socket.
I'm not sure what exactly this means, but it sounds like something that fails if the listening socket is closed. This suggests that, on Windows, one cannot safely close an acceptor that is currently running a completion handler for an async_accept operation.
I hope someone can tell me I'm wrong and that there is a way to safely close a busy acceptor.
The example program will not cancel the async_accept operation. Once the connection has been established, the async_accept operation will be posted internally for completion. At this point, the operation is no longer cancelable and is will not be affected by acceptor.close().
The issue being observed is the result of undefined behavior. The program fails to meet a lifetime requirement for async_accept's peer parameter:
The socket into which the new connection will be accepted. Ownership of the peer object is retained by the caller, which must guarantee that it is valid until the handler is called.
In particular, the peer socket, server_conn, has automatic scope within the for loop. The loop may begin a new iteration while the async_accept operation is outstanding, causing server_conn to be destroyed and violate the lifetime requirement. Consider extending server_conn's lifetime by either:
set a std::future within the accept handler and wait on the related std::promise before continuing to the next iteration of the loop
managing server_conn via a smart pointer and passing ownership to the accept handler

Timeouts on read and writes

I have been searching for a way to cancel a Boost ASIO read or write operation if it takes over a certain amount of time. My server is sending out HTTP requests, and reading results from those requests, so I originally had coded it as a synchronous read/write, and if it took so long, I would just carry on and ignore the results when they came back. This caused a problem if a server went down, my server would open to many sockets, and would crash. So I decided that I wanted to cancel the read/write if there was too long of a delay, but apparently synchronous read/writes are not able to be canceled without destroying the thread they are running in, which I do not want to do. So I found a post about how to mimic a synchronous read/write with asynchronous calls and cancel a call on time out. This
is the post that I followed. I know this post is fairly old, so I am not sure if function calls have change since that version and the one I am working with(1.48), but this doesn't seem to be working quite right. Here is my code
bool connection::query_rtb(const std::string &request_information, std::string &reply_information)
{
try
{
boost::optional<boost::system::error_code> timer1_result, timer2_result, write_result, read_result;
boost::array<char,8192> buf;
buf.assign(0);
boost::asio::deadline_timer dt(io_service_);
dt.expires_from_now(boost::posix_time::milliseconds(100));
dt.async_wait(boost::bind(&connection::set_result, this, &timer1_result, _1, "timer1"));
boost::asio::async_write(socket_, boost::asio::buffer(request_information, request_information.size()), boost::bind(&connection::set_result, this, &write_result, _1, "write"));
io_service_.reset();
while(io_service_.run_one())
{
if(write_result)
{
dt.cancel();
}
else if(timer1_result)
{
socket_.cancel();
}
}
boost::asio::deadline_timer dt2(io_service_);
dt2.expires_from_now(boost::posix_time::milliseconds(3000));
dt2.async_wait(boost::bind(&connection::set_result, this, &timer2_result, _1, "timer2"));
boost::asio::async_read(socket_, boost::asio::buffer(buf), boost::bind(&connection::set_result, this, &read_result, _1, "read"));
//socket_.async_receive(boost::asio::buffer(buf), boost::bind(&connection::set_result, this, &read_result, _1, "read"));
io_service_.reset();
while(io_service_.run_one())
{
if(read_result)
{
dt2.cancel();
}
if(timer2_result)
{
socket_.cancel();
}
}
reply_information = buf.data();
std::cout << reply_information << std::endl;
return true;
}catch(std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
void persistent_connection::set_result(boost::optional<boost::system::error_code> *a, boost::system::error_code ec, std::string t)
{
std::cout << t << std::endl;
a->reset(ec);
}
I was wondering if anyone see anything wrong with this code, or has any ideas on why it is not working. Currently the write seems to be fine, however the will not read until after the dt2 is done with it's timer. Please let me know if you need any more information, I will be glad to provide some.
Edit:
Seems like I got it working testing something I thought I previously tested. Using async_receive instead of async_read seems to have fixed whatever problem I was having. Any clue why this would cause I problem? I want to know if there is a problem with my logic or if that is how is async_read will usually act.
boost::array<char,8192> buf;
...
boost::asio::async_read(socket_, boost::asio::buffer(buf), boost::bind(&connection::set_result, this, &read_result, _1, "read"));
You have instructed your program to read 8192 bytes from the socket. If switching the logic from using the async_read() free function to the async_receive() member function resolves this problem, consult the documentation
Remarks
The receive operation may not receive all of the requested number of
bytes. Consider using the async_read function if you need to ensure
that the requested amount of data is received before the asynchronous
operation completes.