Simple Boost::Asio asynchronous UDP echo server - c++

I'm currently making my way through a book on C++ called "C++ Crash Course". The chapter on networking shows how to use Boost::Asio to write a simple uppercasing TCP server (synchronously or asynchronously). One of the excersises is to recreate it with UDP, which is what I'm having trouble with. Here's my implementation:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/algorithm/string/case_conv.hpp>
using namespace boost::asio;
struct UdpServer {
explicit UdpServer(ip::udp::socket socket)
: socket_(std::move(socket)) {
read();
}
private:
void read() {
socket_.async_receive_from(dynamic_buffer(message_),
remote_endpoint_,
[this](boost::system::error_code ec, std::size_t length) {
if (ec || this->message_ == "\n") return;
boost::algorithm::to_upper(message_);
this->write();
}
);
}
void write() {
socket_.async_send_to(buffer(message_),
remote_endpoint_,
[this](boost::system::error_code ec, std::size_t length) {
if (ec) return;
this->message_.clear();
this->read();
}
);
}
ip::udp::socket socket_;
ip::udp::endpoint remote_endpoint_;
std::string message_;
};
int main() {
try {
io_context io_context;
ip::udp::socket socket(io_context, ip::udp::v4(), 1895);
UdpServer server(std::move(socket));
io_context.run();
} catch (std::exception & e) {
std::cerr << e.what() << std::endl;
}
}
(Note: The original example uses enable_shared_from_this to capture this by shared_ptr into the lambdas, but I deliberately omitted it to see what would happen without it.)
My code does not compile, and I feel it will take me a thousand years to fully parse the error message (posted on pastebin.com since it's enormous).
It seems the issue is that the buffers are being used/constructed the wrong way, but I have no idea what exactly is wrong with this code. The few answers here on SO concerning Asio either use TCP or tackle an entirely different problem, so the mistake I made has to be really basic. I didn't find anything relevant in the Asio docs.
To be fair, Asio seems way too complicated to my newbie self. Maybe I just don't have the qualifications to use it right now. Nonetheless, I would still like to get the exercise done and move on. Any help would be appreciated.

Templates have the ugliest of compiler error messages. You often just have to go through the compiler error output and look for the first reference in your own source file. Ala:
/home/atmaks/Code/CCC_chapter20/main.cpp:53:9: required from here
In any case, on Visual Studio, the error was a bit more clear. (Not really, it just identified the offending line better).
Stare at it and contemplate all your life's decisions that led you to want to be developing in C++ in the first place. :)
I can't for the life of me figure out how to get dynamic_buffer to work. It may simply be the case that async_read doesn't like this type. And I think that actually makes sense for UDP. The receive buffer has to be sized before the recvfrom call in a synchronous mode. And I suspect async UDP, especially for Windows, the buffer has to be passed down to the kernel to be filled up. By then it's too late to be sized.
Asio lacks proper documentation and leaves us with cryptic template types to figure out. And the only Asio documentation that is worthwhile are the decent examples - none of which reference dynamic_buffer.
So let's change to a fixed sized buffer for receiving.
While we're at it, it didn't like your socket constructor and threw an exception. So I fixed it up such that it will work.
#include <iostream>
#include <boost/asio.hpp>
#include <boost/algorithm/string/case_conv.hpp>
using namespace boost::asio;
struct UdpServer {
explicit UdpServer(ip::udp::socket socket)
: socket_(std::move(socket)) {
read();
}
private:
void read() {
socket_.async_receive_from(buffer(data_, 1500),
remote_endpoint_,
[this](boost::system::error_code ec, std::size_t length) {
if (ec)
{
return;
}
data_[length] = '\0';
if (strcmp(data_, "\n") == 0)
{
return;
}
boost::algorithm::to_upper(data_);
this->write();
}
);
}
void write() {
socket_.async_send_to(buffer(data_, strlen(data_)),
remote_endpoint_,
[this](boost::system::error_code ec, std::size_t length) {
if (ec) return;
data_[0] = '\0';
this->read();
}
);
}
ip::udp::socket socket_;
ip::udp::endpoint remote_endpoint_;
char data_[1500 + 1]; // +1 for we can always null terminate safely
};
int main() {
try {
io_context io_context;
ip::udp::endpoint ep(ip::udp::v6(), 1895); // also listens on ipv4
ip::udp::socket sock(io_context, ep);
UdpServer server(std::move(sock));
io_context.run();
}
catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
Update
I did get dynamic_buffer to work, but it still requires a pre-allocation to be made.
Update the the start of the read() function as follows:
void read() {
auto db = dynamic_buffer(message_);
auto b = db.prepare(1500);
socket_.async_receive_from(b,
...
That at least lets you stick with std::string instead of using a flat C array.
And now for evidence that it's working:

Related

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.

Operation canceled boost asio async_receive_from

I have an UDP Server set up with boost/asio (I copied the example and just changed a few things). Below is the code:
udp_server.hpp
using boost::asio::ip::udp;
class udp_server {
public:
udp_server(boost::asio::io_service&, int);
private:
boost::array<char, 256> recBuffer;
udp::socket socket_;
udp::endpoint remote_endpoint_;
void start_receive();
void handle_receive(const boost::system::error_code&, std::size_t);
void handle_send(boost::shared_ptr<std::string> /*message*/,
const boost::system::error_code& /*error*/,
std::size_t /*bytes_transferred*/)
{}
};
and udp_server.cpp
udp_server::udp_server( boost::asio::io_service& io_service,
int port)
: socket_(io_service, udp::endpoint(udp::v4(), port)) {
serverNotifications.push_back("UDP Server class initialized.");
start_receive();
}
void udp_server::start_receive() {
socket_.async_receive_from(
boost::asio::buffer(recBuffer),
remote_endpoint_,
boost::bind(&udp_server::handle_receive,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
serverNotifications.push_back("Starting to receive UDP Messages.");
}
void udp_server::handle_receive(const boost::system::error_code& error,
std::size_t size) {
serverNotifications.push_back("RecFrom: " + remote_endpoint_.address().to_string());
if (!error) {
// I do data stuff here
} else {
errors.push_back("Handle Receive error: " + error.message());
}
}
After initializing the Server with:
try {
udp_server server(io_service, ApplData.PORT, (size_t)ApplData.BUFLEN);
} catch (std::exception& e) {
// error handling
}
and running it with io_service.run() in a seperate try catch in another function I get some problems:
My Callback function handle_receive gets called without any UDP message getting send in the whole network (aka only my laptop without connection)
error.message() returns "Operation canceled"
remote_endpoint_.address().to_string() returns "acfc:4000:0:0:7800::%2885986016" which I can't identify as something useful
Also I recognized that my io_service is stopping all the time, but in my understanding it should run all the time, right?
I already thought about referencing this in the callback function bind with a shared_from_this ptr, but since I have a real instance of the udp_server class until I leave my program I can't think of a good reason to do that.
Can someone explain thy this failure occurs, what these errors tell me about my code or what I can do to avoid them?
Nevermind, Rubberduck debugging was enough. I just read the line
but since I have a real instance of the udp_server class until I leave my program I can't think of a good reason to do that.
and noticed, that I actually didn't have this and this was the error.

Writing a simple C++ protobuf streaming client/server

I want to use protobuf to send messages back and forth between a client and server. In my case, I want to send an arbitrary number of protobuf messages from the server to the client. How can I build this quickly in C++?
Note: I wrote this question along with my answer after pooling a really useful Kenton Varda answer and Fulkerson answer on stackoverflow. Others have asked similar questions and hit similar roadblocks - see here, here, and here.
I'm new with protobuf and asio so feel free to correct/suggest improvements, or provide your own answer.
First, the C++ protobuf API lacks built-in support for sending multiple protobuf messages over a single stream/connection. The Java API has it, but it still hasn't been added to the C++ version. Kenton Varda (creator of protobuf v2) was nice enough to post the C++ version. So you need that code to get support for multiple messages on your single connection.
Then, you can create your client/server using boost::asio . Don't try to use the istream/ostream style interface asio provides; it is easier to wrap that and create the stream types (ZeroCopyInputStream/ZeroCopyOutputStream) required by protobuf, but it doesn't work. I don't completely understand why, but this answer by Fulkerson talks about the brittle nature of trying to do it. It also provides sample code to adapt the raw sockets into the types we need.
Putting all of this together along with a basic boost::asio tutorial, here are the client and server, followed by the supporting code. We are sending multiple instances of a simple protobuf class called persistence::MyMessage located in MyMessage.pb.h. Replace it with your own.
Client:
#include <boost/asio.hpp>
#include "ProtobufHelpers.h"
#include "AsioAdapting.h"
#include "MyMessage.pb.h"
using boost::asio::ip::tcp;
int main()
{
const char* hostname = "127.0.0.1";
const char* port = "27015";
boost::asio::io_service io_service;
tcp::resolver resolver(io_service);
tcp::resolver::query query(hostname, port);
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::socket socket(io_service);
boost::asio::connect(socket, endpoint_iterator);
AsioInputStream<tcp::socket> ais(socket);
CopyingInputStreamAdaptor cis_adp(&ais);
for (;;)
{
persistence::MyMessage myMessage;
google::protobuf::io::readDelimitedFrom(&cis_adp, &myMessage);
}
return 0;
}
Server:
#include <boost/asio.hpp>
#include "ProtobufHelpers.h"
#include "AsioAdapting.h"
#include "MyMessage.pb.h"
using boost::asio::ip::tcp;
int main()
{
boost::asio::io_service io_service;
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 27015));
for (;;)
{
tcp::socket socket(io_service);
acceptor.accept(socket);
AsioOutputStream<boost::asio::ip::tcp::socket> aos(socket); // Where m_Socket is a instance of boost::asio::ip::tcp::socket
CopyingOutputStreamAdaptor cos_adp(&aos);
int i = 0;
do {
++i;
persistence::MyMessage myMessage;
myMessage.set_myString("hello world");
myMessage.set_myInt(i);
google::protobuf::io::writeDelimitedTo(metricInfo, &cos_adp);
// Now we have to flush, otherwise the write to the socket won't happen until enough bytes accumulate
cos_adp.Flush();
} while (true);
}
return 0;
}
Here are the supporting files courtesy of Kenton Varda:
ProtobufHelpers.h
#pragma once
#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream.h>
#include <google/protobuf/message_lite.h>
namespace google {
namespace protobuf {
namespace io {
bool writeDelimitedTo(
const google::protobuf::MessageLite& message,
google::protobuf::io::ZeroCopyOutputStream* rawOutput);
bool readDelimitedFrom(
google::protobuf::io::ZeroCopyInputStream* rawInput,
google::protobuf::MessageLite* message);
}
}
}
and
ProtobufHelpers.cpp
#include "ProtobufHelpers.h"
namespace google {
namespace protobuf {
namespace io {
bool writeDelimitedTo(
const google::protobuf::MessageLite& message,
google::protobuf::io::ZeroCopyOutputStream* rawOutput) {
// We create a new coded stream for each message. Don't worry, this is fast.
google::protobuf::io::CodedOutputStream output(rawOutput);
// Write the size.
const int size = message.ByteSize();
output.WriteVarint32(size);
uint8_t* buffer = output.GetDirectBufferForNBytesAndAdvance(size);
if (buffer != NULL) {
// Optimization: The message fits in one buffer, so use the faster
// direct-to-array serialization path.
message.SerializeWithCachedSizesToArray(buffer);
}
else {
// Slightly-slower path when the message is multiple buffers.
message.SerializeWithCachedSizes(&output);
if (output.HadError()) return false;
}
return true;
}
bool readDelimitedFrom(
google::protobuf::io::ZeroCopyInputStream* rawInput,
google::protobuf::MessageLite* message) {
// We create a new coded stream for each message. Don't worry, this is fast,
// and it makes sure the 64MB total size limit is imposed per-message rather
// than on the whole stream. (See the CodedInputStream interface for more
// info on this limit.)
google::protobuf::io::CodedInputStream input(rawInput);
// Read the size.
uint32_t size;
if (!input.ReadVarint32(&size)) return false;
// Tell the stream not to read beyond that size.
google::protobuf::io::CodedInputStream::Limit limit =
input.PushLimit(size);
// Parse the message.
if (!message->MergeFromCodedStream(&input)) return false;
if (!input.ConsumedEntireMessage()) return false;
// Release the limit.
input.PopLimit(limit);
return true;
}
}
}
}
and courtesy of Fulkerson:
AsioAdapting.h
#pragma once
#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
using namespace google::protobuf::io;
template <typename SyncReadStream>
class AsioInputStream : public CopyingInputStream {
public:
AsioInputStream(SyncReadStream& sock);
int Read(void* buffer, int size);
private:
SyncReadStream& m_Socket;
};
template <typename SyncReadStream>
AsioInputStream<SyncReadStream>::AsioInputStream(SyncReadStream& sock) :
m_Socket(sock) {}
template <typename SyncReadStream>
int
AsioInputStream<SyncReadStream>::Read(void* buffer, int size)
{
std::size_t bytes_read;
boost::system::error_code ec;
bytes_read = m_Socket.read_some(boost::asio::buffer(buffer, size), ec);
if (!ec) {
return bytes_read;
}
else if (ec == boost::asio::error::eof) {
return 0;
}
else {
return -1;
}
}
template <typename SyncWriteStream>
class AsioOutputStream : public CopyingOutputStream {
public:
AsioOutputStream(SyncWriteStream& sock);
bool Write(const void* buffer, int size);
private:
SyncWriteStream& m_Socket;
};
template <typename SyncWriteStream>
AsioOutputStream<SyncWriteStream>::AsioOutputStream(SyncWriteStream& sock) :
m_Socket(sock) {}
template <typename SyncWriteStream>
bool
AsioOutputStream<SyncWriteStream>::Write(const void* buffer, int size)
{
boost::system::error_code ec;
m_Socket.write_some(boost::asio::buffer(buffer, size), ec);
return !ec;
}
I'd recommend using gRPC. It supports "streaming" requests in which the client and server can send multiple messages in either direction over time as part of a single logical request, which should suit your needs. With gRPC a lot of the nitty-gritty setup is taken care of for you, you have extensive documentation and tutorials to follow, TLS encryption is built-in, you have cross-language support, you can easily add new kinds of requests and parallel streams, etc.

Correct use of Boost::asio inside of a separate thread

I am writing a DLL plugin for the Orbiter space simulator, which allows for UDP communication with an external system. I've chosen boost::asio for the task, as it allows me to abstract from the low-level stuff.
The "boundary conditions" are as follows:
I can create any threads or call any API functions from my DLL
I can modify the data inside of the simulation only inside the callback passed to my DLL (each frame), due to lack of other thread safety.
Hence, I chose the following architecture for the NetworkClient class I'm using for communications:
Upon construction, it initializes the UDP socket (boost::socket+boost::io_service) and starts a thread, which calls io_service.run()
Incoming messages are put asyncronously into a queue (thread-safe via CriticalSection)
The callback processing function can pull the messages from queue and process it
However, I have run into some strange exception upon running the implementation:
boost::exception_detail::clone_impl > at memory location 0x01ABFA00.
The exception arises in io_service.run() call.
Can anyone point me, please, am I missing something? The code listings for my classes are below.
NetworkClient declaration:
class NetworkClient {
public:
NetworkClient(udp::endpoint server_endpoint);
~NetworkClient();
void Send(shared_ptr<NetworkMessage> message);
inline bool HasMessages() {return incomingMessages.HasMessages();};
inline shared_ptr<NetworkMessage> GetQueuedMessage() {return incomingMessages.GetQueuedMessage();};
private:
// Network send/receive stuff
boost::asio::io_service io_service;
udp::socket socket;
udp::endpoint server_endpoint;
udp::endpoint remote_endpoint;
boost::array<char, NetworkBufferSize> recv_buffer;
// Queue for incoming messages
NetworkMessageQueue incomingMessages;
void start_receive();
void handle_receive(const boost::system::error_code& error, std::size_t bytes_transferred);
void handle_send(boost::shared_ptr<std::string> /*message*/, const boost::system::error_code& /*error*/, std::size_t /*bytes_transferred*/) {}
void run_service();
NetworkClient(NetworkClient&); // block default copy constructor
};
Methods implementation:
NetworkClient::NetworkClient(udp::endpoint server_endpoint) : socket(io_service, udp::endpoint(udp::v4(), 28465)) {
this->server_endpoint = server_endpoint;
boost::thread* th = new boost::thread(boost::bind(&NetworkClient::run_service,this));
start_receive();
}
void NetworkClient::start_receive()
{
socket.async_receive_from(boost::asio::buffer(recv_buffer), remote_endpoint,
boost::bind(&NetworkClient::handle_receive, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)
);
}
void NetworkClient::run_service()
{
this->io_service.run();
}
There's nothing wrong with your architecture that I can see. You should catch exceptions thrown from io_service::run(), that is likely the source of your problem.
void NetworkClient::run_service()
{
while(1) {
try {
this->io_service.run();
} catch( const std::exception& e ) {
std::cerr << e.what << std::endl;
}
}
}
You'll also want to fix whatever is throwing the exception.

Is there a boost::iostreams (bidirectional) Device for a blocking boost::asio TCP connection?

I'm surveying c++ libraries for portable, blocking I/O access to the filesystem and network. It looks like boost::filesystem, boost::iostreams and boost::asio will, between the three of them, do the job.
To be clear, I'm not currently interested in the asynchronous aspects of boost::asio; I just want a portable, blocking interface to the network.
Digging in, I see boost::iostreams has a notion of Devices, each of which has an associated mode concept. The bidirectional mode specifically seems hand-tailored for streaming access to a full-duplex TCP connection. Awesome.
boost::iostreams does not seem to offer support for actually opening TCP connections (unlike the local filesystem.) That's fine, surely boost::asio will let me open the connection, appropriately model it as a bidirectional Device, and wrap it in a boost::iostreams::stream.
..except it won't? I see boost::asio::ip::tcp::iostream, which would replace the boost::iostreams::stream, but presumably not act as a Device.
I understand the tcp::iostream would act similarly, but I would still prefer to learn and code against just one interface, not two. Specifically, dealing with two error handling regimes & exception hierarchies is not very palatable.
So, the question: am I blind? Maybe an adapter between the two libraries exists, that I missed googling around. Or perhaps someone has already released such an adapter as a 3rd-party component I could drop in?
I'm not aware of a direct mapping. However, if you were interested, writing such a device is fairly straightforward. This version throws boost::system::system_error for non-EOF errors, but you could choose to do something else.
#include <iosfwd>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/iostreams/categories.hpp>
#include <boost/system/system_error.hpp>
class asio_stream_device
{
public:
typedef char char_type;
typedef boost::iostreams::bidirectional_device_tag category;
explicit asio_stream_device(boost::asio::ip::tcp::socket& sock) : socket_(sock)
{
}
std::streamsize read(char* s, std::streamsize n)
{
// Read up to n characters from the underlying data source
// into the buffer s, returning the number of characters
// read; return -1 to indicate EOF
boost::system::error_code ec;
std::size_t rval = socket_.read_some(boost::asio::buffer(s, n), ec);
if (!ec)
{
return rval;
}
else if (ec == boost::asio::error::eof)
{
return -1;
}
else
{
throw boost::system::system_error(ec,"read_some");
}
}
std::streamsize write(const char* s, std::streamsize n)
{
// Write up to n characters to the underlying
// data sink into the buffer s, returning the
// number of characters written
boost::system::error_code ec;
std::size_t rval = socket_.write_some(boost::asio::buffer(s, n), ec);
if (!ec)
{
return rval;
}
else if (ec == boost::asio::error::eof)
{
return -1;
}
else
{
throw boost::system::system_error(ec,"write_some");
}
}
private:
boost::asio::ip::tcp::socket& socket_;
};
Basically, open/connect the socket as normal, then pass it to the constructor. The example simply reads and outputs to the screen.
void test
{
namespace asio = boost::asio;
namespace io = boost::iostreams;
asio::io_service service;
asio::ip::tcp::socket socket(service);
asio::ip::tcp::endpoint remote - ...; ////
socket.connect(remote);
io::stream<asio_stream_device> str(socket);
std::string line;
while (std::getline(str, line)) {
std::cout << line << std::endl;
}
}