I have written a basic client-server application in C++ using asio library. The client sends messages from the console to the server.
If I run it on localhost on either linux or windows, it works great. However, when I run it on my actual server, I get a strange behavior. Each time I send a message, then immediately after another message is sent that contains garbage or is empty. This sometimes happens, sometimes doesn't. But it does most of the times. I tried using a different port.
For example if I send messages 1, 2, and 3 this is what I see in the server's console:
What could I be doing wrong ?
server.cpp - Almost same code as seen here
#define ASIO_STANDALONE
#include <iostream>
#include <asio.hpp>
using asio::ip::tcp;
const std::size_t max_length = 2048;
const unsigned short PORT = 15562;
class Session
: public std::enable_shared_from_this<Session>
{
public:
Session(tcp::socket server_socket)
: _session_socket(std::move(server_socket))
{
}
void start()
{
do_read();
}
private:
void do_read()
{
auto self(shared_from_this()); // shared_ptr instance to this
// Start an asynchronous read.
// This function is used to asynchronously read data from the stream socket.
_session_socket.async_read_some(asio::buffer(_data, max_length),
[this, self](std::error_code error, std::size_t length)
{
if (!error)
{
std::cout << "Data RECEIVED: " << std::endl;
std::cout << _data << std::endl;
do_write(length);
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this()); // shared_ptr instance to this
// Start an asynchronous write.
// This function is used to asynchronously write data to the stream socket.
strncpy(_data, "Hi, from the server", max_length);
asio::async_write(_session_socket, asio::buffer(_data, length),
[this, self](std::error_code error, std::size_t /*length*/)
{
if (!error)
{
do_read();
}
});
}
tcp::socket _session_socket;
char _data[max_length];
};
class server
{
public:
server(asio::io_service &io_service, const tcp::endpoint &endpoint)
: _server_socket(io_service),
_server_acceptor(io_service, endpoint)
{
}
void do_accept()
{
// Start an asynchronous accept.
// This function is used to asynchronously accept a new connection into a socket.
_server_acceptor.async_accept(_server_socket,
[this](std::error_code error)
{
// Accept succeeded
if (!error)
{
// Create a session
auto session = std::make_shared<Session>(
std::move(_server_socket));
session->start();
}
// Continue to accept more connections
do_accept();
});
}
private:
tcp::acceptor _server_acceptor;
tcp::socket _server_socket;
};
int main()
{
try
{
asio::io_service io_service; // io_service provides functionality for sockets, connectors, etc
tcp::endpoint endpoint(tcp::v4(), PORT); // create an endpoint using a IP='any' and the specified PORT
server server(io_service, endpoint); // create server on PORT
server.do_accept();
std::cout << "Server started on port: " << PORT << std::endl;
io_service.run();
}
catch (std::exception &e)
{
std::cerr << "Exception: " << e.what() << "\n"; // Print error
}
return 0;
}
client.cpp - Almost same code as seen here
#define ASIO_STANDALONE
#include <iostream>
#include <asio.hpp>
using asio::ip::tcp;
int main(int argc, char *argv[])
{
asio::io_service io_service;
tcp::socket socket(io_service);
tcp::resolver resolver(io_service);
// Connect
asio::connect(socket, resolver.resolve({"localhost", "15562"}));
for (int i = 0; i < 10; ++i)
{
std::cout << "Enter message to sent to server:" << std::endl;
char client_message[2048];
std::cin.getline(client_message, 2048);
// Send message to server
asio::write(socket, asio::buffer(client_message, 2048));
char server_message[2048];
// Read message from server
asio::read(socket, asio::buffer(server_message, 2048));
std::cout << "Reply is: " << std::endl;
std::cout << server_message << std::endl;
}
return 0;
}
std::cin.getline(client_message, 2048);
Gets a line of input from the user. In this case "1". This will be politely NULL terminated, but without looking you have no idea how much data was actually provided by the user.
asio::write(socket, asio::buffer(client_message, 2048))
Writes the entire 2048 bytes of client_message into the socket. So in goes '1', a NULL, and 2046 more bytes of unknown contents. All of this will be read by the server.
How this causes at least some of the OP's deviant behaviour:
Some of that 2048 bytes of data wind up in one packet. The rest winds up in another packet. The server reads the first packet and processes it. A few milliseconds later the second packet arrives. The first packet as a 1 and null in it, so cout prints 1 and discards the rest because that's what cout does with char *. The second packet has god-knows-what in it. cout will try to interpret it the way it would any other null terminated string. It will print random garbage until it finds a null, the cows come home, or the program crashes.
This needs to be fixed. Quick hack fix:
std::cin.getline(client_message, 2048);
size_t len = strlen(client_message)
asio::write(socket, asio::buffer(client_message, len+1))
Now only the user's input string and a null will be sent. Consider using std::string and std::getline instead of the char array and iostream::getline
But because many messages may be put into the same packet by the TCP stack, you need to know when a message begins and ends. You can't count on one message one packet.
Typical solutions are
read-a-byte read-a-byte read-a-byte-byte-byte until a protpcol-defined terminator is reached. Slow and painful, but sometimes the best solution. Buffering packets in a std::stringstream while waiting for a terminator that may not have arrived yet can ease this pain.
I prefer prepending the length of the message to the message in a fixed size data type. Receiver reads for a the size of the length, then reads length bytes. Say you send an unsigned 32 bit length field. Receiver reads 32 bits to get the length, then reads length bytes for the message. When sending binary numbers over a network watch out for different endian among receivers. To avoid differing endians, make sure your protocol specifies what endian to use. Industry standard is to always send in big endian, but most processors you are likely to encounter these days are little endian. You make the call.
I'm fuzzy on the specifics of asio::buffer. You want to get the length (as a uint32_t) and the message (as a std::string) into the output stream. This might be as simple as
std::getline(cin, client_message);
uint32_t len = client_message.length();
asio::write(socket, asio::buffer(len, sizeof(len)))
asio::write(socket, asio::buffer(client_message.c_str(), len+1))
There may be a better way built into asio, and the above may be total craptastic nonsense. Please consult an asio expert on how to optimize this.
The receiver reads the message something like:
uint32_t len;
asio::read(socket, asio::buffer(len, sizeof(len)));
asio::read(socket, asio::buffer(server_message, len));
std::cout << "Reply is: " << std::endl;
std::cout << server_message << std::endl;
The asynch version should be somewhat similar.
Related
I'm converting an application from using Juce asynchronous i/o to asio. The first part is to rewrite the code that receives traffic from another application on the same machine (it's a Lightroom Lua plugin that sends \n delimited messages on port 58764). Whenever I successfully connect to that port with my C++ program, I get a series of error codes, all the same:
An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.
Can someone point out my error? I can see that the socket is successfully opened. I've reduced this from my full program to a minimal example. I also tried it with connect instead of async_connect and had the same problem.
#include <iostream>
#include "asio.hpp"
asio::io_context io_context_;
asio::ip::tcp::socket socket_{io_context_};
void loop_me()
{
asio::streambuf streambuf{};
while (true) {
if (!socket_.is_open()) {
return;
}
else {
asio::async_read_until(socket_, streambuf, '\n',
[&streambuf](const asio::error_code& error_code, std::size_t bytes_transferred) {
if (error_code) {
std::cerr << "Socket error " << error_code.message() << std::endl;
return;
}
// Extract up to the first delimiter.
std::string command{buffers_begin(streambuf.data()),
buffers_begin(streambuf.data()) + bytes_transferred};
std::cout << command << std::endl;
streambuf.consume(bytes_transferred);
});
}
}
}
int main()
{
auto work_{asio::make_work_guard(io_context_)};
std::thread io_thread_;
std::thread run_thread_;
io_thread_ = std::thread([] { io_context_.run(); });
socket_.async_connect(asio::ip::tcp::endpoint(asio::ip::address_v4::loopback(), 58764),
[&run_thread_](const asio::error_code& error) {
if (!error) {
std::cout << "Socket connected in LR_IPC_In\n";
run_thread_ = std::thread(loop_me);
}
else {
std::cerr << "LR_IPC_In socket connect failed " << error.message() << std::endl;
}
});
std::this_thread::sleep_for(std::chrono::seconds(1));
socket_.close();
io_context_.stop();
if (io_thread_.joinable())
io_thread_.join();
if (run_thread_.joinable())
run_thread_.join();
}
You are trying to start an infinite number of asynchronous read operations at the same time. You shouldn't start a new asynchronous read until the previous one finished.
async_read_until returns immediately, even though the data hasn't been received yet. That's the point of "async".
I have this requirement where my app have to connect to another app via sockets and will have to maintain persistent connection for quiet long time. My app will be a TCP client and the other is a TCP server. My app will send commands and the server will respond accordingly.
The problem am facing right now is how to read the whole data from server a string and return for app which will issue the next command. Reading synchronously (with asio::read) looked like a good option up until I observed socket hanging up until I terminate the server. Looking at the documentation I found that the library is correctly working.
his function is used to read a certain number of bytes of data from a stream. The call will block until one of the following conditions is true:
1. The supplied buffers are full. That is, the bytes transferred is equal to the sum of the buffer sizes.
2. An error occurred.
The problem is I don't know correct buffer size as the response from the server varies. So If I put a too small buffer it returns fine but missing some data. If I put too big it will hang forever until server quits.
So I thought I would do the async reading. It works only once and I don't know how to make it fetch data until whole data it read.
here is the relevant async code
#define ASIO_STANDALONE 1
#include <iostream>
#include <asio.hpp>
int main()
{
asio::io_context context;
size_t reply_length;
size_t length = 1024;
std::vector<char> buffer;
//create socket
asio::ip::tcp::socket socket(context);
socket.connect(asio::ip::tcp::endpoint(asio::ip::address::from_string("127.0.0.1"), 8088));
std::string dataOut = "list --files"; //some command to write
std::error_code error;
asio::write(socket, asio::buffer(dataOut), error);
if (!error)
{
std::cout << "Receiving...!" << std::endl;
buffer.resize(length);
asio::async_read(socket, asio::buffer(buffer), [&buffer, &context](const asio::error_code &ec, std::size_t bytes_transferred) {
std::copy(buffer.begin(), buffer.end(), std::ostream_iterator<char>(std::cout, ""));
std::cout << "\nRead total of:" << bytes_transferred << "\n";
context.run();
});
}
else
{
std::cout << "send failed: " << error.message() << std::endl;
}
context.run();
}
Searching didn't help much solving my issue.
So my question is, how can I read all the data in a persistent socket with asio? Am not using boost.
You need to loop async_read calls. If you don't want your client to hang on read operation you can define the smallest possible buffer i.e. 1 byte.
Define function which takes socket, buffer and two additional parameters according to async_read's handler signature, and this function calls itself with async_read to make the loop of async_read calls - it reads until some error occures:
void onRead (
asio::ip::tcp::socket& socket,
std::array<char,1>& buf,
const system::error_code& ec,
std::size_t bytes)
{
if (ec)
{
if (ec == asio::error::eof && bytes == 1)
std::cout << buf[0];
return;
}
std::cout << buf[0];
asio::async_read(socket,asio::buffer(buf),
std::bind(onRead, std::ref(socket), std::ref(buf),
std::placeholders::_1, // error code
std::placeholders::_2)); // transferred bytes
}
and the changes in main:
std::array<char,1> buf;
asio::write(socket, asio::buffer(dataOut), error);
if (!error)
{
std::cout << "Receiving...!" << std::endl;
asio::async_read(socket, asio::buffer(buf),
std::bind(onRead, std::ref(socket), std::ref(buf),
std::placeholders::_1,
std::placeholders::_2));
context.run();
}
else
{
std::cout << "send failed: " << error.message() << std::endl;
}
(I am using Boost, so you should replace system::error_code on asio::error_code).
I started to learn the boost::asio and tried to make simple client-server application. At now I have troubles with server. Here it code:
int main(int argc, char* argv[])
{
using namespace boost::asio;
io_service service;
ip::tcp::endpoint endp(ip::tcp::v4(), 2001);
ip::tcp::acceptor acc(service, endp);
for (;;)
{
socker_ptr sock(new ip::tcp::socket(service));
acc.accept(*sock);
for (;;)
{
byte data[512];
size_t len = sock->read_some(buffer(data)); // <--- here exception at second iteration
if (len > 0)
write(*sock, buffer("ok", 2));
}
}
}
It correctly accepted the client socket, correctly read, then it write data and strarted new iteration. On second iteration throwed exception. It looks like:
And I don`t get why it happens?
I just need that server must read/write continuosly while the client present. And when the client gone the server must accept next client.
So the main question: why excpection happens and what how to aviod it?
...
UPDATE1: I found that at first iteration the error code of both read/write operation is successful. But (!) on second iteration at place where exception reised the error code is "End of file". But why?
You get the end of file condition because the remote end of the connection closed or dropped the connection.
You should be handling the system errors, or using the overloads that take a reference to boost::system::error_code. How else would you ever terminate the infinite loop?
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
int main()
{
using namespace boost::asio;
io_service service;
ip::tcp::endpoint endp(ip::tcp::v4(), 2001);
ip::tcp::acceptor acc(service, endp);
for (;;)
{
ip::tcp::socket sock(service);
acc.accept(sock);
boost::system::error_code ec;
while (!ec)
{
uint8_t data[512];
size_t len = sock.read_some(buffer(data), ec);
if (len > 0)
{
std::cout << "received " << len << " bytes\n";
write(sock, buffer("ok", 2));
}
}
std::cout << "Closed: " << ec.message() << "\n";
}
}
I am writing some binary data to a device fie like /dev/itun.
void ahaConnector::asyncWriteData(vector<uint8_t> packedMessage) {
cout<<"\n async write data packed message";
deviceStreamDescriptor.assign(device);
boost::asio::write (
deviceStreamDescriptor,
boost::asio::buffer(packedMessage)
);
readbuffer.resize(1024);
deviceStreamDescriptor.async_read_some(boost::asio::buffer(readbuffer),
boost::bind(&ahaConnector::readHeader, this,
boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()
));
io_service.run();
}
void ahaConnector::readHeader(const boost::system::error_code &ec, std::size_t bytes_transferred) {
if(!ec) {
std::cout<<"\n Bytes transfereed :"<<bytes_transferred<<" "<<readbuffer.size();
deviceStreamDescriptor.async_read_some(boost::asio::buffer(readbuffer),
boost::bind(&ahaConnector::readHeader, this,
boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()
));
Callbacks callbacks;
callbacks.OnReceivingPackedMessage();
io_service.run();
}
else {
cout<<"\n System Error Code "<<ec;
}
}
The callback function readhandler is getting executed successfully, however I am not able to transfer the control from my Callback function to another class.
Is something wrong from the design perspective. I need to handle the message received from the callback function for further logic. Should I use another thread here ?
Looking at this code you might just want to replace the read(device,...) by boost Asio's support for Posix streams:
#include <boost/asio.hpp>
#include <boost/asio/posix/stream_descriptor.hpp>
#include <boost/function.hpp>
#include <iostream>
static int device = 0;
using namespace boost;
int main() {
boost::asio::io_service io_svc;
boost::asio::posix::stream_descriptor iodevice(io_svc, device);
char buffer[1024];
function<void(system::error_code const&, size_t)> callback;
callback = [&](boost::system::error_code const& ec, size_t bytes_transferred) {
if (ec)
{
std::cout << "Error '" << ec.message() << "' during asynchronous operation\n";
}
else
{
std::cout << "Read exactly " << bytes_transferred << " bytes\n";
std::cout << "Data: '";
std::cout.write(buffer, bytes_transferred);
std::cout << "'\n";
iodevice.async_read_some(asio::buffer(buffer), callback);
}
};
iodevice.async_read_some(asio::buffer(buffer), callback);
io_svc.run();
}
See it Live On Coliru.
Sadly on Coliru it can't work because input is redirected from a non-stream. But if you run it interactively it will work and print the first 10 characters entered.
The answer depends on exactly what are the properties of the device. Check the documentation for the device driver you're trying to use. If the device supports non-blocking I/O, open the device with O_NONBLOCK, and use poll() to wait for device to be available for reading or writing.
If the device does not support non-blocking I/O, the only viable option would be to use a separate thread to read and/or write to the device, and use the background thread to construct facade that pretends and behaves like a non/blocking data source and sink.
I'm writing peer-to-peer (it shouldn't have server - it's a task) program for exchanging text messages. It's a very tiny chat. Simply messages, nothing else. It's my 1st practice with Boost::Asio, therefore I've some questions.
My chat should be peer-to-peer as I said and it should use udp protocol. I think, the best way is to use broadcast. And the first problem: how can I learn about new connections?
Another problem is in sending message: I send it on broadcast address and then it spreads to all computers in local network. Is it right?
This code sends message and receives its back. Like an echo. Is it right?
#include <iostream>
#include <boost/asio.hpp>
#include <boost/array.hpp>
int main()
{
try
{
namespace ip = boost::asio::ip;
boost::asio::io_service io_service;
ip::udp::socket socket(io_service,
ip::udp::endpoint(ip::udp::v4(), 1555));
socket.set_option(boost::asio::socket_base::broadcast(true));
ip::udp::endpoint broadcast_endpoint(ip::address_v4::broadcast(), 1555);
boost::array<char, 4> buffer1;
socket.send_to(boost::asio::buffer(buffer1), broadcast_endpoint);
ip::udp::endpoint sender_endpoint;
boost::array<char, 4> buffer2;
std::size_t bytes_transferred =
socket.receive_from(boost::asio::buffer(buffer2), sender_endpoint);
std::cout << "got " << bytes_transferred << " bytes." << std::endl;
}
catch (std::exception &e)
{
std::cerr << e.what();
}
system("PAUSE");
return 0;
}
Tested on Ubuntu 20.04.3 LTS and Boost.Asio 1.71.
Usually this kind of task is accomplished by using multicast. Broadcast creates too much load on a network.
Basing on the sender and receiver examples while combining both of them, you should open your socket on a multicast address, which represents a "chat room" and at the same time subscribe to that multicast group to receive the messages sent from other chat participants.
#include <iostream>
#include <string>
#include <boost/asio.hpp>
constexpr std::uint16_t multicast_port = 30001;
class Peer {
public:
Peer(boost::asio::io_context& io_context,
const boost::asio::ip::address& chat_room,
const std::string& nickname)
: socket_(io_context)
, multicast_endpoint_(chat_room, multicast_port)
, nickname_(nickname)
{
boost::asio::ip::udp::endpoint listen_endpoint(chat_room, multicast_port);
socket_.open(listen_endpoint.protocol());
socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
socket_.bind(listen_endpoint);
Note that we using reuse_address option, so you could test this example locally.
If you want to receive messages sent to multicast group, you have to subscribe to that multicast group:
socket_.set_option(boost::asio::ip::multicast::join_group(chat_room));
And as you asked if you want to learn about new connections (though UDP is a connectionless protocol), you can send multicast welcome message:
auto welcome_message = std::string(nickname_ + " connected to the chat\n");
socket_.async_send_to(boost::asio::buffer(welcome_message), multicast_endpoint_,
[this](const boost::system::error_code& error_code, std::size_t bytes_sent){
if (!error_code.failed()){
std::cout << "Entered chat room successfully" << std::endl;
}
});
So, for now we have to establish two loops: first one will expect local user's input, send it to the multicast group and then waits for another user input, while the other one will listen for incoming UDP datagrams on a socket, print datagram's content on every datagram received and then return back to socket listening:
void do_receive(){
socket_.async_receive_from(boost::asio::buffer(receiving_buffer_), remote_endpoint_,
[this](const boost::system::error_code& error_code, std::size_t bytes_received){
if (!error_code.failed() && bytes_received > 0){
auto received_message_string = std::string(receiving_buffer_.begin(), receiving_buffer_.begin() + bytes_received);
// We don't want to receive the messages we produce
if (received_message_string.find(name_) != 0){
std::cout.write(receiving_buffer_.data(), bytes_received);
std::cout << std::flush;
}
do_receive();
}
});
}
void do_send(){
std::string nickname = nickname_;
std::string message;
std::getline(std::cin, message);
std::string buffer = name.append(": " + message);
socket_.async_send_to(boost::asio::buffer(buffer, maximum_message_size_), multicast_endpoint_,
[this, message](const boost::system::error_code& /*error_code*/, std::size_t bytes_sent){
std::cout << "You: " << message << std::endl;
do_send();
});
}
There we also invoke the same IO function in each completion handler to achieve the loop effect still looking like recursion.
For now, all we have to do is to publish each of the function call in the separate threads because of io_context.run() invocation blocking, otherwise one of our loops will block another one, so we call io_context.run() in each thread:
int main(int argc, char* argv[])
{
boost::asio::thread_pool thread_pool(2);
if(argc != 3){
std::cerr << "Usage: ./peer <your_nickname> <multicast_address>" << std::endl;
std::exit(1);
}
boost::asio::io_context io_context;
boost::asio::ip::address chat_room(boost::asio::ip::make_address(argv[2]));
Peer peer(io_context, chat_room, argv[1]);
boost::asio::post(thread_pool, [&]{
peer.do_receive();
io_context.run();
});
boost::asio::post(thread_pool, [&]{
peer.do_send();
io_context.run();
});
thread_pool.join();
return 0;
}
Full source code is available here.