int main(){
boost::asio::io_context io_context;
Server server(io_context, SOCKET_ADDRESS, SOCKET_PORT);
std::thread thread_server([&]() {
server.start();
io_context.run();
});
std::thread thread_client([&]() {
Client &client = Client::create(SOCKET_ADDRESS, SOCKET_PORT);
client.start();
done = true; // <-----atomic
});
std::thread thread_stop([&]() {
while (done == false) {
std::this_thread::sleep_for(std::chrono::milliseconds(5));
}
server.stop();
});
thread_server.join();
thread_client.join();
thread_stop.join();
}
I am experimenting with boost::asio and encountered problem which I am unable to solve. When I run program(simpler example above) on Linux(compiled with gcc) everything is fine. Same when I run it on Release in VS2017CE. However when I run it on Debug(VS2017CE as well) it crash with and exception:
cannot dereference string iterator because string iterator was invalidated
It crash on _endthreadx either when exiting thread_stop or thread_server(most likely second one). Here are my questions then:
What are the differences between Release and Debug basic configurations which might affect code execution and point me where should I look.(I am aware of some but couldn't find anything connected with this particular problem.)
What mistakes were made by me which affects code execution.
I've made some classes so I will provide more code if neccessary, but code basically works so I am starting with just a piece of it.
The code shown doesn't do anything with strings. Also you don't show what io_context is being used in Client instances.
As given, everything is a giant race-condition, because none of the client work might ever get run, but you always set done = true immediately after posting the (supposedly) asynchronous operation of Client::start.
(The potentially sane interpretation here would be if Client::start() were actually completely synchronous, but that really would make the whole existence of static Client& Client::create(...) pretty strange and useless?).
Using a thread to sleep is an anti-pattern, and doubly so in asynchronous code.
cannot dereference string iterator because string iterator was invalidated
This is a clear sign that MSVC's Iterator Debugging is working. It just tells you you have a programming error.
Your error causes string iterators to be used when they're no longer valid. I can't see it, but 99% of the time this would be caused by asynchronous operations using a buffer that gets destroyed (or modified) before the async operation completes. In a nutshell:
void foo() {
std::string msg = "message";
boost::asio::async_write(_socket, boost::asio::buffer(msg), /*...*/);
}
Suggested Code
Simplifying from your code, and showing some hints:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
#include <iomanip>
using boost::asio::ip::tcp;
using boost::system::error_code;
static std::string const SOCKET_ADDRESS = "127.0.0.1";
static unsigned short const SOCKET_PORT = 6767;
bool check(error_code const& ec, char const* message) {
std::cout << message << " (" << ec.message() << ")\n";
return !ec;
}
struct Server {
boost::asio::io_context& io_;
Server(boost::asio::io_context& io, std::string host, unsigned short port) : io_(io), host_(host), port_(port) {}
void start() {
acc_.set_option(tcp::acceptor::reuse_address(true));
acc_.listen(5);
accept_loop();
}
void stop() {
io_.post([this] { // thread safety
acc_.cancel();
acc_.close();
});
}
private:
void accept_loop() {
acc_.async_accept(sock_, [this](error_code ec) {
if (check(ec, "accepted")) {
std::make_shared<Connection>(std::move(sock_))->start();
accept_loop();
}
});
}
struct Connection : std::enable_shared_from_this<Connection> {
tcp::socket sock_;
std::string buffer_;
Connection(tcp::socket&& sock) : sock_(std::move(sock)) {}
~Connection() {
error_code ec;
std::cout << "Disconnected " << sock_.remote_endpoint(ec) << "\n";
}
void start() {
auto self = shared_from_this();
async_read_until(sock_, boost::asio::dynamic_buffer(buffer_), "\n", [self,this](error_code ec, size_t bytes) {
if (check(ec, "received request")) {
std::cout << "Request: " << std::quoted(buffer_.substr(0, bytes), '\'') << "\n";
if (bytes > 0)
std::reverse(buffer_.begin(), buffer_.begin() + bytes - 1); // reverse the request for the response
async_write(sock_, boost::asio::buffer(buffer_, bytes), [self,this](error_code ec, size_t bytes) {
if (check(ec, "response sent")) {
buffer_.erase(0, bytes);
start(); // handle more requests, if any
}
});
}
});
}
};
std::string host_;
unsigned short port_;
tcp::acceptor acc_{io_, {boost::asio::ip::address_v4::from_string(host_), port_}};
tcp::socket sock_{io_};
};
struct Client {
Client(std::string host, std::string port) : host_(host), port_(port) {}
void start() {
boost::asio::io_context io;
tcp::socket s(io);
tcp::resolver r(io);
connect(s, r.resolve(host_, port_));
send_request(s, "hello world\n");
send_request(s, "bye world\n");
}
private:
void send_request(tcp::socket& s, std::string const& request) {
write(s, boost::asio::buffer(request));
boost::asio::streambuf sb;
read_until(s, sb, "\n");
std::cout << "Received server response: '" << &sb << "'\n";
}
std::string host_;
std::string port_;
};
int main(){
boost::asio::io_context io_context;
Server server(io_context, SOCKET_ADDRESS, SOCKET_PORT);
server.start();
std::thread thread_server([&]() { io_context.run(); });
{
Client client {SOCKET_ADDRESS, std::to_string(SOCKET_PORT)};
client.start();
}
{
Client client {SOCKET_ADDRESS, std::to_string(SOCKET_PORT)};
client.start();
}
server.stop();
thread_server.join();
}
Prints
accepted (Success)
received request (Success)
Request: 'hello world
'
response sent (Success)
Received server response: 'dlrow olleh
'
received request (Success)
Request: 'bye world
'
response sent (Success)
Received server response: 'dlrow eyb
'
received request (End of file)
Disconnected 127.0.0.1:49778
accepted (Success)
received request (Success)
Request: 'hello world
'
response sent (Success)
Received server response: 'dlrow olleh
'
received request (Success)
Request: 'bye world
'
response sent (Success)
Received server response: 'dlrow eyb
'
received request (End of file)
Disconnected 127.0.0.1:49780
accepted (Operation canceled)
Note There's a startup race. Depending on your luck, the first Client might try to connect before the Server has started listening. I'm assuming this is not your worst worry, and I'll leave it as an exercise for the reader.
Related
After I:
created a tcp-socket
put it in listening
received an Incoming "message" from the Client
"processed" the incoming accomplice and closed the socket...
Whether that it is possible to put this socket on listening again? Experiment shows that no - the accept() function - throws an error.
Is it possible, somehow, to "reset" a closed socket to its original state? In order not to release the previously allocated memory for this socket and not to create a new socket, which in the end will also have to be deleted.
PS:
my_socket_p = new boost::asio::ip::tcp::socket(io_context);
my_acceptor.accept(my_socket_p );
The socket you put in listening mode is typically not the socket that you close when you're done handling a request.
Asio makes this distinction more explicit than in the underlying sockets API. I discussed this before here: Design rationale behind that separate acceptor class exists in ASIO (see also e.g. What does it mean to "open" an acceptor?).
Simple example of a typical server that accepts requests and echoes them back reversed:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
int main() {
boost::asio::io_context ioc;
// listen on port 7878
tcp::acceptor acc(ioc, {{}, 7878});
acc.listen();
while (true) {
tcp::socket conn = acc.accept();
auto peer = conn.remote_endpoint();
try {
std::string request;
read_until(conn, asio::dynamic_buffer(request), "\n");
reverse(begin(request), end(request) - 1); // reverse until line-end
write(conn, asio::buffer("reversed: " + request));
conn.close();
} catch(boost::system::system_error const& se) {
std::cerr << "Error with peer " << peer << ": " << se.code().message() << std::endl;
}
}
}
With example clients:
netcat 127.0.0.1 7878 <<< "Hello world!"
netcat 127.0.0.1 7878 <<< "Bye world!"
Prints
reversed: !dlrow olleH
reversed: !dlrow eyB
Re-using?
There are other overloads of accept that take a socket by reference, and yes they can be re-used:
tcp::socket conn(ioc); // reused
while (true) {
acc.accept(conn);
auto peer = conn.remote_endpoint();
With the rest of the code un-changed: Live On Coliru
More Typical
More typically code would be asynchronous and the "connection handling" would be in some other class, e.g. Session. In such cases conn would be moved into Session. This is also explicitly documented as proper use, e.g.:
Following the move, the moved-from object is in the same state as if constructed using the basic_socket(const executor_type&) constructor.
So, you could write the code as such:
tcp::socket conn(ioc); // reused
while (true) {
acc.accept(conn);
std::make_shared<Session>(std::move(conn))->run();
}
You can see it Live On Coliru again, although I'll include the far more idiomatic version using the move-accept handler overload of async_accept instead here:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
using boost::system::error_code;
struct Session : std::enable_shared_from_this<Session> {
Session(tcp::socket s) : conn_(std::move(s)) {}
void run() { do_reverse_echo(); }
private:
void do_reverse_echo() {
async_read_until( //
conn_, asio::dynamic_buffer(buffer_), "\n",
[this, self = shared_from_this()](error_code ec, size_t) {
if (ec) {
std::cerr << "Error with peer " << peer_ << ": " << ec.message() << std::endl;
return;
}
reverse(begin(buffer_), end(buffer_) - 1); // reverse until line-end
buffer_ = "reversed: " + buffer_;
async_write(conn_, asio::buffer(buffer_), asio::detached);
});
}
tcp::socket conn_;
tcp::endpoint peer_{conn_.remote_endpoint()};
std::string buffer_;
};
struct Listener {
Listener(asio::any_io_executor ex, uint16_t port) : acc(ex, {{}, port}) {
acc.listen();
accept_loop();
}
private:
tcp::acceptor acc;
void accept_loop() {
acc.async_accept([this](error_code ec, tcp::socket conn) {
if (ec) {
std::cerr << "Stopping listener: " << ec.message() << std::endl;
return;
}
accept_loop();
std::make_shared<Session>(std::move(conn))->run();
});
}
};
int main() {
boost::asio::io_context ioc;
Listener s(ioc.get_executor(), 7878);
ioc.run();
}
Still with the same output, obviously.
I'm trying to implement a simple IPC protocol for a project that will be built using Boost ASIO. The idea is to have the communication be done through IP/TCP, with a server with the backend and a client that will be using the data received from the server to build the frontend. The whole session would go like this:
The connection is established
The client sends a 2 byte packet with some information that will be used by the server to build its response (this is stored as the struct propertiesPacket)
The server processes the data received and stores the output in a struct of variable size called processedData
The server sends a 2 byte unsigned integer that will indicate the client what size the struct it will receive has (let's say the struct is of size n bytes)
The server sends the struct data as a n byte packet
The connection is ended
I tried implementing this by myself, following the great tutorial available in Boost ASIO's documentation, as well as the examples included in the library and some repos I found on Github, but as this is my first hand working with networking and IPC, I couldn't make it work, my client returns an exception saying the connection was reset by the peer.
What I have right now is this:
// File client.cpp
int main(int argc, char *argv[])
{
try {
propertiesPacket properties;
// ...
// We set the data inside the properties struct
// ...
boost::asio::io_context io;
boost::asio::ip::tcp::socket socket(io);
boost::asio::ip::tcp::resolver resolver(io);
boost::asio::connect(socket, resolver.resolve(argv[1], argv[2]));
boost::asio::write(socket, boost::asio::buffer(&properties, sizeof(propertiesPacket)));
unsigned short responseSize {};
boost::asio::read(socket, boost::asio::buffer(&responseSize, sizeof(short)));
processedData* response = reinterpret_cast<processedData*>(malloc(responseSize));
boost::asio::read(socket, boost::asio::buffer(response, responseSize));
// ...
// The client handles the data
// ...
return 0;
} catch (std::exception &e) {
std::cerr << e.what() << std::endl;
}
}
// File server.cpp
class ServerConnection
: public std::enable_shared_from_this<ServerConnection>
{
public:
using TCPSocket = boost::asio::ip::tcp::socket;
ServerConnection::ServerConnection(TCPSocket socket)
: socket_(std::move(socket)),
properties_(nullptr),
filePacket_(nullptr),
filePacketSize_(0)
{
}
void start() { doRead(); }
private:
void doRead()
{
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(properties_, sizeof(propertiesPacket)),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec) {
processData();
doWrite(&filePacketSize_, sizeof(short));
doWrite(filePacket_, sizeof(*filePacket_));
}
});
}
void doWrite(void* data, size_t length)
{
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(data, length),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec) { doRead(); }
});
}
void processData()
{ /* Data is processed */ }
TCPSocket socket_;
propertiesPacket* properties_;
processedData* filePacket_;
short filePacketSize_;
};
class Server
{
public:
using IOContext = boost::asio::io_context;
using TCPSocket = boost::asio::ip::tcp::socket;
using TCPAcceptor = boost::asio::ip::tcp::acceptor;
Server::Server(IOContext& io, short port)
: socket_(io),
acceptor_(io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
{
doAccept();
}
private:
void doAccept()
{
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec)
{
if (!ec) {
std::make_shared<ServerConnection>(std::move(socket_))->start();
}
doAccept();
});
}
TCPSocket socket_;
TCPAcceptor acceptor_;
};
What did I do wrong? My guess is that inside the doRead function, calling multiple times the doWrite function, when that function then also calls doRead is in part what's causing problems, but I don't know what the correct way of writing data asynchronously multiple times is. But I'm also sure that isn't the only part of my code that isn't behaving as I think it should.
Besides the problems with the code shown that I mentioned in the comments, there is indeed the problem that you suspected:
My guess is that inside the doRead function, calling multiple times the doWrite function, when that function then also calls doRead is in part what's causing problems
The fact that "doRead" is in the same function isn't necessarily a problem (that's just full-duplex socket IO). However "calling multiple times" is. See the docs:
This operation is implemented in terms of zero or more calls to the stream's async_write_some function, and is known as a composed operation. The program must ensure that the stream performs no other write operations (such as async_write, the stream's async_write_some function, or any other composed operations that perform writes) until this operation completes.
The usual way is to put the whole message in a single buffer, but if that would be "expensive" to copy, you can use a BufferSequence, which is known as scatter/gather buffers.
Specifically, you would replace
doWrite(&filePacketSize_, sizeof(short));
doWrite(filePacket_, sizeof(*filePacket_));
with something like
std::vector<boost::asio::const_buffer> msg{
boost::asio::buffer(&filePacketSize_, sizeof(short)),
boost::asio::buffer(filePacket_, sizeof(*filePacket_)),
};
doWrite(msg);
Note that this assumes that filePacketSize and filePacket have been assigned proper values!
You could of course modify do_write to accept the buffer sequence:
template <typename Buffers> void doWrite(Buffers msg)
{
auto self(shared_from_this());
boost::asio::async_write(
socket_, msg,
[this, self](boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
doRead();
}
});
}
But in your case I'd simplify by inlining the body (now that you don't call it more than once anyway).
SIDE NOTES
Don't use new or delete. NEVER use malloc in C++. Never use reinterpret_cast<> (except in the very rarest of exceptions that the standard allows!). Instead of
processedData* response = reinterpret_cast<processedData*>(malloc(responseSize));
Just use
processedData response;
(optionally add {} for value-initialization of aggregates). If you need variable-length messages, consider to put a vector or a array<char, MAXLEN> inside the message. Of course, array is fixed length but it preserves POD-ness, so it might be easier to work with. If you use vector, you'd want a scatter/gather read into a buffer sequence like I showed above for the write side.
Instead of reinterpreting between inconsistent short and unsigned short types, perhaps just spell the type with the standard sizes: std::uint16_t everywhere.
Keep in mind that you are not taking into account byte order so your protocol will NOT be portable across compilers/architectures.
Provisional Fixes
This is the listing I ended up with after reviewing the code you shared.
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
namespace ba = boost::asio;
using boost::asio::ip::tcp;
using boost::system::error_code;
using TCPSocket = tcp::socket;
struct processedData { };
struct propertiesPacket { };
// File server.cpp
class ServerConnection : public std::enable_shared_from_this<ServerConnection> {
public:
ServerConnection(TCPSocket socket) : socket_(std::move(socket))
{ }
void start() {
std::clog << __PRETTY_FUNCTION__ << std::endl;
doRead();
}
private:
void doRead()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
auto self(shared_from_this());
socket_.async_read_some(
ba::buffer(&properties_, sizeof(properties_)),
[this, self](error_code ec, std::size_t length) {
std::clog << "received: " << length << std::endl;
if (!ec) {
processData();
std::vector<ba::const_buffer> msg{
ba::buffer(&filePacketSize_, sizeof(uint16_t)),
ba::buffer(&filePacket_, filePacketSize_),
};
ba::async_write(socket_, msg,
[this, self = shared_from_this()](
error_code ec, std::size_t length) {
std::clog << " written: " << length
<< std::endl;
if (!ec) {
doRead();
}
});
}
});
}
void processData() {
std::clog << __PRETTY_FUNCTION__ << std::endl;
/* Data is processed */
}
TCPSocket socket_;
propertiesPacket properties_{};
processedData filePacket_{};
uint16_t filePacketSize_ = sizeof(filePacket_);
};
class Server
{
public:
using IOContext = ba::io_context;
using TCPAcceptor = tcp::acceptor;
Server(IOContext& io, uint16_t port)
: socket_(io)
, acceptor_(io, {tcp::v4(), port})
{
doAccept();
}
private:
void doAccept()
{
std::clog << __PRETTY_FUNCTION__ << std::endl;
acceptor_.async_accept(socket_, [this](error_code ec) {
if (!ec) {
std::clog << "Accepted " << socket_.remote_endpoint()
<< std::endl;
std::make_shared<ServerConnection>(std::move(socket_))->start();
doAccept();
} else {
std::clog << "Accept " << ec.message() << std::endl;
}
});
}
TCPSocket socket_;
TCPAcceptor acceptor_;
};
// File client.cpp
int main(int argc, char *argv[])
{
ba::io_context io;
Server s{io, 6869};
std::thread server_thread{[&io] {
io.run();
}};
// always check argc!
std::vector<std::string> args(argv, argv + argc);
if (args.size() == 1)
args = {"demo", "127.0.0.1", "6869"};
// avoid race with server accept thread
post(io, [&io, args] {
try {
propertiesPacket properties;
// ...
// We set the data inside the properties struct
// ...
tcp::socket socket(io);
tcp::resolver resolver(io);
connect(socket, resolver.resolve(args.at(1), args.at(2)));
write(socket, ba::buffer(&properties, sizeof(properties)));
uint16_t responseSize{};
ba::read(socket, ba::buffer(&responseSize, sizeof(uint16_t)));
std::clog << "Client responseSize: " << responseSize << std::endl;
processedData response{};
assert(responseSize <= sizeof(response));
ba::read(socket, ba::buffer(&response, responseSize));
// ...
// The client handles the data
// ...
// for online demo:
io.stop();
} catch (std::exception const& e) {
std::clog << e.what() << std::endl;
}
});
io.run_one();
server_thread.join();
}
Printing something similar to
void Server::doAccept()
Server::doAccept()::<lambda(boost::system::error_code)> Success
void ServerConnection::start()
void ServerConnection::doRead()
void Server::doAccept()
received: 1
void ServerConnection::processData()
written: 3
void ServerConnection::doRead()
Client responseSize: 1
I'm trying to write a TCP client using several different examples using Asio from Boost 1.60. The connection works properly for probably 30 seconds or so, but disconnects with the error:
The network connection was aborted by the local system
I've attempted to set up a "ping/pong" setup to keep the connection alive but it still terminates. The only previous Stack Overflow answers I've found suggested using Boost's shared_from_this and a shared pointer, which I've adapted my code to use. But the problem persists.
Setting up the Connection object and its thread:
boost::asio::io_service ios;
boost::asio::ip::tcp::resolver res(ios);
boost::shared_ptr<Connection> conn = boost::shared_ptr<Connection>(new Connection(ios));
conn->Start(res.resolve(boost::asio::ip::tcp::resolver::query("myserver", "10635")));
boost::thread t(boost::bind(&boost::asio::io_service::run, &ios));
Here's the relevant portions of the Connection class (I made sure to use shared_from_this() everywhere else, too):
class Connection : public boost::enable_shared_from_this<Connection>
{
public:
Connection(boost::asio::io_service &io_service)
: stopped_(false),
socket_(io_service),
deadline_(io_service),
heartbeat_timer_(io_service)
{
}
void Start(tcp::resolver::iterator endpoint_iter)
{
start_connect(endpoint_iter);
deadline_.async_wait(boost::bind(&Connection::check_deadline, shared_from_this()));
}
private:
void start_read()
{
deadline_.expires_from_now(boost::posix_time::seconds(30));
boost::asio::async_read_until(socket_, input_buffer_, 0x1f,
boost::bind(&Connection::handle_read, shared_from_this(), _1));
}
void handle_read(const boost::system::error_code& ec)
{
if (stopped_)
return;
if (!ec)
{
std::string line;
std::istream is(&input_buffer_);
std::getline(is, line);
if (!line.empty())
{
std::cout << "Received: " << line << "\n";
}
start_read();
}
else
{
// THIS IS WHERE THE ERROR IS LOGGED
std::cout << "Error on receive: " << ec.message() << "\n";
Stop();
}
}
void check_deadline()
{
if (stopped_)
return;
if (deadline_.expires_at() <= deadline_timer::traits_type::now())
{
socket_.close();
deadline_.expires_at(boost::posix_time::pos_infin);
}
deadline_.async_wait(boost::bind(&Connection::check_deadline, shared_from_this()));
}
};
The issue turned out to be on the server's end. The server wasn't sending the "pong" response to the client's ping properly, so the async_read_until() call never finished and consequently never reset the deadline timer.
[disclaimer] I am new to boost.
Looking into boost::asio and tried to create a simple asynchronous TCP server with the following functionality:
Listen for connections on port 13
When connected, receive data
If data received == time, then return current datetime, else return a predefined string ("Something else was requested")
The problem:
Although, I accept the connection and receive the data, when transmitting data using async_send, although I receive no error and the value of bytes_transferred is correct, I receive empty data on the client side.
If I try to transmit the data from within handle_accept (instead of handle_read), this works fine.
The implementation:
I worked on the boost asio tutorial found here:
Instantiate a tcp_server object, that basically initiates the acceptor and starts listening. as shown below:
int main()
{
try
{
boost::asio::io_service io_service;
tcp_server server(io_service);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return 0;
}
and in tcp_server:
class tcp_server
{
public:
tcp_server(boost::asio::io_service& io_service)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), 13))
{
start_accept();
}
private:
void start_accept()
{
using std::cout;
tcp_connection::pointer new_connection =
tcp_connection::create(acceptor_.get_io_service());
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
cout << "Done";
}
...
}
Once a connection is accepted, I am handling it as shown below:
void handle_accept(tcp_connection::pointer new_connection,
const boost::system::error_code& error)
{
if (!error)
{
new_connection->start();
}
start_accept();
}
Below is the tcp_connection::start() method:
void start()
{
boost::asio::async_read(socket_, boost::asio::buffer(inputBuffer_),
boost::bind(&tcp_connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
/* the snippet below works here - but not in handle_read
outputBuffer_ = make_daytime_string();
boost::asio::async_write(socket_, boost::asio::buffer(outputBuffer_),
boost::bind(&tcp_connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));*/
}
and in handle_read:
void handle_read(const boost::system::error_code& error, size_t bytes_transferred)
{
outputBuffer_ = make_daytime_string();
if (strcmp(inputBuffer_, "time"))
{
/*this does not work - correct bytes_transferred but nothing shown on receiving end */
boost::asio::async_write(socket_, boost::asio::buffer(outputBuffer_),
boost::bind(&tcp_connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
outputBuffer_ = "Something else was requested";//, 128);
boost::asio::async_write(socket_, boost::asio::buffer(outputBuffer_),
boost::bind(&tcp_connection::handle_write, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
The handle_write is shown below:
void handle_write(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
std::cout << "Bytes transferred: " << bytes_transferred;
std::cout << "Message sent: " << outputBuffer_;
}
else
{
std::cout << "Error in writing: " << error.message();
}
}
Note the following regarding handle_write (and this is the really strange thing):
There is no error
The bytes_transferred variable has the correct value
outputBuffer_ has the correct value (as set in handle_read)
Nevertheless, the package received at the client side (Packet Sender) is empty (as far as data is concerned).
The complete code is shared here.
Complete test program (c++14). Note the handling of asynchronous buffering when responding to a receive - there may be a send already in progress.
#include <boost/asio.hpp>
#include <thread>
#include <future>
#include <vector>
#include <array>
#include <memory>
#include <mutex>
#include <condition_variable>
#include <iterator>
#include <iostream>
namespace asio = boost::asio;
asio::io_service server_service;
asio::io_service::work server_work{server_service};
bool listening = false;
std::condition_variable cv_listening;
std::mutex management_mutex;
auto const shared_query = asio::ip::tcp::resolver::query(asio::ip::tcp::v4(), "localhost", "8082");
void client()
try
{
asio::io_service client_service;
asio::ip::tcp::socket socket(client_service);
auto lock = std::unique_lock<std::mutex>(management_mutex);
cv_listening.wait(lock, [] { return listening; });
lock.unlock();
asio::ip::tcp::resolver resolver(client_service);
asio::connect(socket, resolver.resolve(shared_query));
auto s = std::string("time\ntime\ntime\n");
asio::write(socket, asio::buffer(s));
socket.shutdown(asio::ip::tcp::socket::shutdown_send);
asio::streambuf sb;
boost::system::error_code sink;
asio::read(socket, sb, sink);
std::cout << std::addressof(sb);
socket.close();
server_service.stop();
}
catch(const boost::system::system_error& se)
{
std::cerr << "client: " << se.code().message() << std::endl;
}
struct connection
: std::enable_shared_from_this<connection>
{
connection(asio::io_service& ios)
: strand_(ios)
{
}
void run()
{
asio::async_read_until(socket_, buffer_, "\n",
strand_.wrap([self = shared_from_this()](auto const&ec, auto size)
{
if (size == 0 )
{
// error condition
boost::system::error_code sink;
self->socket_.shutdown(asio::ip::tcp::socket::shutdown_receive, sink);
}
else {
self->buffer_.commit(size);
std::istream is(std::addressof(self->buffer_));
std::string str;
while (std::getline(is, str))
{
if (str == "time") {
self->queue_send("eight o clock");
}
}
self->run();
}
}));
}
void queue_send(std::string s)
{
assert(strand_.running_in_this_thread());
s += '\n';
send_buffers_pending_.push_back(std::move(s));
nudge_send();
}
void nudge_send()
{
assert(strand_.running_in_this_thread());
if (send_buffers_sending_.empty() and not send_buffers_pending_.empty())
{
std::swap(send_buffers_pending_, send_buffers_sending_);
std::vector<asio::const_buffers_1> send_buffers;
send_buffers.reserve(send_buffers_sending_.size());
std::transform(send_buffers_sending_.begin(), send_buffers_sending_.end(),
std::back_inserter(send_buffers),
[](auto&& str) {
return asio::buffer(str);
});
asio::async_write(socket_, send_buffers,
strand_.wrap([self = shared_from_this()](auto const& ec, auto size)
{
// should check for errors here...
self->send_buffers_sending_.clear();
self->nudge_send();
}));
}
}
asio::io_service::strand strand_;
asio::ip::tcp::socket socket_{strand_.get_io_service()};
asio::streambuf buffer_;
std::vector<std::string> send_buffers_pending_;
std::vector<std::string> send_buffers_sending_;
};
void begin_accepting(asio::ip::tcp::acceptor& acceptor)
{
auto candidate = std::make_shared<connection>(acceptor.get_io_service());
acceptor.async_accept(candidate->socket_, [candidate, &acceptor](auto const& ec)
{
if (not ec) {
candidate->run();
begin_accepting(acceptor);
}
});
}
void server()
try
{
asio::ip::tcp::acceptor acceptor(server_service);
asio::ip::tcp::resolver resolver(server_service);
auto first = resolver.resolve(shared_query);
acceptor.open(first->endpoint().protocol());
acceptor.bind(first->endpoint());
acceptor.listen();
begin_accepting(acceptor);
auto lock = std::unique_lock<std::mutex>(management_mutex);
listening = true;
lock.unlock();
cv_listening.notify_all();
server_service.run();
}
catch(const boost::system::system_error& se)
{
std::cerr << "server: " << se.code().message() << std::endl;
}
int main()
{
using future_type = std::future<void>;
auto stuff = std::array<future_type, 2> {{std::async(std::launch::async, client),
std::async(std::launch::async, server)}};
for (auto& f : stuff) f.wait();
}
There are multiple issues in this code. Some of them may be responsible for your problem:
TCP has no definition of packets, so there's no guarantee that you will ever receive time at once in handle_read. You need a statemachine for that and to respect the bytes_transferred info. If you only have received a part of the message you need to continue at the correct offset. Or you can use asio utility functions, like reading exactly a length of bytes or reading a line.
In addition the last point, you shouldn't really compare the received data with strcmp. That will only work if the remote also sends a null terminator over the connection - does it?
You don't check whether an error happend, although that might manifest itself in other errors.
You are possibly issueing multiple concurrent async writes if you receive multiple data fragments in a shart timespan. This is not valid in asio.
More important, you mutate the send buffer (outputBuffer_) while the send is in progress. This will pretty much lead to undefined behavior. asio might try to write a piece of memory which is no longer valid.
I have solved the problem with the collective help of the comments provided in the question. The behavior that I was experiencing was because of the functionality of async_read. More specifically in the boost asio documentation it reads:
This function is used to asynchronously read a certain number of bytes
of data from a stream. The function call always returns immediately.
The asynchronous operation will continue until one of the following
conditions is true:
The supplied buffers are full. That is, the bytes transferred is equal to the sum of the buffer sizes.
An error occurred.
The inputBuffer_ I was using to read the input, was a 128 char array. The client I was using, would only transfer the real data (without padding), and therefore the async_read would not return until the connection was closed by the client (or 128 bytes of data were transferred). When the connection was closed by the client there was no way to send back the requested data. This is also the reason that it was working with #Arunmu's simple python tcp client (because he was sending 128 bytes of data always).
To fix the issues, I made the following changes (the full working code is supplied here for reference):
In tcp_connection::start: I am now using async_read_until to read the incoming data (and use \n as a delimiter). The input is stored in a boost::asio::streambuf. async_read is guaranteed to return once the delimiter has been found, or an error has occurred. So there is no chance to issue multiple async_write concurrently.
In handle_read: I have included error checking, which made it much simpler to debug.
I'm trying to create a server that receives connections via domain sockets. I can start the server and I can see the socket being created on the filesystem. But whenever I try to connect to it via socat I get the following error:
2015/03/02 14:00:10 socat[62720] E connect(3, LEN=19 AF=1 "/var/tmp/rpc.sock", 19): Connection refused
This is my Asio code (only the .cpp files). Despite the post title I'm using the Boost-free version of Asio but I don't think that would be a problem.
namespace myapp {
DomainListener::DomainListener(const string& addr) : socket{this->service}, Listener{addr} {
remove(this->address.c_str());
stream_protocol::endpoint ep(this->address);
stream_protocol::acceptor acceptor(this->service, ep);
acceptor.async_accept(this->socket, ep, bind(&DomainListener::accept_callback, this, _1));
}
DomainListener::~DomainListener() {
this->service.stop();
remove(this->address.c_str());
}
void DomainListener::accept_callback(const error_code& ec) noexcept {
this->socket.async_read_some(asio::buffer(this->data), bind(&DomainListener::read_data, this, _1, _2));
}
void DomainListener::read_data(const error_code& ec, size_t length) noexcept {
//std::cerr << "AAA" << std::endl;
//std::cerr << this->data[0] << std::endl;
//std::cerr << "BBB" << std::endl;
}
}
Listener::Listener(const string& addr) : work{asio::io_service::work(this->service)} {
this->address = addr;
}
void Listener::listen() {
this->service.run();
}
Listener::~Listener() {
}
In the code that uses these classes I call listen() whenever I want to start listening to the socket for connections.
I've managed to get this to work with libuv and changed to Asio because I thought it would make for more readable code but I'm finding the documentation to be very ambiguous.
The issue is most likely the lifetime of the acceptor.
The acceptor is an automatic variable in the DomainListener constructor. When the DomainListener constructor completes, the acceptor is destroyed, causing the acceptor to close and cancel outstanding operations, such as the async_accept operations. Cancelled operations will be provided an error code of asio::error::operation_aborted and scheduled for deferred invocation within the io_service. Hence, there may not be an active listener when attempting to connect to the domain socket. For more details on the affects of IO object destruction, see this answer.
DomainListener::DomainListener(const string&) : /* ... */
{
// ...
stream_protocol::acceptor acceptor(...);
acceptor.async_accept(..., bind(accept_callback, ...));
} // acceptor destroyed, and accept_callback likely cancelled
To resolve this, consider extending the lifetime of the acceptor by making it a data member for DomainListener. Additionally, checking the error_code provided to asynchronous operations can provide more insight into the asynchronous call chains.
Here is a complete minimal example demonstrating using domain sockets with Asio.
#include <cstdio>
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
/// #brief server demonstrates using domain sockets to accept
/// and read from a connection.
class server
{
public:
server(
boost::asio::io_service& io_service,
const std::string& file)
: io_service_(io_service),
acceptor_(io_service_,
boost::asio::local::stream_protocol::endpoint(file)),
client_(io_service_)
{
std::cout << "start accepting connection" << std::endl;
acceptor_.async_accept(client_,
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error));
}
private:
void handle_accept(const boost::system::error_code& error)
{
std::cout << "handle_accept: " << error.message() << std::endl;
if (error) return;
std::cout << "start reading" << std::endl;
client_.async_read_some(boost::asio::buffer(buffer_),
boost::bind(&server::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(
const boost::system::error_code& error,
std::size_t bytes_transferred)
{
std::cout << "handle_read: " << error.message() << std::endl;
if (error) return;
std::cout << "read: ";
std::cout.write(buffer_.begin(), bytes_transferred);
std::cout.flush();
}
private:
boost::asio::io_service& io_service_;
boost::asio::local::stream_protocol::acceptor acceptor_;
boost::asio::local::stream_protocol::socket client_;
std::array<char, 1024> buffer_;
};
int main(int argc, char* argv[])
{
if (argc != 2)
{
std::cerr << "Usage: <file>\n";
return 1;
}
// Remove file on startup and exit.
std::string file(argv[1]);
struct file_remover
{
file_remover(std::string file): file_(file) { std::remove(file.c_str()); }
~file_remover() { std::remove(file_.c_str()); }
std::string file_;
} remover(file);
// Create and run the server.
boost::asio::io_service io_service;
server s(io_service, file);
io_service.run();
}
Coliru does not have socat installed, so the following commands use OpenBSD netcat to write "asio domain socket example" to the domain socket:
export SOCKFILE=$PWD/example.sock
./a.out $SOCKFILE &
sleep 1
echo "asio domain socket example" | nc -U $SOCKFILE
Which outputs:
start accepting connection
handle_accept: Success
start reading
handle_read: Success
read: asio domain socket example