Ping(ICMP) multiple destinations parallely Using Boost.asio - c++

I have modified ICMP pinging implementation (https://think-async.com/Asio/asio-1.18.0/src/examples/cpp03/icmp/ping.cpp) to ping multiple destination concurrently instead of sequentially as shown in the example. I tried with std::thread and std::async(along with futures).
But it works as expected only when all the destination are not reachable. Is it not possible to do it concurrently? I had disabled re-pinging on result/timeout in the pinger class
const char* ping(const char* destination)
{
asio::io_context io_context;
pinger p(io_context, destination);
io_context.run();
return p.get();
}
int main()
{
std::future<const char*> a1 = std::async(std::launch::async, ping, "10.2.7.196");
std::future<const char*> a2 = std::async(std::launch::async, ping, "10.2.7.19");
std::cout<<a1.get()<<std::endl;
std::cout<<a2.get()<<std::endl;
}

You wouldn't need std::async¹.
But from the little bit of code you show I can can guess² that your error is returning raw char const*. The chance is considerable that they refer to data inside pinger that - obviously - isn't valid anymore when the future is completed (pinger would be out of scope).
A typical way for this to happen is if you stored output in a std::string member and returned that from get() using .c_str().
A reason why it would "work" for unreachable targets would be if get() simply returned a string literal like return "unreachable", which would NOT have the lifetime problem described above.
Ditching The Crystal Ball
So, imagining a correct way to return results:
Live On Wandbox³
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
namespace asio = boost::asio;
#include "icmp_header.hpp"
#include "ipv4_header.hpp"
using asio::steady_timer;
using asio::ip::icmp;
namespace chrono = asio::chrono;
class pinger {
public:
pinger(asio::io_context& io_context, const char* destination)
: resolver_(io_context), socket_(io_context, icmp::v4()),
timer_(io_context), sequence_number_(0), num_replies_(0) {
destination_ = *resolver_.resolve(icmp::v4(), destination, "").begin();
start_send();
start_receive();
}
std::string get() { auto r = _output.str(); _output.str(""); return r; }
private:
void start_send() {
std::string body("\"Hello!\" from Asio ping.");
// Create an ICMP header for an echo request.
icmp_header echo_request;
echo_request.type(icmp_header::echo_request);
echo_request.code(0);
echo_request.identifier(get_identifier());
echo_request.sequence_number(++sequence_number_);
compute_checksum(echo_request, body.begin(), body.end());
// Encode the request packet.
asio::streambuf request_buffer;
std::ostream os(&request_buffer);
os << echo_request << body;
// Send the request.
time_sent_ = steady_timer::clock_type::now();
socket_.send_to(request_buffer.data(), destination_);
// Wait up to five seconds for a reply.
num_replies_ = 0;
timer_.expires_at(time_sent_ + chrono::seconds(5));
timer_.async_wait(boost::bind(&pinger::handle_timeout, this));
}
void handle_timeout() {
if (num_replies_ == 0)
_output << "Request timed out";
//// Requests must be sent no less than one second apart.
//timer_.expires_at(time_sent_ + chrono::seconds(1));
//timer_.async_wait(boost::bind(&pinger::start_send, this));
}
void start_receive() {
// Discard any data already in the buffer.
reply_buffer_.consume(reply_buffer_.size());
// Wait for a reply. We prepare the buffer to receive up to 64KB.
socket_.async_receive(reply_buffer_.prepare(65536),
boost::bind(&pinger::handle_receive, this,
boost::placeholders::_2));
}
void handle_receive(std::size_t length) {
// The actual number of bytes received is committed to the buffer so
// that we can extract it using a std::istream object.
reply_buffer_.commit(length);
// Decode the reply packet.
std::istream is(&reply_buffer_);
ipv4_header ipv4_hdr;
icmp_header icmp_hdr;
is >> ipv4_hdr >> icmp_hdr;
// We can receive all ICMP packets received by the host, so we need to
// filter out only the echo replies that match the our identifier and
// expected sequence number.
if (is && icmp_hdr.type() == icmp_header::echo_reply &&
icmp_hdr.identifier() == get_identifier() &&
icmp_hdr.sequence_number() == sequence_number_) {
// If this is the first reply, interrupt the five second timeout.
if (num_replies_++ == 0)
timer_.cancel();
// Print out some information about the reply packet.
chrono::steady_clock::time_point now = chrono::steady_clock::now();
chrono::steady_clock::duration elapsed = now - time_sent_;
_output
<< length - ipv4_hdr.header_length() << " bytes from "
<< ipv4_hdr.source_address()
<< ": icmp_seq=" << icmp_hdr.sequence_number()
<< ", ttl=" << ipv4_hdr.time_to_live() << ", time="
<< chrono::duration_cast<chrono::milliseconds>(elapsed).count();
}
//start_receive();
}
static unsigned short get_identifier() {
#if defined(ASIO_WINDOWS)
return static_cast<unsigned short>(::GetCurrentProcessId());
#else
return static_cast<unsigned short>(::getpid());
#endif
}
std::ostringstream _output;
icmp::resolver resolver_;
icmp::endpoint destination_;
icmp::socket socket_;
steady_timer timer_;
unsigned short sequence_number_;
chrono::steady_clock::time_point time_sent_;
asio::streambuf reply_buffer_;
std::size_t num_replies_;
};
std::string ping1(const char* destination) {
asio::io_context io_context;
pinger p(io_context, destination);
io_context.run();
return p.get();
}
#include <list>
#include <iostream>
int main(int argc, char** argv) {
std::list<std::future<std::string> > futures;
for (char const* arg : std::vector(argv+1, argv+argc)) {
futures.push_back(std::async(std::launch::async, ping1, arg));
}
for (auto& f : futures) {
std::cout << f.get() << std::endl;
}
}
As you can see I made the list of destinations command line parameters. Therefore, when I run it like:
sudo ./sotest 127.0.0.{1..100} |& sort | uniq -c
I get this output:
1 32 bytes from 127.0.0.12: icmp_seq=1, ttl=64, time=0
1 32 bytes from 127.0.0.16: icmp_seq=1, ttl=64, time=0
7 32 bytes from 127.0.0.44: icmp_seq=1, ttl=64, time=0
1 32 bytes from 127.0.0.77: icmp_seq=1, ttl=64, time=1
1 32 bytes from 127.0.0.82: icmp_seq=1, ttl=64, time=1
1 32 bytes from 127.0.0.9: icmp_seq=1, ttl=64, time=0
88 Request timed out
I'm not actually sure why so many time out, but the point is correct code now. This code runs and completes UBSan/ASan clean. See below for the fix discovered later, though
Now, Let's Drop The Future
The futures are likely creating a lot of overhead. As is the fact that you have an io_service per ping. Let's do it all on a single one.
#include <list>
#include <iostream>
int main(int argc, char** argv) {
asio::io_context io_context;
std::list<pinger> pingers;
for (char const* arg : std::vector(argv+1, argv+argc)) {
pingers.emplace_back(io_context, arg);
}
io_context.run();
for (auto& p : pingers) {
std::cout << p.get() << std::endl;
}
}
Note that the synchronization point here is io_context.run(), just like before, except now it runs all the pings in one go, on the main thread.
Correcting Cancellation
So, I noticed now why so many pings were misrepresented as unreachable.
The reason is because handle_receive needs to filter out ICMP replies that are not in response to our ping, so if that happens we need to continue start_receive() until we get it:
void start_receive() {
// Discard any data already in the buffer.
reply_buffer_.consume(reply_buffer_.size());
// Wait for a reply. We prepare the buffer to receive up to 64KB.
socket_.async_receive(reply_buffer_.prepare(65536),
boost::bind(&pinger::handle_receive, this,
boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
}
void handle_receive(boost::system::error_code ec, std::size_t length) {
if (ec) {
if (ec == boost::asio::error::operation_aborted) {
_output << "Request timed out";
} else {
_output << "error: " << ec.message();
}
return;
}
// The actual number of bytes received is committed to the buffer so
// that we can extract it using a std::istream object.
reply_buffer_.commit(length);
// Decode the reply packet.
std::istream is(&reply_buffer_);
ipv4_header ipv4_hdr;
icmp_header icmp_hdr;
is >> ipv4_hdr >> icmp_hdr;
// We can receive all ICMP packets received by the host, so we need to
// filter out only the echo replies that match the our identifier and
// expected sequence number.
if (is && icmp_hdr.type() == icmp_header::echo_reply &&
icmp_hdr.identifier() == get_identifier() &&
icmp_hdr.sequence_number() == sequence_number_) {
// If this is the first reply, interrupt the five second timeout.
if (num_replies_++ == 0)
timer_.cancel();
// Print out some information about the reply packet.
chrono::steady_clock::time_point now = chrono::steady_clock::now();
chrono::steady_clock::duration elapsed = now - time_sent_;
_output
<< length - ipv4_hdr.header_length() << " bytes from "
<< ipv4_hdr.source_address()
<< ": icmp_seq=" << icmp_hdr.sequence_number()
<< ", ttl=" << ipv4_hdr.time_to_live() << ", time="
<< chrono::duration_cast<chrono::milliseconds>(elapsed).count();
} else start_receive();
}
Now, handle_timeout can be simplified to:
void handle_timeout() {
if (num_replies_ == 0) {
socket_.cancel(); // _output is set in response to error_code
}
}
In fact, we might simplify to remove num_replies altogether, but I'll leave this as an exorcism for the reader
Full Demo
Live On Wandbox
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
namespace asio = boost::asio;
#include "icmp_header.hpp"
#include "ipv4_header.hpp"
using asio::steady_timer;
using asio::ip::icmp;
namespace chrono = asio::chrono;
class pinger {
public:
pinger(asio::io_context& io_context, const char* destination)
: resolver_(io_context), socket_(io_context, icmp::v4()),
timer_(io_context), sequence_number_(0), num_replies_(0) {
destination_ = *resolver_.resolve(icmp::v4(), destination, "").begin();
start_send();
start_receive();
}
std::string get() { auto r = _output.str(); _output.str(""); return r; }
private:
void start_send() {
std::string body("\"Hello!\" from Asio ping.");
// Create an ICMP header for an echo request.
icmp_header echo_request;
echo_request.type(icmp_header::echo_request);
echo_request.code(0);
echo_request.identifier(get_identifier());
echo_request.sequence_number(++sequence_number_);
compute_checksum(echo_request, body.begin(), body.end());
// Encode the request packet.
asio::streambuf request_buffer;
std::ostream os(&request_buffer);
os << echo_request << body;
// Send the request.
time_sent_ = steady_timer::clock_type::now();
socket_.send_to(request_buffer.data(), destination_);
// Wait up to five seconds for a reply.
num_replies_ = 0;
timer_.expires_at(time_sent_ + chrono::seconds(5));
timer_.async_wait(boost::bind(&pinger::handle_timeout, this));
}
void handle_timeout() {
if (num_replies_ == 0) {
socket_.cancel(); // _output is set in response to error_code
}
}
void start_receive() {
// Discard any data already in the buffer.
reply_buffer_.consume(reply_buffer_.size());
// Wait for a reply. We prepare the buffer to receive up to 64KB.
socket_.async_receive(reply_buffer_.prepare(65536),
boost::bind(&pinger::handle_receive, this,
boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
}
void handle_receive(boost::system::error_code ec, std::size_t length) {
if (ec) {
if (ec == boost::asio::error::operation_aborted) {
_output << "Request timed out";
} else {
_output << "error: " << ec.message();
}
return;
}
// The actual number of bytes received is committed to the buffer so
// that we can extract it using a std::istream object.
reply_buffer_.commit(length);
// Decode the reply packet.
std::istream is(&reply_buffer_);
ipv4_header ipv4_hdr;
icmp_header icmp_hdr;
is >> ipv4_hdr >> icmp_hdr;
// We can receive all ICMP packets received by the host, so we need to
// filter out only the echo replies that match the our identifier and
// expected sequence number.
if (is && icmp_hdr.type() == icmp_header::echo_reply &&
icmp_hdr.identifier() == get_identifier() &&
icmp_hdr.sequence_number() == sequence_number_) {
// If this is the first reply, interrupt the five second timeout.
if (num_replies_++ == 0)
timer_.cancel();
// Print out some information about the reply packet.
chrono::steady_clock::time_point now = chrono::steady_clock::now();
chrono::steady_clock::duration elapsed = now - time_sent_;
_output
<< length - ipv4_hdr.header_length() << " bytes from "
<< ipv4_hdr.source_address()
<< ": icmp_seq=" << icmp_hdr.sequence_number()
<< ", ttl=" << ipv4_hdr.time_to_live() << ", time="
<< chrono::duration_cast<chrono::milliseconds>(elapsed).count();
} else start_receive();
}
static unsigned short get_identifier() {
#if defined(ASIO_WINDOWS)
return static_cast<unsigned short>(::GetCurrentProcessId());
#else
return static_cast<unsigned short>(::getpid());
#endif
}
std::ostringstream _output;
icmp::resolver resolver_;
icmp::endpoint destination_;
icmp::socket socket_;
steady_timer timer_;
unsigned short sequence_number_;
chrono::steady_clock::time_point time_sent_;
asio::streambuf reply_buffer_;
std::size_t num_replies_;
};
#include <list>
#include <iostream>
int main(int argc, char** argv) {
asio::io_context io_context;
std::list<pinger> pingers;
for (char const* arg : std::vector(argv+1, argv+argc)) {
pingers.emplace_back(io_context, arg);
}
io_context.run();
for (auto& p : pingers) {
std::cout << p.get() << std::endl;
}
}
Now the output of e.g. time sudo ./sotest 127.0.0.{1..100} 18.0.0.1 is as expected:
32 bytes from 127.0.0.1: icmp_seq=1, ttl=64, time=8
32 bytes from 127.0.0.2: icmp_seq=1, ttl=64, time=8
32 bytes from 127.0.0.3: icmp_seq=1, ttl=64, time=8
32 bytes from 127.0.0.4: icmp_seq=1, ttl=64, time=8
...
32 bytes from 127.0.0.98: icmp_seq=1, ttl=64, time=0
32 bytes from 127.0.0.99: icmp_seq=1, ttl=64, time=0
32 bytes from 127.0.0.100: icmp_seq=1, ttl=64, time=0
Request timed out
¹ in fact that is rarely/never the right tool
² using my crystal ball
³ obviously we have no permissions to craft ICMP packets, let alone send them on Wandbox

Related

Boost Beast Read Conent By Portions

I am trying to understand how can I limit the amount of data that is read from the internet by calling 'read_some' function in boost beast.
The starting point is the incremental read example in the beast's docs.
From the docs I understood that the really read data is stored in the flat_buffer.
I make the following experiment:
Set max flat_buffer's size to 1024
Connect to a relatively large (several KB) html page
Call read_some one time
Turn the internet off
Try to read the page to the end
Since buffer's capacity is not large enough to store the entire page, my experiment should fail - I should not be able to read the entire page. Nevertheless, it finishes successfully. That means that there exists some additional buffer where the read data is stored. But what is it made for and how can I limit its size?
UPD
Here is my source code:
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/strand.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
using namespace http;
template<
bool isRequest,
class SyncReadStream,
class DynamicBuffer>
void
read_and_print_body(
std::ostream& os,
SyncReadStream& stream,
DynamicBuffer& buffer,
boost::beast::error_code& ec ) {
parser<isRequest, buffer_body> p;
read_header( stream, buffer, p, ec );
if ( ec )
return;
while ( !p.is_done()) {
char buf[512];
p.get().body().data = buf;
p.get().body().size = sizeof( buf );
read_some( stream, buffer, p, ec );
if ( ec == error::need_buffer )
ec = {};
if ( ec )
return;
os.write( buf, sizeof( buf ) - p.get().body().size );
}
}
int main(int argc, char** argv)
{
try
{
// Check command line arguments.
if(argc != 4 && argc != 5)
{
std::cerr <<
"Usage: http-client-sync <host> <port> <target> [<HTTP version: 1.0 or 1.1(default)>]\n" <<
"Example:\n" <<
" http-client-sync www.example.com 80 /\n" <<
" http-client-sync www.example.com 80 / 1.0\n";
return EXIT_FAILURE;
}
auto const host = argv[1];
auto const port = argv[2];
auto const target = argv[3];
int version = argc == 5 && !std::strcmp("1.0", argv[4]) ? 10 : 11;
// The io_context is required for all I/O
net::io_context ioc;
// These objects perform our I/O
boost::asio::ip::tcp::resolver resolver(ioc);
beast::tcp_stream stream(ioc);
// Look up the domain name
auto const results = resolver.resolve(host, port);
// Make the connection on the IP address we get from a lookup
stream.connect(results);
// Set up an HTTP GET request message
http::request<http::string_body> req{http::verb::get, target, version};
req.set(http::field::host, host);
req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
// Send the HTTP request to the remote host
http::write(stream, req);
// This buffer is used for reading and must be persisted
beast::flat_buffer buffer;
boost::beast::error_code ec;
read_and_print_body<false>(std::cout, stream, buffer, ec);
}
catch(std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
The operating system's TCP IP stack obviously needs to buffer data, so that's likely where it gets buffered.
The way to test your desired scenario:
Live On Coliru
#include <boost/beast.hpp>
#include <iostream>
#include <thread>
namespace net = boost::asio;
namespace beast = boost::beast;
namespace http = beast::http;
using net::ip::tcp;
void server()
{
net::io_context ioc;
tcp::acceptor acc{ioc, {{}, 8989}};
acc.listen();
auto conn = acc.accept();
http::request<http::string_body> msg(
http::verb::get, "/", 11, std::string(20ull << 10, '*'));
msg.prepare_payload();
http::request_serializer<http::string_body> ser(msg);
size_t hbytes = write_header(conn, ser);
// size_t bbytes = write_some(conn, ser);
size_t bbytes = write(conn, net::buffer(msg.body(), 1024));
std::cout << "sent " << hbytes << " header and " << bbytes << "/"
<< msg.body().length() << " of body" << std::endl;
// closes connection
}
namespace {
template<bool isRequest, class SyncReadStream, class DynamicBuffer>
auto
read_and_print_body(
std::ostream& /*os*/,
SyncReadStream& stream,
DynamicBuffer& buffer,
boost::beast::error_code& ec)
{
struct { size_t hbytes = 0, bbytes = 0; } ret;
http::parser<isRequest, http::buffer_body> p;
//p.header_limit(8192);
//p.body_limit(1024);
ret.hbytes = read_header(stream, buffer, p, ec);
if(ec)
return ret;
while(! p.is_done())
{
char buf[512];
p.get().body().data = buf;
p.get().body().size = sizeof(buf);
ret.bbytes += http::read_some(stream, buffer, p, ec);
if(ec == http::error::need_buffer)
ec = {};
if(ec)
break;
//os.write(buf, sizeof(buf) - p.get().body().size);
}
return ret;
}
}
void client()
{
net::io_context ioc;
tcp::socket conn{ioc};
conn.connect({{}, 8989});
beast::error_code ec;
beast::flat_buffer buf;
auto [hbytes, bbytes] = read_and_print_body<true>(std::cout, conn, buf, ec);
std::cout << "received hbytes:" << hbytes << " bbytes:" << bbytes
<< " (" << ec.message() << ")" << std::endl;
}
int main()
{
std::jthread s(server);
std::this_thread::sleep_for(std::chrono::seconds(1));
std::jthread c(client);
}
Prints
sent 41 header and 1024/20480 of body
received 1065 bytes of message (partial message)
Side Notes
You start your question with:
I am trying to understand how can I limit the amount of data that is read from the internet
That's built in to Beast
by calling 'read_some' function in boost beast.
To just limit the total amount of data read, you don't have to use read_some in a loop (http::read by definition already does exactly that).
E.g. with the above example, if you replace 20ull<<10 (20 KiB) with 20ull<<20 (20 MiB) you will exceed the default size limit:
http::request<http::string_body> msg(http::verb::get, "/", 11,
std::string(20ull << 20, '*'));
Prints Live On Coliru
sent 44 header and 1024/20971520 of body
received hbytes:44 bbytes:0 (body limit exceeded)
You can also set your own parser limits:
http::parser<isRequest, http::buffer_body> p;
p.header_limit(8192);
p.body_limit(1024);
Which prints Live On Coliru:
sent 41 header and 1024/20480 of body
received hbytes:41 bbytes:0 (body limit exceeded)
As you can see it even knows to reject the request after just reading the headers, using the content-length information from the headers.

Boost Asio and Udp Poll() No incoming data

I have to handle information from 100 ports in parallel for 100ms per second.
I am using Ubuntu OS.
I did some research and i saw that poll() function is a good candidate, to avoid to open 100 threads to handle in parallel data coming on udp protocol.
I did main part with boost and I tried to integrate poll() with boost.
The problem is when i am trying to send by client data to the server, I receive nothing.
According to wireshark, data are coming on the right host. (localhost, port 1234)
Did I miss something or did I put something wrong ?
The test code (server) :
#include <deque>
#include <iostream>
#include <chrono>
#include <thread>
#include <sys/poll.h>
#include <boost/optional.hpp>
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
using boost::asio::ip::udp;
using namespace boost::asio;
using namespace std::chrono_literals;
std::string ip_address = "127.0.0.1";
template<typename T, size_t N>
size_t arraySize( T(&)[N] )
{
return(N);
}
class UdpReceiver
{
using Resolver = udp::resolver;
using Sockets = std::deque<udp::socket>;
using EndPoint = udp::endpoint;
using Buffer = std::array<char, 100>; // receiver buffer
public:
explicit UdpReceiver()
: work_(std::ref(resolver_context)), thread_( [this]{ resolver_context.run(); })
{ }
~UdpReceiver()
{
work_ = boost::none; // using work to keep run active always !
thread_.join();
}
void async_resolve(udp::resolver::query const& query_) {
resolver_context.post([this, query_] { do_resolve(query_); });
}
// callback for event-loop in main thread
void run_handler(int fd_idx) {
// start reading
auto result = read(fd_idx, receive_buf.data(), sizeof(Buffer));
// increment number of received packets
received_packets = received_packets + 1;
std::cout << "Received bytes " << result << " current recorded packets " << received_packets <<'\n';
// run handler posted from resolver threads
handler_context.poll();
handler_context.reset();
}
static void handle_receive(boost::system::error_code error, udp::resolver::iterator const& iterator) {
std::cout << "handle_resolve:\n"
" " << error.message() << "\n";
if (!error)
std::cout << " " << iterator->endpoint() << "\n";
}
// get current file descriptor
int fd(size_t idx)
{
return sockets[idx].native_handle();
}
private:
void do_resolve(boost::asio::ip::udp::resolver::query const& query_) {
boost::system::error_code error;
Resolver resolver(resolver_context);
Resolver::iterator result = resolver.resolve(query_, error);
sockets.emplace_back(udp::socket(resolver_context, result->endpoint()));
// post handler callback to service running in main thread
resolver_context.post(boost::bind(&UdpReceiver::handle_receive, error, result));
}
private:
Sockets sockets;
size_t received_packets = 0;
EndPoint remote_receiver;
Buffer receive_buf {};
io_context resolver_context;
io_context handler_context;
boost::optional<boost::asio::io_context::work> work_;
std::thread thread_;
};
int main (int argc, char** argv)
{
UdpReceiver udpReceiver;
udpReceiver.async_resolve(udp::resolver::query(ip_address, std::to_string(1234)));
//logic
pollfd fds[2] { };
for(int i = 0; i < arraySize(fds); ++i)
{
fds[i].fd = udpReceiver.fd(0);
fds[i].events = 0;
fds[i].events |= POLLIN;
fcntl(fds[i].fd, F_SETFL, O_NONBLOCK);
}
// simple event-loop
while (true) {
if (poll(fds, arraySize(fds), -1)) // waiting for wakeup call. Timeout - inf
{
for(auto &fd : fds)
{
if(fd.revents & POLLIN) // checking if we have something to read
{
fd.revents = 0; // reset kernel message
udpReceiver.run_handler(fd.fd); // call resolve handler. Do read !
}
}
}
}
return 0;
}
This looks like a confused mix of C style poll code and Asio code. The point is
you don't need poll (Asio does it internally (or epoll/select/kqueue/IOCP - whatever is available)
UDP is connectionless, so you don't need more than one socket to receive all "connections" (senders)
I'd replace it all with a single udp::socket on a single thread. You don't even have to manage the thread/work:
net::thread_pool io(1); // single threaded
udp::socket s{io, {{}, 1234}};
Let's run an asynchronous receive loop for 5s:
std::array<char, 100> receive_buffer;
udp::endpoint sender;
std::function<void(error_code, size_t)> read_loop;
read_loop = [&](error_code ec, size_t bytes) {
if (bytes != size_t(-1)) {
//std::cout << "read_loop (" << ec.message() << ")\n";
if (ec)
return;
received_packets += 1;
unique_senders.insert(sender);
//std::cout << "Received:" << bytes << " sender:" << sender << " recorded:" << received_packets << "\n";
//std::cout << std::string_view(receive_buffer.data(), bytes) << "\n";
}
s.async_receive_from(net::buffer(receive_buffer), sender, read_loop);
};
read_loop(error_code{}, -1); // prime the async pump
// after 5s stop
std::this_thread::sleep_for(5s);
post(io, [&s] { s.cancel(); });
io.join();
At the end, we can report the statistics:
std::cout << "A total of " << received_packets << " were received from "
<< unique_senders.size() << " unique senders\n";
With a similated load in bash:
function client() { while read a; do echo "$a" > /dev/udp/localhost/1234 ; done < /etc/dictionaries-common/words; }
for a in {1..20}; do client& done; time wait
We get:
A total of 294808 were received from 28215 unique senders
real 0m5,007s
user 0m0,801s
sys 0m0,830s
This is obviously not optimized, the bottle neck here is likely the many many bash subshells being launched for the clients.
Full Listing
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
#include <iostream>
#include <set>
namespace net = boost::asio;
using boost::asio::ip::udp;
using boost::system::error_code;
using namespace std::chrono_literals;
int main ()
{
net::thread_pool io(1); // single threaded
udp::socket s{io, {{}, 1234}};
std::set<udp::endpoint> unique_senders;
size_t received_packets = 0;
{
std::array<char, 100> receive_buffer;
udp::endpoint sender;
std::function<void(error_code, size_t)> read_loop;
read_loop = [&](error_code ec, size_t bytes) {
if (bytes != size_t(-1)) {
//std::cout << "read_loop (" << ec.message() << ")\n";
if (ec)
return;
received_packets += 1;
unique_senders.insert(sender);
//std::cout << "Received:" << bytes << " sender:" << sender << " recorded:" << received_packets << "\n";
//std::cout << std::string_view(receive_buffer.data(), bytes) << "\n";
}
s.async_receive_from(net::buffer(receive_buffer), sender, read_loop);
};
read_loop(error_code{}, -1); // prime the async pump
// after 5s stop
std::this_thread::sleep_for(5s);
post(io, [&s] { s.cancel(); });
io.join();
}
std::cout << "A total of " << received_packets << " were received from "
<< unique_senders.size() << " unique senders\n";
}

Implement Client and Server model which communicate via TCP protocol using Boost framework

I have to try to implement the following details, can you please let me know what is wrong with the implementation and how the right one implemented.
Client and Server should be able exchange 4-byte messages via TCP protocol.Client generate random unsigned number in range [10^6, 10^8) and sends it to Server.
2)Server has a SUM resource which represents a calculation result of processed messages received from Clients.
On receiving random unsigned number in range [10^6, 10^8) from client, Server adds this number to SUM if it is an even number and subtracts otherwise.
3)Server continuously listens for new connections and is capable of handling multiple clients at the same time.
4)If an absolute value of SUM in the console did not exceed MAX UINT 32 value after the operation,
The server prints current all connections and sends an acknowledgment message with MAX UINT 32 value.
otherwise, SUM exceeds MAX UINT 32 value, closes all connections, and exits.
5)If the Client receives the acknowledgment, it starts a 50ms timer and repeats the generation/sending sequence, until Server closes the connection.
=======================client.cpp===================================
#include <iostream>
#include <boost/asio.hpp>
#include<boost/lexical_cast.hpp>
using namespace boost::asio;
using ip::tcp;
using std::string;
using std::cout;
using std::endl;
int getRandomNumber(int min, int max)
{ // static used for efficiency, so we only calculate this value once
static constexpr uint_32 fraction { 1.0 / (RAND_MAX + 1.0) };
// evenly distribute the random number across our range
return min + static_cast<int>((max - min + 1) * (std::rand() * fraction));
}
int main() {
boost::asio::io_service io_service;
//socket creation
tcp::socket socket(io_service);
//connection
socket.connect( tcp::endpoint( boost::asio::ip::address::from_string("127.0.0.9"), 4321 ));
// request/message from client
const string msg = "Hello from Client!\n";
unsigned long int number;
number = getRandomNumber(10^6, 10^8);
boost::system::error_code error;
boost::asio::write( socket, boost::asio::buffer(number), error );
if( !error ) {
cout << "Client sent hello message!" << endl;
}
else {
cout << "send failed: " << error.message() << endl;
}
// getting response from server
boost::asio::streambuf receive_buffer;
boost::asio::read(socket, receive_buffer, boost::asio::transfer_all(), error);
if( error && error != boost::asio::error::eof ) {
cout << "receive failed: " << error.message() << endl;
sleep_for_a_while(50);
}
else {
const char* data = boost::asio::buffer_cast<const char*>(receive_buffer.data());
cout << data << endl;
}
return 0;
}
=============================server.cpp===========================
unsigned long int& read_(tcp::socket & socket, unsigned long int& sum) {
boost::asio::streambuf buf;
boost::asio::read_until( socket, buf, "\n" );
unsigned long int data = boost::asio::buffer_cast<const char*>(buf.data());
if(data % 2 == 1) {
sum = sum - data;
} else {
sum = sum + data;
}
return data;
}
//send msg to client
void send_(tcp::socket & socket, const string& message) {
const string msg = message + "\n";
boost::asio::write( socket, boost::asio::buffer(message) );
}
//main to init server
int main() {
unsigned long int sum = 0;
//io_service object is needed whenever a program is using asio
boost::asio::io_service io_service;
//listen for new connection requested by the client
tcp::acceptor acceptor_(io_service, tcp::endpoint(tcp::v4(), 4321 ));// IP-ipv4 and port-1234
//socket creation
tcp::socket socket_(io_service);
//waiting for connection
acceptor_.accept(socket_);
//read operation //write operation
unsigned long int SUM = read_(socket_, &sum);
if(abs(SUM) <= 4294967295) { //2^32
cout << "SUM = " << SUM<< endl;
send_(socket_, "Acknowledgment from Server with MAX UINT 32 value-4294967295");
} else {
socket_.close();
}
return 0;
}

Something is went wrong when im sending a created Packet with boost::asio to a Minecraft Client

Goal and Instruction to the Protocol about Notchian Communication
I have a Server Application that uses boost::asio's asynchronous read/write Functions to communicate with connecting Notchian Clients. So far so good I read the Documented Website and only wrote a Status Handshake Packet. In Minecraft you can get those Packets at each Notchian Server. These Packets do use specific Data Types. My Server is just sending a String as a Json Response to the Client.
Code Section | How I wrote the ByteBuffer
typedef unsigned char byte; /* Sending unsigned bytes */
class LBuffer {
std::vector<byte> buf;
public:
std::vector<byte>& getBuf() {
return buf;
}
void write(byte data) {
buf.push_back(data);
}
void writeInt(int32_t data) {
buf.push_back(data >> 24);
buf.push_back((data << 8) >> 24);
buf.push_back((data << 16) >> 24);
buf.push_back((data << 24) >> 24);
}
void writeString(std::string data) {
std::copy(data.begin(), data.end(), std::back_inserter(buf));
}
};
Code Section | How I wrote the Packet to the Buffer
LBuffer createHandshakeStatusResponsePacket() {
LBuffer buffer;
buffer.write(0x00);
buffer.writeString("{{\"version\":{\"name\":\"1.8.7\",\"protocol\":47},\"players\":{\"max\":100,\"online\":5,\"sample\":[{\"name\":\"thinkofdeath\",\"id\":\"4566e69f-c907-48ee-8d71-d7ba5aa00d20\"}]},\"description\":{\"text\":\"Helloworld\"}}}");
return buffer;
}
Code Section | Writing Server with the ResponseBuf
int main() {
boost::asio::io_service svc;
tcp::acceptor a(svc);
a.open(tcp::v4());
a.set_option(tcp::acceptor::reuse_address(true));
a.bind({ {}, 6767 });
a.listen(5);
using session = std::shared_ptr<tcp::socket>;
std::function<void()> doAccept;
std::function<void(session)> doSession;
doSession = [&](session s) {
auto buf = std::make_shared<std::vector<byte>>(1024);
s->async_read_some(boost::asio::buffer(*buf), [&, s, buf](error_code ec, size_t bytes) {
if (ec)
std::cerr << "read failed: " << ec.message() << "\n";
else {
/*
As you see I dont read the Request from the Client..
But thats not relevant when I just want to send the Data
to receive it's Motd and so on..
*/
if (ec)
std::cerr << "endpoint failed: " << ec.message() << std::endl;
else {
std::vector<byte> responseBuf = createHandshakeStatusResponsePacket().getBuf();
async_write(*s, boost::asio::buffer(responseBuf), [&, s, buf](error_code ec, size_t) {
if (ec) std::cerr << "write failed: " << ec.message() << "\n";
});
}
doSession(s);
}
});
};
doAccept = [&] {
auto s = std::make_shared<session::element_type>(svc);
a.async_accept(*s, [&, s](error_code ec) {
if (ec)
std::cerr << "accept failed: " << ec.message() << "\n";
else {
doSession(s);
doAccept();
}
});
};
doAccept();
svc.run();
}
Results and Problems
When my Notchian Client reads the Packet that I sent as Response from the Server, it's giving me without any Delay this Result here:
Can't connect to Server
The Log from my Notchian Client said:
[04:42:54] [Client thread/ERROR]: Can't ping 127.0.0.1:6767: Internal Exception: io.netty.handler.codec.DecoderException:
java.io.IOException: Bad packet id 123
But how can it be the packetId 123 ? Because I'm sending the PacketId 0 at first.
Declaration
Notchian: Typically Software written from Notch ( so I grabbed it up )
ByteBuffer: Sending Bytes in a specific order.
I do hope for Tips and Solutions,
thanks

Async read completes, but buffer does not contain expected results

I've been following numerous tutorials online on learning Asynchronous Networking in Asio, so if I've made a really obvious mistake, there's your explanation.
Nonetheless, I've written a program that sets up both a client and server simultaneously and tries to communicate between the two. Simply connecting and making requests to send/receive data seem to be working fine, but the data itself isn't being sent.
#define ASIO_STANDALONE
#include<asio.hpp>
#include<thread>
#include<iostream>
#include<vector>
#include<array>
#include<mutex>
#include<memory>
#include<functional>
#define IPADDRESS "127.0.0.1"
#define PORT "6118"
enum side_type {
t_server, t_client
};
std::mutex m_lock;
std::array<char, 32> clientBuffer;
std::array<char, 32> serverBuffer;
bool stop(false);
void read_function(const asio::error_code&, size_t, std::shared_ptr<asio::ip::tcp::socket>, std::array<char, 32> &, side_type &);
void write_function(const asio::error_code&, size_t, std::shared_ptr<asio::ip::tcp::socket>, std::array<char, 32> &, side_type &);
void read_function(const asio::error_code& ec, size_t bytes_read, std::shared_ptr<asio::ip::tcp::socket> socket, std::array<char, 32> & buffer, side_type & type) {
if (ec) return;
using namespace std;
using namespace std::placeholders;
char value = buffer[0];
{
lock_guard<mutex> guard(m_lock);
string type_str = type == t_server ? "Server" : "Client";
cout << "Value of " << int(value) << " read by " << type_str << "." << endl;
}
if (value >= 100) stop = true;
else {
if(type == t_server)
buffer[0] = value + 1;
socket->async_write_some(asio::buffer(&buffer[0], buffer.max_size()), bind(write_function, _1, _2, socket, buffer, type));
}
}
void write_function(const asio::error_code& ec, size_t bytes_written, std::shared_ptr<asio::ip::tcp::socket> socket, std::array<char, 32> & buffer, side_type & type) {
if (ec) return;
using namespace std;
using namespace std::placeholders;
socket->async_read_some(asio::buffer(&buffer[0], buffer.max_size()), bind(read_function, _1, _2, socket, buffer, type));
}
void work_function(std::shared_ptr<asio::io_service> io_service) {
using namespace std;
asio::error_code ec;
while (!ec) {
try {
io_service->run(ec);
break;
}
catch (exception & e) {
lock_guard<mutex> guard(m_lock);
cout << "Exception thrown: \"" << e.what() << "\"." << endl;
}
}
}
void connect_function(const asio::error_code & ec, std::shared_ptr<asio::ip::tcp::socket> socket) {
using namespace std;
using namespace std::placeholders;
lock_guard<mutex> guard(m_lock);
if (ec) {
cout << "Error Connecting: " << ec << endl;
}
else {
cout << "Successful Connection!" << endl;
socket->async_read_some(asio::buffer(&clientBuffer[0], clientBuffer.max_size()), bind(read_function, _1, _2, socket, clientBuffer, t_client));
}
}
void accept_function(const asio::error_code & ec, std::shared_ptr<asio::ip::tcp::socket> socket) {
using namespace std;
using namespace std::placeholders;
lock_guard<mutex> guard(m_lock);
if (ec) {
cout << "Error Accepting: " << ec << endl;
}
else {
cout << "Successful Acception!" << endl;
serverBuffer[0] = 0;
socket->async_write_some(asio::buffer(&serverBuffer[0], serverBuffer.max_size()), bind(write_function, _1, _2, socket, serverBuffer, t_server));
}
}
int main(int argc, char** argv) {
using namespace std;
using namespace std::placeholders;
shared_ptr<asio::io_service> io_service(new asio::io_service());
shared_ptr<asio::io_service::work> work(new asio::io_service::work(*io_service));
vector<shared_ptr<thread>> threads;
int num_of_threads = thread::hardware_concurrency();
for (auto i = 0; i < thread::hardware_concurrency(); i++) {
threads.push_back(shared_ptr<thread>(new thread(work_function, io_service)));
}
using namespace asio::ip;
tcp::resolver resolver(*io_service);
tcp::resolver::query query(IPADDRESS, PORT);
tcp::resolver::iterator iterator = resolver.resolve(query);
tcp::endpoint endpoint = *iterator;
cout << "Connecting to " << endpoint << endl;
shared_ptr<tcp::acceptor> acceptor(new tcp::acceptor(*io_service));
shared_ptr<tcp::socket> acc_socket(new tcp::socket(*io_service));
shared_ptr<tcp::socket> socket(new tcp::socket(*io_service));
acceptor->open(endpoint.protocol());
acceptor->set_option(tcp::acceptor::reuse_address(false));
acceptor->bind(endpoint);
acceptor->listen(asio::socket_base::max_connections);
acceptor->async_accept(*acc_socket, bind(accept_function, _1, acc_socket));
asio::error_code ec;
socket->async_connect(endpoint, bind(connect_function, _1, socket));
//while (!stop);
cout << "Press Any Key to Continue..." << endl;
cin.get();
socket->shutdown(tcp::socket::shutdown_both, ec);
socket->close(ec);
work.reset();
while (!io_service->stopped());
for (shared_ptr<thread> & t : threads) {
t->join();
}
return 0;
}
As output, I've been getting the following:
Connecting to 127.0.0.1:6118
Press Any Key to Continue...
Successful Connection!
Successful Acception!
Value of 0 read by Client.
Value of 0 read by Server.
Value of 0 read by Client.
Value of 1 read by Server.
Value of 0 read by Client.
Value of 2 read by Server.
Value of 0 read by Client.
Value of 3 read by Server.
......
Value of 0 read by Client.
Value of 98 read by Server.
Value of 0 read by Client.
Value of 99 read by Server.
Value of 0 read by Client.
Value of 100 read by Server.
However, what I'm expecting is:
Connecting to 127.0.0.1:6118
Press Any Key to Continue...
Successful Connection!
Successful Acception!
Value of 0 read by Client.
Value of 0 read by Server.
Value of 1 read by Client.
Value of 1 read by Server.
Value of 2 read by Client.
Value of 2 read by Server.
Value of 3 read by Client.
Value of 3 read by Server.
......
Value of 98 read by Client.
Value of 98 read by Server.
Value of 99 read by Client.
Value of 99 read by Server.
Value of 100 read by Client.
Value of 100 read by Server.
Clearly what's happening is that the Server buffer is getting updated (when I manually increment the value), while the Client Buffer never gets updated by the async_read_some function. Additionally, because the client buffer never gets updated, the server is just reading in old values (also without getting updated) and thus technically has incorrect output as well. However, I don't know what's wrong. I'm passing in all my buffers the way I think I'm supposed to, and all the functions seem to be bound correctly, but the data isn't being passed. So what did I do wrong?
The problem is that a copy of the buffer is being bound to the completion handler, which is a different buffer than that which is provided to the asynchronous operations:
socket->async_read_some(asio::buffer(buffer), std::bind(..., buffer, ...));
// ^~~~~~ = reference ^~~~~~ = copy
In the above snippet, the async_read_some() operation will operate on buffer, and the completion handler will be provided a copy of buffer before the operation has made any modifications. To resolve this, use std::ref() to pass a reference to std::bind().
socket->async_read_some(asio::buffer(buffer), std::bind(..., std::ref(buffer), ...));
// ^~~~~~ = reference ^~~~~~ = reference
In this case, passing a reference will also fix a potential case where undefined behavior could have been invoked. The async_write_some() and async_read_some() operations require that ownership of the underlying buffer memory is retained by the caller, who must guarantee that it remains valid until the completion handler is called. When std::bind() was being provided a copy of the buffer, the buffer's lifetime was bound to the functor object returned from std::bind(), which may have ended before the completion handler was invoked.
void read_function(
...,
std::shared_ptr<asio::ip::tcp::socket> socket,
std::array<char, 32>& buffer,
...)
{
...
socket->async_write_some(asio::buffer(buffer), handler);
} // buffer's lifetime ends shortly after returning from this function
socket->async_read_some(
asio::buffer(buffer),
std::bind(&read_function, ..., socket, buffer, ...));
Here is an example demonstrating the fundamental problem and behavior:
#include <array>
#include <cassert>
#include <functional>
int get_data(std::array<char, 32>& data)
{
return data[0];
}
int main()
{
std::array<char, 32> data;
data[0] = 0;
auto fn_copy = std::bind(&get_data, data);
auto fn_ref = std::bind(&get_data, std::ref(data));
data[0] = 1;
assert(0 == fn_copy());
assert(1 == fn_ref());
}
Your Readhandler and WriteHander:
void read_function(const asio::error_code&, size_t, std::shared_ptr<asio::ip::tcp::socket>, std::array<char, 32> &, side_type &);
void write_function(const asio::error_code&, size_t, std::shared_ptr<asio::ip::tcp::socket>, std::array<char, 32> &, side_type &);
don't conform to the asio Read handler and Write handler requirements. I.e. just:
void read_function(const asio::error_code&, size_t);
void write_function(const asio::error_code&, size_t);
Your application needs to "own" the read and write buffers and not expect their locations to be sent back to you by the handlers. If you use clientBuffer and serverBuffer where appropriate, it should work correctly.