I'm novice in boost::asio and have the first own troubles.
I create a simple Host resolver (see full code below).
Problem 1.
In case of lost Internet connection, my host resolver stops resolving after the first enter into deadline_timer.
My assumption, that "localhost" must be resolved at any time. But "localhost" are not resolved after timeout during resolving google.us (for example, we unplugged Ethernet jack).
The same behaviour in case of resolving unexisted TLD (for example, google.usd instead google.us).
Problem 2.
In case of lost Internet connection, destructor io_service runs very long (usually, 5 seconds).
What's wrong?
I use VS2012, boost 1.54
File hostresolver.h
pragma once
#include <set>
#include <boost/system/error_code.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ip/basic_resolver.hpp>
#include <boost/asio/ip/basic_resolver_iterator.hpp>
typedef std::set<unsigned long> hostresolver_result_container;
class hostresolver
{
public:
hostresolver(boost::asio::io_service* io_service);
~hostresolver(void);
boost::asio::io_service* ios_ptr;
boost::asio::ip::tcp::resolver resolver_;
boost::asio::deadline_timer timer_;
volatile bool is_completed;
bool is_timeout;
std::string hostname;
hostresolver_result_container result;
void on_timeout(const boost::system::error_code &err);
void start_resolve(const char* hostname, int timeout_seconds);
void finish_resolve(const boost::system::error_code& err, boost::asio::ip::tcp::resolver::iterator endpoint_iterator);
private:
void stop();
};
File hostresolver.cpp
#include "stdafx.h"
#include "hostresolver.h"
#include <boost/bind.hpp>
hostresolver::hostresolver(boost::asio::io_service* io_service) :
resolver_(*io_service), timer_(*io_service), is_completed(false), is_timeout(false)
{
ios_ptr = io_service;
}
hostresolver::~hostresolver(void)
{
}
void hostresolver::start_resolve(const char* hostname, int timeout_second)
{
this->hostname.assign(hostname);
timer_.expires_from_now(boost::posix_time::seconds(timeout_second));
timer_.async_wait(boost::bind(&hostresolver::on_timeout, this, _1));
boost::asio::ip::tcp::resolver::query query(hostname, "http");
resolver_.async_resolve(query,
boost::bind(&hostresolver::finish_resolve, this,
boost::asio::placeholders::error,
boost::asio::placeholders::iterator));
do
{
ios_ptr->run_one();
}
while (!is_completed);
}
void hostresolver::stop()
{
resolver_.cancel();
timer_.cancel();
is_completed = true;
}
void hostresolver::on_timeout(const boost::system::error_code &err)
{
if ((!err) && (err != boost::asio::error::operation_aborted))
{
is_timeout = true;
stop();
}
}
void hostresolver::finish_resolve(const boost::system::error_code& err, boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
if (!err)
{
while (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator())
{
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
if (endpoint.address().is_v4())
{
result.insert(endpoint.address().to_v4().to_ulong());
}
endpoint_iterator++;
}
}
stop();
}
File main.cpp
#include "stdafx.h"
#include "hostresolver.h"
int _tmain(int argc, _TCHAR* argv[])
{
boost::asio::io_service ios;
for (int i = 0; i < 2; i++)
{
std::cout << "iteration: " << i << std::endl;
{
hostresolver hres(&ios);
hres.start_resolve("localhost", 1);
if (hres.result.size() == 0)
std::cout << "failed" << std::endl;
}
{
hostresolver hres(&ios);
hres.start_resolve("google.usd", 1);
}
}
return 0;
}
After returning from run_once, the io_service would most likely enteres the "stopped" state. Thus, you should call ios_ptr->reset() prior to calling run_once() again. Quoting from run_once reference:
Return Value: The number of handlers that were executed. A zero return
value implies that the io_service object is stopped (the stopped()
function returns true). Subsequent calls to run(), run_one(), poll()
or poll_one() will return immediately unless there is a prior call to
reset().
Related
I've got troubles with following scenario using asio 1.66.0 Windows implementation
bind socket
run io_context
stop io_context
close socket
restart io_context
repeat 1-4
A call to io_context::run in second iteration is followed by system error 995
The I/O operation has been aborted because of either a thread exit or
an applica tion request
Looks like this error is from socket closure: asio uses PostQueuedCompletionStatus/GetQueuedCompletionStatus to signal itself that io_context::stop was called. But I/O operation, enqueued by WSARecvFrom in socket_.async_receive_from, is failed because of socket is closed and in the next call to io_context::run it is the first what I get in handler passed to socket_.async_receive_from.
Is it intended behavior of asio io_context? How do I avoid this error in sequential iterations?
If I stop io_context::run by closing the socket, all works except there will be same error and it looks little dirty.
Another odd thing is if I proceed with do_receive after error receipt, I will receive as many errors as number of previous iterations, and then I'll receive data from socket.
// based on boost_asio/example/cpp11/multicast/receiver.cpp
// https://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/example/cpp11/multicast/receiver.cpp
#include <array>
#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <future>
#include <chrono>
#include <thread>
using namespace std::chrono_literals;
constexpr short multicast_port = 30001;
class receiver
{
public:
explicit receiver(boost::asio::io_context& io_context) : socket_(io_context)
{}
~receiver()
{
close();
}
void open(
const boost::asio::ip::address& listen_address,
const boost::asio::ip::address& multicast_address)
{
// Create the socket so that multiple may be bound to the same address.
boost::asio::ip::udp::endpoint listen_endpoint(
listen_address, multicast_port);
socket_.open(listen_endpoint.protocol());
socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
socket_.bind(listen_endpoint);
// Join the multicast group.
socket_.set_option(
boost::asio::ip::multicast::join_group(multicast_address));
do_receive();
}
void close()
{
if (socket_.is_open())
{
socket_.close();
}
}
private:
void do_receive()
{
socket_.async_receive_from(
boost::asio::buffer(data_), sender_endpoint_,
[this](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
std::cout.write(data_.data(), length);
std::cout << std::endl;
do_receive();
}
else
{
// A call to io_context::run in second iteration is followed by system error 995
std::cout << ec.message() << std::endl;
}
});
}
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint sender_endpoint_;
std::array<char, 1024> data_;
};
int main(int argc, char* argv[])
{
try
{
const std::string listen_address = "0.0.0.0";
const std::string multicast_address = "239.255.0.1";
boost::asio::io_context io_context;
receiver r(io_context);
std::future<void> fut;
for (int i = 5; i > 0; --i)
{
io_context.restart();
r.open(
boost::asio::ip::make_address(listen_address),
boost::asio::ip::make_address(multicast_address));
fut = std::async(std::launch::async, [&](){ io_context.run(); });
std::this_thread::sleep_for(3s);
io_context.stop();
fut.get();
r.close();
}
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
I have next snippet:
void TcpConnection::Send(const std::vector<uint8_t>& buffer) {
std::shared_ptr<std::vector<uint8_t>> bufferCopy = std::make_shared<std::vector<uint8_t>>(buffer);
auto socket = m_socket;
m_socket->async_send(asio::buffer(bufferCopy->data(), bufferCopy->size()), [socket, bufferCopy](const boost::system::error_code& err, size_t bytesSent)
{
if (err)
{
logwarning << "clientcomms_t::sendNext encountered error: " << err.message();
// Assume that the communications path is no longer
// valid.
socket->close();
}
});
}
This code leads to memory leak. if m_socket->async_send call is commented then there is not memeory leak. I can not understand why bufferCopy is not freed after callback is dispatched. What I am doing wrong?
Windows is used.
Since you don't show any relevant code, and the code shown does not contain a strict problem, I'm going to assume from the code smells.
The smell is that you have a TcpConnection class that is not enable_shared_from_this<TcpConnection> derived. This leads me to suspect you didn't plan ahead, because there's no possible reasonable way to continue using the instance after the completion of any asynchronous operation (like the async_send).
This leads me to suspect you have a crucially simple problem, which is that your completion handler never runs. There's only one situation that could explain this, and that leads me to assume you never run() the ios_service instance
Here's the situation live:
Live On Coliru
#include <boost/asio.hpp>
namespace asio = boost::asio;
using asio::ip::tcp;
#include <iostream>
auto& logwarning = std::clog;
struct TcpConnection {
using Buffer = std::vector<uint8_t>;
void Send(Buffer const &);
TcpConnection(asio::io_service& svc) : m_socket(std::make_shared<tcp::socket>(svc)) {}
tcp::socket& socket() const { return *m_socket; }
private:
std::shared_ptr<tcp::socket> m_socket;
};
void TcpConnection::Send(Buffer const &buffer) {
auto bufferCopy = std::make_shared<Buffer>(buffer);
auto socket = m_socket;
m_socket->async_send(asio::buffer(bufferCopy->data(), bufferCopy->size()),
[socket, bufferCopy](const boost::system::error_code &err, size_t /*bytesSent*/) {
if (err) {
logwarning << "clientcomms_t::sendNext encountered error: " << err.message();
// Assume that the communications path is no longer
// valid.
socket->close();
}
});
}
int main() {
asio::io_service svc;
tcp::acceptor a(svc, tcp::v4());
a.bind({{}, 6767});
a.listen();
boost::system::error_code ec;
do {
TcpConnection conn(svc);
a.accept(conn.socket(), ec);
char const* greeting = "whale hello there!\n";
conn.Send({greeting, greeting+strlen(greeting)});
} while (!ec);
}
You'll see that any client, connection e.g. with netcat localhost 6767 will receive the greeting, after which, surprisingly the connection will stay open, instead of being closed.
You'd expect the connection to be closed by the server side either way, either because
a transmission error occurred in async_send
or because after the completion handler is run, it is destroyed and hence the captured shared-pointers are destructed. Not only would that free the copied buffer, but also would it run the destructor of socket which would close the connection.
This clearly confirms that the completion handler never runs. The fix is "easy", find a place to run the service:
int main() {
asio::io_service svc;
tcp::acceptor a(svc, tcp::v4());
a.set_option(tcp::acceptor::reuse_address());
a.bind({{}, 6767});
a.listen();
std::thread th;
{
asio::io_service::work keep(svc); // prevent service running out of work early
th = std::thread([&svc] { svc.run(); });
boost::system::error_code ec;
for (int i = 0; i < 11 && !ec; ++i) {
TcpConnection conn(svc);
a.accept(conn.socket(), ec);
char const* greeting = "whale hello there!\n";
conn.Send({greeting, greeting+strlen(greeting)});
}
}
th.join();
}
This runs 11 connections and exits leak-free.
Better:
It becomes a lot cleaner when the accept loop is also async, and the TcpConnection is properly shared as hinted above:
Live On Coliru
#include <boost/asio.hpp>
namespace asio = boost::asio;
using asio::ip::tcp;
#include <memory>
#include <thread>
#include <iostream>
auto& logwarning = std::clog;
struct TcpConnection : std::enable_shared_from_this<TcpConnection> {
using Buffer = std::vector<uint8_t>;
TcpConnection(asio::io_service& svc) : m_socket(svc) {}
void start() {
char const* greeting = "whale hello there!\n";
Send({greeting, greeting+strlen(greeting)});
}
void Send(Buffer);
private:
friend struct Server;
Buffer m_output;
tcp::socket m_socket;
};
struct Server {
Server(unsigned short port) {
_acceptor.set_option(tcp::acceptor::reuse_address());
_acceptor.bind({{}, port});
_acceptor.listen();
do_accept();
}
~Server() {
keep.reset();
_svc.post([this] { _acceptor.cancel(); });
if (th.joinable())
th.join();
}
private:
void do_accept() {
auto conn = std::make_shared<TcpConnection>(_svc);
_acceptor.async_accept(conn->m_socket, [this,conn](boost::system::error_code ec) {
if (ec)
logwarning << "accept failed: " << ec.message() << "\n";
else {
conn->start();
do_accept();
}
});
}
asio::io_service _svc;
// prevent service running out of work early:
std::unique_ptr<asio::io_service::work> keep{std::make_unique<asio::io_service::work>(_svc)};
std::thread th{[this]{_svc.run();}}; // TODO handle handler exceptions
tcp::acceptor _acceptor{_svc, tcp::v4()};
};
void TcpConnection::Send(Buffer buffer) {
m_output = std::move(buffer);
auto self = shared_from_this();
m_socket.async_send(asio::buffer(m_output),
[self](const boost::system::error_code &err, size_t /*bytesSent*/) {
if (err) {
logwarning << "clientcomms_t::sendNext encountered error: " << err.message() << "\n";
// not holding on to `self` means the socket gets closed
}
// do more with `self` which points to the TcpConnection instance...
});
}
int main() {
Server server(6868);
std::this_thread::sleep_for(std::chrono::seconds(3));
}
I'm trying to make a client class from boost TCP client example for my projects, and I've noticed that sometimes handle_connect doesn't get called when connecting to nonexistent host.
I've read similar issues here on stack, where people forgot to run io_service or called it before any tasks were posted, but I don't think that's my case, since I launch io_service.run() thread right after calling async_connect, and successfull connect, network unreachable, and some other cases I've tested work just fine.
Here is the full listing:
tcp_client.hpp
#ifndef TCP_CLIENT_HPP
#define TCP_CLIENT_HPP
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <boost/thread/thread.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <mutex>
#include <iostream>
#include <iomanip>
namespace com {
using boost::asio::ip::tcp;
using namespace std;
class client : public boost::enable_shared_from_this<client> {
private:
std::mutex mx_;
bool stopped_ = 1;
boost::asio::streambuf ibuf_;
boost::shared_ptr<boost::asio::io_service> io_service_;
boost::shared_ptr<boost::asio::ip::tcp::socket> sock_;
boost::shared_ptr<tcp::resolver::iterator> ei_;
std::vector<std::string> inbound_;
std::string host_, port_;
public:
client() {}
void connect( std::string host, std::string port ) {
if (!stopped_) stop();
host_ = host; port_ = port;
io_service_.reset(new boost::asio::io_service);
sock_.reset(new boost::asio::ip::tcp::socket(*io_service_));
ei_.reset(new tcp::resolver::iterator);
tcp::resolver r(*io_service_);
ei_ = boost::make_shared<tcp::resolver::iterator>( r.resolve(tcp::resolver::query(host_, port_)) );
stopped_ = 0;
start_connect();
boost::thread work( boost::bind(&client::work, shared_from_this()) );
return;
}
bool is_running() {
return !stopped_;
}
void stop() {
stopped_ = 1;
sock_->close();
return;
}
void send(std::string str) {
if (stopped_) return;
auto msg = boost::asio::buffer(str, str.size());
boost::asio::async_write( (*sock_), msg, boost::bind(&client::handle_write, shared_from_this(), _1) );
return;
}
std::string pull() {
std::lock_guard<std::mutex> lock(mx_);
std::string msg;
if (inbound_.size()>0) {
msg = inbound_.at(0);
inbound_.erase(inbound_.begin());
}
return msg;
}
int size() {
std::lock_guard<std::mutex> lock(mx_);
return inbound_.size();
}
void clear() {
std::lock_guard<std::mutex> lock(mx_);
inbound_.clear();
return;
}
private:
void work() {
if (stopped_) return;
std::cout<<"work in"<<std::endl;
io_service_->run();
std::cout<<"work out"<<std::endl;
return;
}
void start_connect() {
if ((*ei_) != tcp::resolver::iterator()) {
std::cout<<"Trying "<<(*ei_)->endpoint()<<std::endl;
sock_->async_connect( (*ei_)->endpoint(), boost::bind(&client::handle_connect, shared_from_this(), boost::asio::placeholders::error) );
} else {
stop();
}
return;
}
void handle_connect(const boost::system::error_code& ec) {
if (stopped_) return;
if (!sock_->is_open()) {
std::cout<<"Socket closed"<<std::endl;
(*ei_)++;
start_connect();
} else if (ec) {
std::cout<<"Connect error: "<<ec.message()<<std::endl;
sock_->close();
(*ei_)++;
start_connect();
} else {
std::cout<<"Connected to "<<(*ei_)->endpoint()<<std::endl;
start_read();
}
return;
}
void start_read() {
if (stopped_) return;
boost::asio::async_read_until((*sock_), ibuf_, "", boost::bind(&client::handle_read, shared_from_this(), boost::asio::placeholders::error));
return;
}
void handle_read(const boost::system::error_code& ec) {
std::lock_guard<std::mutex> lock(mx_);
if (stopped_) return;
if (ec) {
std::cout<<"Read error: "<<ec.message()<<std::endl;
stop();
return;
}
std::string line;
std::istream is(&ibuf_);
std::getline(is, line);
if (!line.empty() && inbound_.size()<1000) inbound_.push_back(line);
start_read();
return;
}
private:
void handle_write(const boost::system::error_code& ec) {
if (stopped_) return;
if (ec) {
std::cout<<"Write error: "<<ec.message()<<std::endl;
stop();
return;
}
return;
}
};
};
and tcp_test.cpp
#include "tcp_client.hpp"
int main(int argc, char* argv[]) {
auto tcp_client = boost::shared_ptr<com::client>(new com::client);
try {
tcp_client->connect("192.168.1.15", "50000");
boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
tcp_client->connect("192.168.1.20", "50000");
} catch (std::exception& e) {
std::cerr<<"Exception: "<<e.what()<<std::endl;
}
int cnt=0;
while (cnt<5) {
std::cout<<cnt<<std::endl;
cnt++;
tcp_client->send("<test>");
boost::this_thread::sleep_for(boost::chrono::milliseconds(500));
}
tcp_client->stop();
while (tcp_client->size()>0) std::cout<<tcp_client->pull()<<std::endl;
return 0;
}
The output I get is when connecting to loopback server:
Trying 192.168.1.15:50000
work in
work out
Trying 192.168.1.20:50000
0
work in
Connected to 192.168.1.20:50000
1
2
3
4
work out
<test>
<test>
<test>
<test>
<test>
The 192.168.1.20 works just as it should, as you see. The 192.168.1.15 doesnt'e exist, but I've expected it to throw some kind of error. Instead io_service.run() returns right away, like async_connect never posted callback task. Maybe it's related to endpoint iterator and not async_connect?
Can anyone please explain why is it happening like this?
Then I've tried to isolate the problem in this code:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <boost/thread/thread.hpp>
boost::asio::io_service io_svc;
boost::asio::ip::tcp::socket sock(io_svc);
boost::asio::ip::tcp::resolver::iterator ei;
void work() {
std::cout<<"work in"<<std::endl;
io_svc.run();
std::cout<<"work out"<<std::endl;
return;
}
void stop() {
sock.close();
return;
}
void start_connect();
void handle_connect(const boost::system::error_code& ec) {
if (!sock.is_open()) {
std::cout<<"Socket closed"<<std::endl;
ei++;
start_connect();
} else if (ec) {
std::cout<<"Connect error: "<<ec.message()<<std::endl;
sock.close();
ei++;
start_connect();
} else {
std::cout<<"Connected to "<<ei->endpoint()<<std::endl;
}
return;
}
void start_connect() {
if (ei != boost::asio::ip::tcp::resolver::iterator()) {
std::cout<<"Trying "<<ei->endpoint()<<std::endl;
sock.async_connect( ei->endpoint(), boost::bind(handle_connect, boost::asio::placeholders::error) );
} else {
stop();
}
return;
}
int main(int argc, char* argv[]) {
std::string host="192.168.1.15", port="50000";
boost::asio::ip::tcp::resolver r(io_svc);
ei = r.resolve(boost::asio::ip::tcp::resolver::query(host, port));
start_connect();
boost::thread* thr = new boost::thread(work);
boost::this_thread::sleep_for(boost::chrono::milliseconds(2000));
return 0;
}
But I've got a totally different result. When I try to connect to a nonexistent host, most of the time it's:
Trying 192.168.1.15:50000
work in
Sometimes it's:
Trying 192.168.1.15:50000
work in
Connect error: Operation canceled
Connect error: Operation canceled
And rarely it's:
Trying 192.168.1.15:50000
work in
Segmentation fault
"work out" is never printed, so I'm guessing io_service in this example is doing something, but how is this different from previous code, and why I get "operation canceled" error only sometimes?
A client running in a background thread should look something like this.
Note that I have note included things like connection timeouts. For that you'd want to have a deadline timer running in parallel with the async_connect. Then you'd have to correctly handle crossing cases (hint: cancel the deadline timer on successful connect and throw away the ensuing error from its async_wait).
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/chrono.hpp>
#include <thread>
#include <functional>
boost::asio::io_service io_svc;
struct client
: std::enable_shared_from_this<client>
{
using protocol = boost::asio::ip::tcp;
using resolver = protocol::resolver;
using socket = protocol::socket;
using error_code = boost::system::error_code;
client(boost::asio::io_service& ios)
: ios_(ios) {}
void start(std::string const& host, std::string const& service)
{
auto presolver = std::make_shared<resolver>(get_io_service());
presolver->async_resolve(protocol::resolver::query(host, service),
strand_.wrap([self = shared_from_this(), presolver](auto&& ec, auto iter)
{
self->handle_resolve(ec, presolver, iter);
}));
}
private:
void
handle_resolve(boost::system::error_code const& ec, std::shared_ptr<resolver> presolver, resolver::iterator iter)
{
if (ec) {
std::cerr << "error resolving: " << ec.message() << std::endl;
}
else {
boost::asio::async_connect(sock, iter, strand_.wrap([self = shared_from_this(),
presolver]
(auto&& ec, auto iter)
{
self->handle_connect(ec, iter);
// note - we're dropping presolver here - we don't need it any more
}));
}
}
void handle_connect(error_code const& ec, resolver::iterator iter)
{
if (ec) {
std::cerr << "failed to connect: " << ec.message() << std::endl;
}
else {
auto payload = std::make_shared<std::string>("Hello");
boost::asio::async_write(sock, boost::asio::buffer(*payload),
strand_.wrap([self = shared_from_this(),
payload] // note! capture the payload so it continues to exist during async send
(auto&& ec, auto size)
{
self->handle_send(ec, size);
}));
}
}
void handle_send(error_code const& ec, std::size_t size)
{
if (ec) {
std::cerr << "send failed after " << size << " butes : " << ec.message() << std::endl;
}
else {
// send something else?
}
}
boost::asio::io_service& get_io_service()
{
return ios_;
}
private:
boost::asio::io_service& ios_;
boost::asio::strand strand_{get_io_service()};
socket sock{get_io_service()};
};
void work()
{
std::cout << "work in" << std::endl;
io_svc.run();
std::cout << "work out" << std::endl;
return;
}
int main(int argc, char *argv[])
{
auto pclient = std::make_shared<client>(io_svc);
std::string host = "192.168.1.15", port = "50000";
pclient->start(host, port);
auto run_thread = std::thread(work);
if (run_thread.joinable())
run_thread.join();
return 0;
}
example output:
work in
<time passes>...
failed to connect: Operation timed out
work out
My server is based on boost spawn echo server.
The server runs fine on single-core machine, not even one crash for several months. Even when it takes 100% CPU it still works fine.
But I need to handle more client requests, now I use multi-core machine. To use all the CPUs I run io_service on several thread, like this:
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
#include <boost/asio/write.hpp>
#include <boost/thread/thread.hpp>
#include <iostream>
#include <memory>
#include <thread>
using namespace std;
using boost::asio::ip::tcp;
class session : public std::enable_shared_from_this<session>{
public:
explicit session(tcp::socket socket)
: socket_(std::move(socket)),
timer_(socket_.get_io_service()),
strand_(socket_.get_io_service())
{}
void go()
{
auto self(shared_from_this());
boost::asio::spawn(strand_, [this, self](boost::asio::yield_context yield)
{
try {
char data[1024] = {'3'};
for( ; ;) {
timer_.expires_from_now(std::chrono::seconds(10));
std::size_t n = socket_.async_read_some(boost::asio::buffer(data, sizeof(data)), yield);
// do something with data
// write back something
boost::asio::async_write(socket_, boost::asio::buffer(data, sizeof(data)), yield);
}
} catch(...) {
socket_.close();
timer_.cancel();
}
});
boost::asio::spawn(strand_, [this, self](boost::asio::yield_context yield)
{
while(socket_.is_open()) {
boost::system::error_code ignored_ec;
timer_.async_wait(yield[ignored_ec]);
if(timer_.expires_from_now() <= std::chrono::seconds(0))
socket_.close();
}
});
}
private:
tcp::socket socket_;
boost::asio::steady_timer timer_;
boost::asio::io_service::strand strand_;
};
int main(int argc, char* argv[]) {
try {
boost::asio::io_service io_service;
boost::asio::spawn(io_service, [&](boost::asio::yield_context yield)
{
tcp::acceptor acceptor(io_service,
#define PORT "7788"
tcp::endpoint(tcp::v4(), std::atoi(PORT)));
for( ; ;) {
boost::system::error_code ec;
tcp::socket socket(io_service);
acceptor.async_accept(socket, yield[ec]);
if(!ec)
// std::make_shared<session>(std::move(socket))->go();
io_service.post(boost::bind(&session::go, std::make_shared<session>(std::move(socket))));
}
});
// ----------- this works fine on single-core machine ------------
{
// io_service.run();
}
// ----------- this crashes (with multi core) ----------
{
auto thread_count = std::thread::hardware_concurrency(); // for multi core
boost::thread_group threads;
for(auto i = 0; i < thread_count; ++i)
threads.create_thread(boost::bind(&boost::asio::io_service::run, &io_service));
threads.join_all();
}
} catch(std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
The code works fine on single-core maching, but crashes all the time on 2-core/4-core/8-core machine. From the crash dump I don't see anything related to my code, just about something with boost::spawn and some randomly named lambda.
So I want to try this: Run io_service per CPU.
I found some demo, but it uses async function:
void server::start_accept()
{
new_connection_.reset(new connection(
io_service_pool_.get_io_service(), request_handler_));
acceptor_.async_accept(new_connection_->socket(),
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error));
}
void server::handle_accept(const boost::system::error_code& e)
{
if (!e)
{
new_connection_->start();
}
start_accept();
}
The io_service_pool_.get_io_service() randomly pickup an io_service, but my code uses spawn
boost::asio::spawn(io_service, ...
How to spawn with random io_service?
Seems I was asking the wrong question, spawn cannot work with multiple io_service, but the socket can. I modified the code to this:
int main(int argc, char* argv[]) {
try {
boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);
auto core_count = std::thread::hardware_concurrency();
// io_service_pool.hpp and io_service_pool.cpp from boost's example
io_service_pool pool(core_count);
boost::asio::spawn(io_service, [&](boost::asio::yield_context yield)
{
#define PORT "7788"
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), std::atoi(PORT)));
for( ; ;) {
boost::system::error_code ec;
boost::asio::io_service& ios = pool.get_io_service();
tcp::socket socket(ios);
acceptor.async_accept(socket, yield[ec]);
if(!ec)
ios.post(boost::bind(&session::go, std::make_shared<session>(std::move(socket))));
}
});
{ // run all io_service
thread t([&] { pool.run(); });
t.detach();
io_service.run();
}
} catch(std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
Now the server doesn't crash anymore. But I still have no idea what could cause the crash if I use a single io_service for all threads.
I'm trying to do an async connection establishment using 3 way handshake. At the bottom of the code there is a main method that takes in an argument to determine whether it's acting as client or server.
If it's acting as server it creates a socket and is supposed to wait till it receives data then call the callback function multiplex to figure out how to handle the data it received.
If it's acting as client I will also create a socket that is asynchronously waiting to receive data but it will also send an syn packet via udp to the server using the create_connection method.
In the constructor for Socket I execute the method start_receive which should call async_receive_from on the udp socket. The problem is that it immediately called the callback function multiplex using an endpoint for 0.0.0.0:0 instead of just waiting to receive data.
#include <iostream>
#include <string>
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/cstdint.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <unordered_map>
#include "packed_message.h"
#include "segment.pb.h"
#include "rtp.hpp"
#define DEBUG true
#define BUFFER_SIZE 10000
using boost::asio::ip::udp;
typedef boost::shared_ptr<rtp::Segment> SegmentPtr;
typedef std::vector<uint8_t> data_buffer;
// constructor for socket
rtp::Socket::Socket(boost::asio::io_service& io_service_, std::string source_ip, std::string source_port):
io_service_(io_service_),
socket_(io_service_, udp::endpoint(boost::asio::ip::address::from_string(source_ip), std::stoi(source_port))),
source_port(source_port),
source_ip(source_ip)
{
// accept incoming rtp segments
std::cout << rtp::get_endpoint_str(socket_.local_endpoint()) << std::endl;
start_receive();
}
/**
* Accept incoming rtp segments
*/
void rtp::Socket::start_receive()
{
data_buffer tmp_buf;
socket_.async_receive_from(boost::asio::buffer(tmp_buf), remote_endpoint_,
boost::bind(&rtp::Socket::multiplex, this,
tmp_buf,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void rtp::Socket::multiplex(data_buffer& tmp_buf,
const boost::system::error_code& error,
std::size_t /*bytes_transferred*/)
{
std::string identifier = rtp::get_endpoint_str(remote_endpoint_);
if (connections.count(identifier) == 0 )
{
boost::shared_ptr<Connection> connection(new Connection(remote_endpoint_));
connections.insert({rtp::get_endpoint_str(remote_endpoint_), connection});
std::cout << rtp::get_endpoint_str(remote_endpoint_) << std::endl;
connection_establishment(tmp_buf, connections.at(identifier));
}
else if(!(connections.at(identifier)->is_valid())) // connection not in list of connections
{
connection_establishment(tmp_buf, connections.at(identifier));
}
boost::shared_ptr<rtp::Connection> rtp::Socket::create_connection(std::string ip, std::string port)
{
udp::resolver resolver_(io_service_);
udp::resolver::query query_(ip, port);
udp::endpoint remote_endpoint_ = *resolver_.resolve(query_);
boost::shared_ptr<Connection> connection(new Connection(remote_endpoint_));
connections.insert({rtp::get_endpoint_str(remote_endpoint_), connection});
PackedMessage<rtp::Segment> m_packed_segment(SegmentPtr(new rtp::Segment()));
boost::shared_ptr<data_buffer> message(new data_buffer);
SegmentPtr ackseg(new rtp::Segment());
ackseg->set_ack(true);
PackedMessage<rtp::Segment> initialack(ackseg);
initialack.pack(*message);
socket_.async_send_to(boost::asio::buffer(*message), remote_endpoint_,
boost::bind(&rtp::Socket::handle_send, this, message,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
return connection;
}
void rtp::Socket::connection_establishment(data_buffer& m_readbuf, boost::shared_ptr<Connection> connection)
{
int buffer_position(0);
PackedMessage<rtp::Segment> m_packed_segment(boost::shared_ptr<rtp::Segment>(new rtp::Segment()));
boost::shared_ptr<data_buffer> message(new data_buffer);
unsigned msg_len = m_packed_segment.decode_header(m_readbuf, buffer_position);
buffer_position += HEADER_SIZE;
m_packed_segment.unpack(m_readbuf, msg_len, buffer_position);
buffer_position += msg_len;
SegmentPtr synackseg = m_packed_segment.get_msg();
if (synackseg->syn() && synackseg->ack())
{
SegmentPtr ackseg(new rtp::Segment());
ackseg->set_ack(true);
PackedMessage<rtp::Segment> finalack(ackseg);
finalack.pack(*message);
connection->set_valid(true);
socket_.async_send_to(boost::asio::buffer(*message), remote_endpoint_,
boost::bind(&rtp::Socket::handle_send, this,
message,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else if (synackseg->syn() )
{
SegmentPtr synackseg(new rtp::Segment());
synackseg->set_ack(true);
synackseg->set_syn(true);
PackedMessage<rtp::Segment> synack(synackseg);
synack.pack(*message);
socket_.async_send_to(boost::asio::buffer(*message), remote_endpoint_,
boost::bind(&rtp::Socket::handle_send, this,
message,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else if (synackseg->ack())
{
connection->set_valid(true);
}
start_receive();
}
void rtp::Socket::handle_send(boost::shared_ptr<data_buffer> /*message*/,
const boost::system::error_code& /*error*/,
std::size_t /*bytes_transferred*/)
{
}
/**
* Get remote peer ip and port in string "<ip>:<port>"
*/
std::string rtp::get_endpoint_str(udp::endpoint remote_endpoint_)
{
std::string ip = remote_endpoint_.address().to_string();
std::string port = std::to_string(remote_endpoint_.port());
return ip + ":" + port;
}
rtp::Connection::Connection(udp::endpoint remote_endpoint_):
remote_endpoint_(remote_endpoint_),
dest_ip(remote_endpoint_.address().to_string()),
dest_port(std::to_string(remote_endpoint_.port())),
valid(false)
{
}
bool rtp::Connection::is_valid()
{
return valid;
}
void rtp::Connection::set_valid(bool val)
{
if(DEBUG) std::cerr << "Connection Created" << std::endl;
valid = val;
}
int main(int argc, char* argv[])
{
if (argc == 1)
{
std::cerr << "Not enough args" << std::endl;
return 1;
}
boost::asio::io_service io_service_;
if (std::string(argv[1]) == u8"server")
{
rtp::Socket socket(io_service_, u8"127.0.0.1", u8"4545");
}
else if (std::string(argv[1]) == u8"client")
{
rtp::Socket socket(io_service_, u8"127.0.0.1", u8"4546");
socket.create_connection(u8"127.0.0.1", u8"4545");
}
io_service_.run();
return 0;
}
The problem is most likely these lines:
if (std::string(argv[1]) == u8"server")
{
rtp::Socket socket(io_service_, u8"127.0.0.1", u8"4545");
}
else if (std::string(argv[1]) == u8"client")
{
rtp::Socket socket(io_service_, u8"127.0.0.1", u8"4546");
socket.create_connection(u8"127.0.0.1", u8"4545");
}
Here in each of the if statement bodies you declare a variable socket, but that is local only within the scope of the if statement body. Once that scope end the variable will be destructed and exist no more.
The behavior you see it probably related to that issue, that the objects you create in those scopes gets destructed.
You need to create a socket whose lifetime is longer than any inner scope, and will last until io_service_.run() returns.