boost::asio read/write trouble - c++

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";
}
}

Related

asio c++ get two threads running data from two sources asynchronously

I am building a programme with C++ in which I want to run "two threads looking at two sources for data asynchronously". I do not know if this is possible or not. I have so far been able to read data from one source with a single thread. I want to read data from two threads asynchronously. Has anyone done something like this in the past that can point me in the right direction? I am using the asio C++ standalone version. Or is this something that is just not possible?
I will really appreciate you advice.
This is my code so far.
#include <iostream>
#define ASIO_STANDALONE
#include <asio.hpp>
#include <asio/ts/buffer.hpp>
#include <asio/ts/internet.hpp>
std::vector<char> vBuffer(2 * 1024);
void getData1(asio::ip::tcp::socket& socket1)
{
socket1.async_read_some(asio::buffer(vBuffer.data(), vBuffer.size()),
[&](std::error_code ec, std::size_t length)
{
if (!ec)
{
std::cout << "\n\nRead " << length << " bytes\n\n";
for (int i = 0; i < length; i++)
std::cout << vBuffer[i];
getData1(socket1);
}
}
);
}
//Make it read from two sources
void getData2(asio::ip::tcp::socket& socket1)
{
socket1.async_read_some(asio::buffer(vBuffer.data(), vBuffer.size()),
[&](std::error_code ec, std::size_t length)
{
if (!ec)
{
std::cout << "\n\nRead " << length << " bytes\n\n";
for (int i = 0; i < length; i++)
std::cout << vBuffer[i];
getData2(socket1);
}
}
);
}
int main() {
asio::error_code ec;
asio::io_context context1;
asio::io_context context2; //This to read data fron another source, eg another ip address and port
//Allow asio to do some fake tasks so that the context doesn't finish
asio::io_context::work idleWork(context1);
//Start the context
std::thread thrContext = std::thread([&]() {context1.run(); });
std::thread thrContext2 = std::thread([&]() {context2.run();});
//Get the address of somewhere we wish to connect to
asio::ip::tcp::endpoint endpoint(asio::ip::make_address("51.38.81.49", ec), 80); //ip exists
asio::ip::tcp::endpoint endpoint2(asio::ip::make_address("127.0.0.1", ec), 80); //ip exists
//Create a socket the, the context will deliver the implementation
asio::ip::tcp::socket socket1(context1);
asio::ip::tcp::socket socket2(context2);
//Tell the socket to connect to the first address specified
socket1.connect(endpoint, ec);
//Tell the socket to connect to the second address specified
socket2.connect(endpoint2, ec);
//Here we can check the error code to see if it was successful
if (!ec)
{
std::cout << "Connected successfully" << std::endl;
}
else
{
std::cout << "Failed to connect to address:\n" << ec.message() << std::endl;
}
//Here we check if socket is open or not with an if statement
if (socket1.is_open())
{
getData1(socket1); //Here we are calling our function as against calling it at the buttom part
std::string sRequest =
"GET /index.html HTTP/1.1\r\n"
"Host: example.com\r\n\r\n";
socket1.write_some(asio::buffer(sRequest.data(), sRequest.size()), ec);
//Stop programme from exiting prematurely
using namespace std::chrono_literals;
std::this_thread::sleep_for(2000ms);
}
system("pause");
return 0;
}

Websockets with c++ asio library weird behavior

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.

Boost.Asio - polling a named pipe

I am trying to listen for input on a named pipe. I'm using Boost.Asio's stream_descriptor and async_read under Linux. The problem is, the call to io_service::run() only blocks like I want it to until the first read. After that, it just keeps calling the handler immediately with the "End of file" error, even though I try to attach more async_reads to it. The code I have is equivalent to the following:
boost::asio::io_service io_service;
int fifo_d = open("/tmp/fifo", O_RDONLY);
boost::asio::posix::stream_descriptor fifo(io_service, fifo_d);
while (true)
{
// buffer and handler probably aren't important for the purposes of this question
boost::asio::async_read(fifo, buffer, handler);
io_service.run();
}
Only the first async_read works as I expect it to. Subsequent async_reads just return immediately. The only way I found to make it work like I want is to close and reopen the named pipe, but it seems like a hack:
boost::asio::io_service io_service;
while (true)
{
int fifo_d = open("/tmp/fifo", O_RDONLY);
boost::asio::posix::stream_descriptor fifo(io_service, fifo_d);
boost::asio::async_read(fifo, buffer, handler);
io_service.run();
close(fifo_d);
}
Can anyone tell me what am I doing wrong?
UPDATE: Here's a simple "read" version, which allowed for some code simplification, the problem remains the same:
int fifo_d = open("/tmp/fifo", O_RDONLY);
boost::asio::posix::stream_descriptor fifo(io_service, fifo_d);
while (true) {
try {
boost::asio::read(fifo, boost::asio::buffer(buffer));
}
catch (boost::system::system_error& err) {
// It loops here with "read: End of file" error
std::cout << err.what() << std::endl;
}
}
This is not how works. run() is not intended to be called in a loop. If you insist, you need to call reset() in between (as per the documentation).
Also, if you /want/ blocking behaviour, why are you using the async_* interface?
Demos
Consider using a simple iostream to read the fd:
Live On Coliru
#include <iostream>
#include <fstream>
int main() {
std::ifstream fifo("/tmp/fifo");
std::string word;
size_t lineno = 0;
while (fifo >> word) {
std::cout << "word: " << ++lineno << "\t" << word << "\n";
}
}
Or if you must attach to some fd you get from somewhere else, use file_descriptor from Boost IOstreams:
Live On Coliru
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>
#include <iostream>
#include <fcntl.h>
int main() {
namespace io = boost::iostreams;
using src = io::file_descriptor_source;
io::stream<src> fifo(src(open("./fifo", O_RDONLY), io::file_descriptor_flags::close_handle));
std::string word;
size_t number = 0;
while (fifo >> word) {
std::cout << "word: " << ++number << "\t" << word << "\n";
}
}
Both examples print the expected:
word: 1 hello
word: 2 world
As also sehe reported, that's not the way boost::asio works.
The ioservice::run() method runs in blocking mode while it has some work. When the ioservice goes out of work you have to call the reset() method before putting other work, so that's why in your first code the async_read is done only once.
A common pattern in this case would look something like:
void handler(...) {
if (!error) {
// do your work
boost::asio::async_read(fifo, buffer, handler); // <-- at the end of the handler a subsequent async_read is put to the ioservice, so it never goes out-of-work
}
}
boost::asio::io_service io_service;
int fifo_d = open("/tmp/fifo", O_RDONLY);
boost::asio::posix::stream_descriptor fifo(io_service, fifo_d);
boost::asio::async_read(fifo, buffer, handler); // <-- you call async_read only once here.
io_service.run(); //<-- this blocks till an error occurs

Boost::Asio peer-to-peer udp chat

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.

Got "Bad file descriptor" when use boost::asio and boost::thread

int func(boost::asio::ip::tcp::socket &socket)
{
boost::system::error_code ec;
socket.write_some(boost::asio::buffer("hello world!"), ec);
cout << socket.is_open() << endl;
if(ec)
{
cout << boost::system::system_error(ec).what() << endl;
}
return 0;
}
int main(int argc, char* argv[])
{
using namespace boost::asio;
io_service iosev;
ip::tcp::acceptor acceptor(iosev, ip::tcp::endpoint(ip::tcp::v4(), 1000));
while(1)
{
ip::tcp::socket socket(iosev);
acceptor.accept(socket);
boost::thread t = boost::thread(func, boost::ref(socket));
}
return 0;
}
I want one new thread handle new connection. But in function "func", the socket is not open and I got "Bad file descriptor". I read some examples in the document and web, but they are async. I think it's not necessary for my simple demand.
How can I fix the error? Any help is appreciated
Your socket is a temporary object, you pass a reffence to it but the object is going out of the scope and being destroyed before the thread process it. Use shared_ptr<socket> or keep them in a container.