Server socket doesn't work properly - "accept is already open" - c++

I've tried to separate my server socket in a singleton. Here's the code:
ServerSocket.h
#pragma once
#include <asio.hpp>
#include <iostream>
using asio::ip::tcp;
class ServerSocket
{
public:
ServerSocket(ServerSocket& otherSingleton) = delete;
void operator=(const ServerSocket& copySingleton) = delete;
tcp::acceptor* InitAcceptor();
tcp::socket* InitSocket();
void StartServerSocket();
void SendData(std::string);
std::array<char, 5000> RecieveData();
static ServerSocket* GetInstance();
private:
static ServerSocket* instance;
tcp::acceptor* acceptor;
tcp::socket* socket;
asio::io_context io_context;
ServerSocket() {
acceptor = InitAcceptor();
socket = InitSocket();
}
~ServerSocket()
{
std::cout << "Server closed";
}
};
ServerSocket.cpp
#include "ServerSocket.h"
tcp::acceptor* ServerSocket::InitAcceptor()
{
try
{
tcp::acceptor* acceptor = new tcp::acceptor(io_context, tcp::endpoint(tcp::v4(), 27015));
return acceptor;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
tcp::socket* ServerSocket::InitSocket()
{
try
{
tcp::socket* socket = new tcp::socket(io_context);
return socket;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
void ServerSocket::StartServerSocket()
{
try
{
std::cout << "Server started";
for (;;)
{
acceptor->accept(*socket);
};
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
std::array<char, 5000> ServerSocket::RecieveData()
{
try {
std::array<char, 5000> buf;
asio::error_code error;
size_t len = socket->read_some(asio::buffer(buf), error);
buf[len] = '\0';
return buf;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
ServerSocket* ServerSocket::instance(nullptr);
ServerSocket* ServerSocket::GetInstance()
{
if (instance == nullptr)
{
instance = new ServerSocket();
}
return instance;
}
Server socket starts, I get:
Server started
when a client connects, I get:
accept: Already open
and the server stops.
I think the error comes from the acceptor being in a for function. But according to the docs, it should work this way. (or at least that's how I understand - https://think-async.com/Asio/asio-1.20.0/doc/asio/tutorial/tutdaytime2.html)
I tried deleting the for loop, like this:
try
{
std::cout << "Server started";
acceptor->accept(*socket);
}
and now there is no problem. But the connection isn't kept open by the server. The client connects once, sends data, and the server stops running.
As far as I understand from the docs, if I set the acceptor in a for(;;), it should be running - but it doesn't work in my case.
So, how can I keep my socket open in my implementation? I want it to be running for more than one SendData - I want it to be able to communicate with the client as long as the client is connected.
Thanks.
//Edit:
Here's the client code:
#include <iostream>
#include <asio.hpp>
#include "../../cereal/archives/json.hpp"
using asio::ip::tcp;
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: client <host>" << std::endl;
return 1;
}
// Socket Parameters
const unsigned port = 27015;
auto ip_address = asio::ip::make_address_v4(argv[1]);
auto endpoint = tcp::endpoint{ ip_address, port };
// Creating and Connecting the Socket
asio::io_context io_context;
auto resolver = tcp::resolver{ io_context };
auto endpoints = resolver.resolve(endpoint);
auto socket = tcp::socket{ io_context };
asio::connect(socket, endpoints);
std::array<char, 5000> buf;
std::cout << "Message to server: ";
asio::error_code ignored_error;
std::string username = "test", password = "mihai";
std::stringstream os;
{
cereal::JSONOutputArchive archive_out(os);
archive_out(
CEREAL_NVP(username),
CEREAL_NVP(password)
);
}
asio::write(socket, asio::buffer(os.str()), ignored_error);
return 0;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
return 1;
}
And Communication.h which is responsible to catching the operation from the client and sending it to the server
#pragma once
#include <iostream>
#include "DBUser.h"
#include "DBPost.h"
class Communication
{
public:
enum class Operations {
eLogin,
eRegister
};
void ExecuteOperation(Operations operation,const std::array<char, 5000>& buffer);
};
.cpp
#include "Communication.h"
void Communication::ExecuteOperation(Operations operation,const std::array<char, 5000>& buffer)
{
DBUser* user= DBUser::getInstance();
switch (operation)
{
case Communication::Operations::eLogin:
{
std::string username, password;
std::stringstream is(buffer.data());
{
cereal::JSONInputArchive archive_in(is);
archive_in(username,password);
}
try
{
user->LoginUser(username, password);
}
catch (const std::exception& e)
{
std::cout << e.what();
}
break;
}
case Communication::Operations::eRegister:
{
std::string username, password;
std::stringstream is(buffer.data());
{
cereal::JSONInputArchive archive_in(is);
archive_in(username, password);
}
try
{
user->CreateUser(username, password);
}
catch (const std::exception& e)
{
std::cout << e.what();
}
break;
}
}
}
Main
#include <iostream>
#include <pqxx/pqxx>
#include "DBLink.h"
#include "DBUser.h"
#include "DBPost.h"
#include "../Logging/Logging.h"
#include <iostream>
#include <string>
#include <asio.hpp>
#include "ServerSocket.h"
#include "Communication.h"
int main()
{
ServerSocket* test = ServerSocket::GetInstance();
test->StartServerSocket();
std::array<char, 5000> buf = test->RecieveData();
Communication communicationInterface;
communicationInterface.ExecuteOperation(Communication::Operations::eRegister, buf);
system("pause");
}

There's a lot of antipattern going on.
Overuse of pointers.
Overuse of new (without any delete, a guaranteed leak)
The destructor claims that "Server closed" but it doesn't actually do a single thing to achieve that.
Two-step initialization (InitXXXX functions). Firstly, you should obviously favor initializer lists
ServerSocket()
: acceptor_(InitAcceptor()), socket_(InitSocket())
{ }
And you need to makeInitAcceptor/InitSocket private to the implementation.
I'll forget the Singleton which is anti-pattern 99% of the time, but I guess that's almost debatable.
In your StartServerSocket you have a loop that reuses the same socket all the time. Of course, it will already be connected. You need separate socket instances:
for (;;) {
acceptor_->accept(*socket_);
};
Simplify/Fix
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
struct Listener {
void Start()
{
std::cout << "Server started";
for (;;) {
auto socket = acceptor_.accept();
std::cout << "Accepted connection from " << socket.remote_endpoint()
<< std::endl;
};
}
static Listener& GetInstance() {
static Listener s_instance{27015}; // or use weak_ptr for finite lifetime
return s_instance;
}
private:
asio::io_context ioc_; // order of declaration is order of init!
tcp::acceptor acceptor_;
Listener(uint16_t port) : acceptor_{ioc_, tcp::endpoint{tcp::v4(), port}} {}
};
int main() {
try {
Listener::GetInstance().Start();
} catch (std::exception const& e) {
std::cerr << e.what() << std::endl;
}
}
Now you could hand the socket instances to a thread. I concur with the other commenters that thread-per-request is likely also an anti-pattern, and you should consider using async IO with Asio (hence the name).
Live Demo

EDIT complete and working example based on the server code from the question:
// main.cxx
#include "ServerSocket.hxx"
#include <boost/asio.hpp>
#include <iostream>
#include <string>
using boost::asio::ip::tcp;
int
main ()
{
ServerSocket *test = ServerSocket::GetInstance ();
test->StartServerSocket ();
std::cout << std::endl;
while (auto msg = test->RecieveData ())
{
std::cout << msg.value ();
}
}
// ServerSocket.hxx
#pragma once
#include <boost/asio.hpp>
#include <iostream>
#include <optional>
using boost::asio::ip::tcp;
class ServerSocket
{
public:
ServerSocket (ServerSocket &otherSingleton) = delete;
void operator= (const ServerSocket &copySingleton) = delete;
tcp::acceptor *InitAcceptor ();
tcp::socket *InitSocket ();
void StartServerSocket ();
void SendData (std::string);
std::optional<std::string> RecieveData ();
static ServerSocket *GetInstance ();
private:
static ServerSocket *instance;
tcp::acceptor *acceptor;
tcp::socket *socket;
boost::asio::io_context io_context;
ServerSocket ()
{
acceptor = InitAcceptor ();
socket = InitSocket ();
}
~ServerSocket () {
delete socket;
delete acceptor;
std::cout << "Server closed"; }
};
// ServerSocket.cxx
#include "ServerSocket.hxx"
#include <optional>
tcp::acceptor *
ServerSocket::InitAcceptor ()
{
try
{
return new tcp::acceptor (io_context, tcp::endpoint (tcp::v4 (), 27015));
}
catch (std::exception &e)
{
std::cerr << e.what () << std::endl;
}
return nullptr;
}
tcp::socket *
ServerSocket::InitSocket ()
{
try
{
return new tcp::socket (io_context);
}
catch (std::exception &e)
{
std::cerr << e.what () << std::endl;
}
return nullptr;
}
void
ServerSocket::StartServerSocket ()
{
try
{
std::cout << "Server started";
acceptor->accept (*socket);
}
catch (std::exception &e)
{
std::cerr << e.what () << std::endl;
}
}
std::optional<std::string>
ServerSocket::RecieveData ()
{
try
{
char data[5000];
for (;;)
{
boost::system::error_code error;
size_t length = socket->read_some (boost::asio::buffer (data), error);
if (error == boost::asio::error::eof) return std::nullopt; // Connection closed cleanly by peer.
else if (error)
throw boost::system::system_error (error); // Some other error.
return std::string{ data, length };
}
}
catch (std::exception &e)
{
std::cerr << e.what () << std::endl;
}
return {};
}
ServerSocket *ServerSocket::instance (nullptr);
ServerSocket *
ServerSocket::GetInstance ()
{
if (instance == nullptr)
{
instance = new ServerSocket ();
}
return instance;
}
Note that there are still some problems with the server:
Error handling
More than one connection
The server does not send a message if the operation was successful
If you disconnect the client the server shuts down
We could replace some pointers with optional no need to write "new"
Just make a normal class do not write it as singleton.
If you like to test the server you can run
telnet localhost 27015
and then write some text and press enter

Related

Instantiating a socket (is it a bad practice or not ?)

I need to do a server-client architecture for an university project and i'm stuck at the sockets part. I thought that i can instantiate the whole server once, then call the socket whenever needed, but, if i do this, whenever i run the server, nothing happens on the client side, unless i specify it to write something in the moment of instantiation.
The code for the instantiated socket:
#include <asio.hpp>
#include <iostream>
using asio::ip::tcp;
class ServerSocket
{
public:
ServerSocket(ServerSocket& otherSingleton) = delete;
void operator=(const ServerSocket& copySingleton) = delete;
static tcp::socket* GetInstance();
private:
static ServerSocket* instance;
tcp::socket* socket;
ServerSocket()
{
std::cout << "Server started";
try
{
asio::io_context io_context;
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 13));
for (;;)
{
tcp::socket socket(io_context);
acceptor.accept(socket);
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
~ServerSocket()
{
std::cout << "Server closed";
}
};
and its cpp
ServerSocket* ServerSocket::instance(nullptr);
tcp::socket* ServerSocket::GetInstance()
{
if (instance == nullptr)
{
instance = new ServerSocket();
}
return instance->socket;
}
I was thinking that actually i need to instantiate the acceptor and specify a socket in my function, ex:
void DBUser::SendUser()
{
ServerSocket* instance;
tcp::socket* socket = instance->GetInstance();
try
{
//instead of trying to write directly in the function i should call the acceptor again
//and create a socket on the spot
asio::error_code ignored_error;
asio::write(*socket, asio::buffer(m_user), ignored_error);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
if anyone can help me understand what i'm doing wrong and how i can fix it i'd appreciate it

Can't set keep_alive to socket in asio

I'm trying to create a ServerSocket as a singleton. Here's the code:
ServerSocket.h
#pragma once
#include <asio.hpp>
#include <iostream>
#include <optional>
using asio::ip::tcp;
class ServerSocket
{
public:
ServerSocket(ServerSocket& otherSingleton) = delete;
void operator= (const ServerSocket& copySingleton) = delete;
tcp::acceptor* InitAcceptor();
tcp::socket* InitSocket();
void StartServerSocket();
void SendData(std::string);
std::optional<std::string> RecieveData();
static ServerSocket* GetInstance();
private:
static ServerSocket* instance;
tcp::acceptor* acceptor;
tcp::socket* socket;
asio::io_context io_context;
ServerSocket()
{
acceptor = InitAcceptor();
socket = InitSocket();
}
~ServerSocket() {
delete socket;
delete acceptor;
std::cout << "Server closed";
}
};
ServerSocket.cpp
#include "ServerSocket.h"
#include <optional>
tcp::acceptor* ServerSocket::InitAcceptor()
{
try
{
return new tcp::acceptor(io_context, tcp::endpoint(tcp::v4(), 27015));
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return nullptr;
}
tcp::socket* ServerSocket::InitSocket()
{
try
{
tcp::socket* deReturnat = new tcp::socket(io_context);
asio::socket_base::keep_alive option(true);
deReturnat->set_option(option);
return deReturnat;
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return nullptr;
}
void ServerSocket::StartServerSocket()
{
try
{
std::cout << "Server started";
acceptor->accept(*socket);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
void ServerSocket::SendData(std::string)
{
}
std::optional<std::string> ServerSocket::RecieveData()
{
try
{
char data[5000];
asio::error_code error;
size_t length = socket->read_some(asio::buffer(data), error);
if (error == asio::error::eof) return std::nullopt; // Connection closed cleanly by peer.
else if (error)
throw asio::system_error(error); // Some other error.
return std::string{ data, length };
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
return {};
}
ServerSocket* ServerSocket::instance(nullptr);
ServerSocket* ServerSocket::GetInstance()
{
if (instance == nullptr)
{
instance = new ServerSocket();
}
return instance;
}
The problem is that in InitSocket, when running a debugger, it seems like set_option doesn't work for some reason, and then an exception is thrown - and I can't understand why.
The exception that is thrown is: set_option: The file handle supplied is not valid.
How can I set the keep_alive option to true in my socket? It seems like this way doesn't work.
#Vasile, your problem, as #dewaffed told you, is that you are setting the options before the socket has been opened.
I don't know what you are trying to do but I can see that you creating a new socket, which is not open, and setting the properties, that's the problem. The correct way is:
Create the Socket
Accept the new connection, with the previous socket you've created.
Once the acceptor has ended to accept a new connection, the socket has a valid File Descriptor, which is required to set the option over the socket.
Check these links:
https://en.wikipedia.org/wiki/Berkeley_sockets
Modifying boost::asio::socket::set_option, which talks about your exception.
https://www.boost.org/doc/libs/1_77_0/doc/html/boost_asio/tutorial/tutdaytime2.html

Trying to write UDP server class, io_context doesn't block

I try to open a UDP server. A baby example works (I receive what I expect and what wireshark also shows):
Baby example:
int main(int argc, char* const argv[])
{
try
{
boost::asio::io_context io_context;
boost::asio::ip::udp::endpoint ep(boost::asio::ip::udp::v4(), 60001);
boost::asio::ip::udp::socket sock(io_context, ep);
UDPServer server(std::move(sock), callbackUDP);
io_context.run();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
UDPServer.hpp:
#include <boost/asio.hpp>
#include <functional>
#include <vector>
#include <thread>
#define BUFFERSIZE 1501
class UDPServer
{
public:
explicit UDPServer(boost::asio::ip::udp::socket socket, std::function<void(const std::vector<char>&)> callbackFunction);
virtual ~UDPServer();
private:
void read();
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint endpoint_;
std::function<void(const std::vector<char>&)> callbackFunction_;
char data_[1500 + 1]; // 1500 bytes is safe limit as it is max of ethernet frame, +1 is for \0 terminator
};
UDPServer.cpp:
#include <iostream>
#include "UDPServer.h"
UDPServer::UDPServer(boost::asio::ip::udp::socket socket, std::function<void(const std::vector<char>&)> callbackFunction):
socket_(std::move(socket)),
callbackFunction_(callbackFunction)
{
read();
}
UDPServer::~UDPServer()
{
}
void UDPServer::read()
{
socket_.async_receive_from(boost::asio::buffer(data_, 1500), endpoint_,
[this](boost::system::error_code ec, std::size_t length)
{
if (ec)
{
return;
}
data_[length] = '\0';
if (strcmp(data_, "\n") == 0)
{
return;
}
std::vector<char> dataVector(data_, data_ + length);
callbackFunction_(dataVector);
read();
}
);
}
Now what I want to convert this to is a class with as constructor only the port and a callback function (let forget about the latter and just print the message for now, adding the callback is normally no problem).
I tried the following, but it doesn't work:
int main(int argc, char* const argv[])
{
UDPServer server(60001);
}
UDPServer.h:
#include <boost/asio.hpp>
#include <functional>
#include <vector>
#include <thread>
#define BUFFERSIZE 1501
class UDPServer
{
public:
explicit UDPServer(uint16_t port);
virtual ~UDPServer();
private:
boost::asio::io_context io_context_;
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint endpoint_;
std::array<char, BUFFERSIZE> recv_buffer_;
std::thread thread_;
void run();
void start_receive();
void handle_reply(const boost::system::error_code& error, std::size_t bytes_transferred);
};
UDPServer.cpp:
#include <iostream>
#include "UDPServer.h"
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <iostream>
UDPServer::UDPServer(uint16_t port):
endpoint_(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
io_context_(),
socket_(io_context_, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
thread_(&UDPServer::run, this)
{
start_receive();
}
UDPServer::~UDPServer()
{
io_context_.stop();
thread_.join();
}
void UDPServer::start_receive()
{
socket_.async_receive_from(boost::asio::buffer(recv_buffer_), endpoint_,
boost::bind(&UDPServer::handle_reply, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
void UDPServer::handle_reply(const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (!error)
{
try {
std::string string(recv_buffer_.data(), recv_buffer_.data() + bytes_transferred);
std::cout << "Message received: " << std::to_string(bytes_transferred) << ", " << string << std::endl;
}
catch (std::exception ex) {
std::cout << "handle_reply: Error parsing incoming message:" << ex.what() << std::endl;
}
catch (...)
{
std::cout << "handle_reply: Unknown error while parsing incoming message" << std::endl;
}
}
else
{
std::cout << "handle_reply: error: " << error.message() << std::endl;
}
start_receive();
}
void UDPServer::run()
{
try {
io_context_.run();
} catch( const std::exception& e )
{
std::cout << "Server network exception: " << e.what() << std::endl;
}
catch(...)
{
std::cout << "Unknown exception in server network thread" << std::endl;
}
std::cout << "Server network thread stopped" << std::endl;
};
When running I get "Server network thread stopped". io_context doesn't seem to start and doesn't block. Someone an idea what I do wrong? Thanks a lot!
EDIT tried this after comment, same result (except that message comes after 1 second)
UDPServer::UDPServer(uint16_t port):
endpoint_(boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port)),
io_context_(),
socket_(io_context_, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), port))
{
start_receive();
std::this_thread::sleep_for (std::chrono::seconds(1));
thread_ = std::thread(&UDPServer::run, this);
}
Your destructor explicitly tells the service to stop:
UDPServer::~UDPServer() {
io_context_.stop();
thread_.join();
}
That's part of your problem. The other part is as pointed out in the comment: you have a race condition where the thread exits before you even post your first async operation.
Solve it by adding a work guard:
boost::asio::io_context io_;
boost::asio::executor_work_guard<boost::asio::io_context::executor_type> work_ {io_.get_executor()};
Now the destructor can be:
UDPServer::~UDPServer() {
work_.reset(); // allow service to run out of work
thread_.join();
}
Other notes:
avoid chaining back to start_receive when there was an error
std::to_string was redundant
the order of initialization for members is defined by the order of their declaration, not their initializers in the initializer list. Catch these bug sources with -Wall -Wextra -pedantic
= handle exceptions in your service thread (see Should the exception thrown by boost::asio::io_service::run() be caught?)
I'd suggest std::bind over boost::bind:
std::bind(&UDPServer::handle_reply, this,
std::placeholders::_1,
std::placeholders::_2));
Or just use a lambda:
[this](error_code ec, size_t xfer) { handle_reply(ec, xfer); });
LIVE DEMO
Compiler Explorer
#include <boost/asio.hpp>
#include <fstream>
#include <functional>
#include <iomanip>
#include <iostream>
#include <thread>
#include <vector>
using boost::asio::ip::udp;
using boost::system::error_code;
using boost::asio::io_context;
#define BUFFERSIZE 1501
class UDPServer {
public:
explicit UDPServer(uint16_t port);
virtual ~UDPServer();
private:
io_context io_;
boost::asio::executor_work_guard<io_context::executor_type> work_ {io_.get_executor()};
udp::endpoint endpoint_;
udp::socket socket_;
std::array<char, BUFFERSIZE> recv_buffer_;
std::thread thread_;
void run();
void start_receive();
void handle_reply(const error_code& error, size_t transferred);
};
UDPServer::UDPServer(uint16_t port)
: endpoint_(udp::endpoint(udp::v4(), port)),
socket_(io_, endpoint_),
thread_(&UDPServer::run, this) {
start_receive();
}
UDPServer::~UDPServer() {
work_.reset(); // allow service to run out of work
thread_.join();
}
void UDPServer::start_receive() {
socket_.async_receive_from(boost::asio::buffer(recv_buffer_), endpoint_,
#if 0
std::bind(&UDPServer::handle_reply, this,
std::placeholders::_1,
std::placeholders::_2));
#else
[this](error_code ec, size_t xfer) { handle_reply(ec, xfer); });
#endif
}
void UDPServer::handle_reply(const error_code& error, size_t transferred) {
if (!error) {
try {
std::string_view s(recv_buffer_.data(), transferred);
std::cout << "Message received: " << transferred << ", "
<< std::quoted(s) << "\n";
} catch (std::exception const& ex) {
std::cout << "handle_reply: Error parsing incoming message:"
<< ex.what() << "\n";
} catch (...) {
std::cout
<< "handle_reply: Unknown error while parsing incoming message\n";
}
start_receive();
} else {
std::cout << "handle_reply: error: " << error.message() << "\n";
}
}
void UDPServer::run() {
while (true) {
try {
if (io_.run() == 0u) {
break;
}
} catch (const std::exception& e) {
std::cout << "Server network exception: " << e.what() << "\n";
} catch (...) {
std::cout << "Unknown exception in server network thread\n";
}
}
std::cout << "Server network thread stopped\n";
}
int main() {
std::cout << std::unitbuf;
UDPServer server(60001);
}
Testing with random words:
sort -R /etc/dictionaries-common/words | while read w; do sleep 1; netcat -u localhost 60001 -w 0 <<<"$w"; done
Live output:

C++ Boost UDP receiver fails when put into thread

I have a UDP receiver that works. The code is here:
#include <array>
#include <iostream>
#include <string>
#include <boost/asio.hpp>
std::string getMyIp()
{
std::string result;
try
{
boost::asio::io_service netService;
boost::asio::ip::udp::resolver resolver(netService);
boost::asio::ip::udp::udp::resolver::query query(boost::asio::ip::udp::v4(), "google.com", "");
boost::asio::ip::udp::udp::resolver::iterator endpoints = resolver.resolve(query);
boost::asio::ip::udp::udp::endpoint ep = *endpoints;
boost::asio::ip::udp::udp::socket socket(netService);
socket.connect(ep);
boost::asio::ip::address addr = socket.local_endpoint().address();
result = addr.to_string();
//std::cout << "My IP according to google is: " << results << std::endl;
}
catch (std::exception& e)
{
std::cerr << "Could not deal with socket. Exception: " << e.what() << std::endl;
}
return result;
}
class receiver
{
private:
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint sender_endpoint_;
std::array<char, 1024> data_;
public:
receiver(boost::asio::io_service& io_service,
const boost::asio::ip::address& listen_address,
const boost::asio::ip::address& multicast_address,
unsigned short multicast_port = 13000)
: socket_(io_service)
{
// Create the socket so that multiple may be bound to the same address.
boost::asio::ip::udp::endpoint listen_endpoint(listen_address, multicast_port);
socket_.open(listen_endpoint.protocol());
socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
socket_.bind(listen_endpoint);
// Join the multicast group.
socket_.set_option(boost::asio::ip::multicast::join_group(multicast_address));
do_receive();
}
private:
void do_receive()
{
socket_.async_receive_from(boost::asio::buffer(data_), sender_endpoint_, [this](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
std::cout.write(data_.data(), length);
std::cout << std::endl;
do_receive();
}
});
}
};
int main(int argc, char* argv[])
{
try
{
boost::asio::io_service io_service;
receiver r(io_service, boost::asio::ip::make_address(getMyIp()), boost::asio::ip::make_address("224.0.0.0"), 13000);
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
I want to put the receiver code into a thread inside a class so I can do other things beside it:
#define _CRT_SECURE_NO_WARNINGS
#include <ctime>
#include <iostream>
#include <string>
#include <queue>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
#include <boost/thread/thread.hpp>
#include <boost/chrono.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
using boost::asio::ip::udp;
using std::cout;
using std::cin;
using std::endl;
using std::string;
using namespace std;
std::string getMyIp()
{
std::string result;
try
{
boost::asio::io_service netService;
boost::asio::ip::udp::resolver resolver(netService);
boost::asio::ip::udp::udp::resolver::query query(boost::asio::ip::udp::v4(), "google.com", "");
boost::asio::ip::udp::udp::resolver::iterator endpoints = resolver.resolve(query);
boost::asio::ip::udp::udp::endpoint ep = *endpoints;
boost::asio::ip::udp::udp::socket socket(netService);
socket.connect(ep);
boost::asio::ip::address addr = socket.local_endpoint().address();
result = addr.to_string();
//std::cout << "My IP according to google is: " << results << std::endl;
}
catch (std::exception& e)
{
std::cerr << "Could not deal with socket. Exception: " << e.what() << std::endl;
}
return result;
}
class UdpReceiver
{
private:
boost::asio::ip::udp::socket socket_;
boost::asio::ip::udp::endpoint sender_endpoint_;
std::array<char, 1024> data_;
string address_send, address_recv;
unsigned short port_send, port_recv;
boost::thread_group threads; // thread group
boost::thread* thread_main; // main thread
boost::thread* thread_receive; // receive thread
boost::thread* thread_send; // get/send thread
boost::mutex stopMutex;
bool initialize = false;
bool stop, showBroadcast;
int i_send, i_recv, i_operator,
interval_send, interval_recv, interval_operator,
mode;
string message_send, message_recv;
string message_STOP = "STOP";
public:
// constructor
UdpReceiver(boost::asio::io_service& io_service, std::string address, unsigned short port, int interval, int mode, bool show = false)
: socket_(io_service),
showBroadcast(show)
{
initialize = false;
Initialize(io_service, show);
}
UdpReceiver(boost::asio::io_service& io_service, bool show = false)
: socket_(io_service),
showBroadcast(show)
{
Initialize(io_service, show);
}
// destructor
~UdpReceiver()
{
// show exit message
cout << "Exiting UDP Core." << endl;
}
// initialize
void Initialize(boost::asio::io_service& io_service, bool show = false)
{
if (initialize == false)
{
GetMode(true);
GetInfo(true);
}
CreateEndpoint(io_service);
CreateThreads();
stop = false;
showBroadcast = show;
i_send = 0;
i_recv = 0;
i_operator = 0;
message_send.clear();
message_recv.clear();
initialize = true; // clear flag
}
void GetMode(bool default_value = false)
{
std::string input;
if (default_value)
{
mode = 0;
}
else
{
string prompt = "Set mode:\n0/other - Listen\n1 - Send\nEnter your choice: ";
cout << prompt;
getline(cin, input);
try
{
mode = stoi(input);
// set default mode to Listen
if (mode > 1)
mode = 0;
}
catch (exception ec)
{
cout << "Error converting mode: " << ec.what() << endl;
Stop();
}
}
}
void GetInfo(bool default_value = false)
{
// always called after GetMode()
string address;
unsigned short port;
int interval;
if (default_value)
{
address = getMyIp();
port = 13000;
interval = 500;
}
switch (mode)
{
case 0:
address_recv = address;
port_recv = port;
interval_recv = interval;
break;
case 1:
address_send = address;
port_send = port;
interval_send = interval;
break;
default:
// already set to 0 in GetMode()
break;
}
}
void CreateEndpoint(boost::asio::io_service& io_service)
{
// Create the socket so that multiple may be bound to the same address.
boost::asio::ip::udp::endpoint listen_endpoint(boost::asio::ip::address::from_string(address_recv), port_recv);
socket_.open(listen_endpoint.protocol());
socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));
socket_.bind(listen_endpoint);
// Join the multicast group.
socket_.set_option(boost::asio::ip::multicast::join_group(boost::asio::ip::address::from_string("224.0.0.0")));
}
void CreateThreads()
{
thread_main = new boost::thread(boost::ref(*this));
interval_operator = 500; // default value
switch (mode)
{
case 0:
thread_receive = new boost::thread(&UdpReceiver::Callable_Receive, this);
threads.add_thread(thread_receive);
break;
default:
// already set to 0 in GetMode()
break;
}
}
// start the threads
void Start()
{
// Wait till they are finished
threads.join_all();
}
// stop the threads
void Stop()
{
// warning message
cout << "Stopping all threads." << endl;
// signal the threads to stop (thread-safe)
stopMutex.lock();
stop = true;
stopMutex.unlock();
// wait for the threads to finish
thread_main->interrupt(); // in case not interrupted by operator()
threads.interrupt_all();
threads.join_all();
// close socket after everything closes
//socketPtr->close();
socket_.close();
}
void Callable_Receive()
{
while (!stop)
{
stopMutex.lock();
socket_.async_receive_from(boost::asio::buffer(data_), sender_endpoint_, [this](boost::system::error_code ec, std::size_t length)
{
if (!ec)
{
//cout << message_recv << endl;
std::cout.write(data_.data(), length);
std::cout << std::endl;
Callable_Receive();
}
});
stopMutex.unlock();
//cout << i_recv << endl;
++i_recv;
}
}
// Thread function
void operator () ()
{
while (!stop)
{
if (message_send == message_STOP)
{
try
{
this->Stop();
}
catch (exception e)
{
cout << e.what() << endl;
}
}
boost::this_thread::sleep(boost::posix_time::millisec(interval_operator));
boost::this_thread::interruption_point();
}
}
};
int main()
{
try
{
boost::asio::io_service io_service;
UdpReceiver mt(io_service, false);
mt.Start();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
}
The async receive is inside Callable_Receive(), which is inside by thread_receive. I can see that thread running when the counter is printed on screen (which I comment out). However, the async_receive_from() never receives anything. Could someone tell me why this happens?
You have probably deadlock in Callable_Receive. In thread with Callable_Receive as body of thread you are calling stopMutex.lock before invoking async_receive_from function. async_receive_from returns immediately, but we don't know when lambda object passed as third paremeter to async_receive_from will be called. When body of lambda object is executed, you are calling Callable_Receive function, if stopMutex was locked (thread with Callable_Receive is still running and next iteration in while loop is being done) and you try to lock it again, you would get deadlock - on boost::mutex you cannot call lock method while mutex is already being locked by the same thread.
You should read about boost::recursive_mutex if you want to resolve this issue.

boost::asio + std::future - Access violation after closing socket

I am writing a simple tcp client to send and receive single lines of text. The asynchronous operations are handled by std::future in order to faciliate blocking queries with timeouts. Unfortunately, my test application crashes with an access violation when destructing the server object.
Here is my code:
TCPClient.hpp
#ifndef __TCPCLIENT_H__
#define __TCPCLIENT_H__
#include <boost/asio.hpp>
#include <boost/asio/use_future.hpp>
#include <memory>
#include <vector>
#include <future>
#include <thread>
#include <chrono>
#include <iostream>
#include <iterator>
using namespace boost::asio;
class TCPClient {
public:
TCPClient();
~TCPClient();
void connect(const std::string& address, const std::string& port);
void disconnect();
std::string sendMessage(const std::string& msg);
private:
boost::asio::io_service ioservice;
boost::asio::io_service::work work;
std::thread t;
std::unique_ptr<boost::asio::ip::tcp::socket> socket;
};
inline TCPClient::TCPClient() : ioservice(), work(ioservice) {
t = std::thread([&]() {
try {
ioservice.run();
}
catch (const boost::system::system_error& e) {
std::cerr << e.what() << std::endl;
}
});
}
inline TCPClient::~TCPClient() {
disconnect();
ioservice.stop();
if (t.joinable()) t.join();
}
inline void TCPClient::connect(const std::string& address, const std::string& port) {
socket.reset(new ip::tcp::socket(ioservice));
ip::tcp::resolver::query query(address, port);
std::future<ip::tcp::resolver::iterator> conn_result = async_connect(*socket, ip::tcp::resolver(ioservice).resolve(query), use_future);
if (conn_result.wait_for(std::chrono::seconds(6)) != std::future_status::timeout) {
conn_result.get(); // throws boost::system::system_error if the operation fails
}
else {
//socket->close();
// throw timeout_error("Timeout");
throw std::exception("timeout");
}
}
inline void TCPClient::disconnect() {
if (socket) {
try {
socket->shutdown(ip::tcp::socket::shutdown_both);
std::cout << "socket points to " << std::addressof(*socket) << std::endl;
socket->close();
}
catch (const boost::system::system_error& e) {
// ignore
std::cerr << "ignored error " << e.what() << std::endl;
}
}
}
inline std::string TCPClient::sendMessage(const std::string& msg) {
auto time_over = std::chrono::system_clock::now() + std::chrono::seconds(4);
/*
// Doesn't affect the error
std::future<size_t> write_fut = boost::asio::async_write(*socket, boost::asio::buffer(msg), boost::asio::use_future);
try {
write_fut.get();
}
catch (const boost::system::system_error& e) {
std::cerr << e.what() << std::endl;
}
*/
boost::asio::streambuf response;
std::future<std::size_t> read_fut = boost::asio::async_read_until(*socket, response, '\n', boost::asio::use_future);
if (read_fut.wait_until(time_over) != std::future_status::timeout) {
std::cout << "read " << read_fut.get() << " bytes" << std::endl;
return std::string(std::istreambuf_iterator<char>(&response), std::istreambuf_iterator<char>());
}
else {
std::cout << "socket points to " << std::addressof(*socket) << std::endl;
throw std::exception("timeout");
}
}
#endif
main.cpp
#include <iostream>
#include "TCPClient.hpp"
int main(int argc, char* argv[]) {
TCPClient client;
try {
client.connect("localhost", "27015");
std::cout << "Response: " << client.sendMessage("Hello!") << std::endl;
}
catch (const boost::system::system_error& e) {
std::cerr << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
system("pause");
return 0;
}
The output is "timeout" as expected (test server sends no data on purpose), but ioservice.run() crashes immediately (access violation) after closing the socket in TCPClient::disconnect(). Am I doing some memory mismanagment here?
Compiler is MSVC 12.0.31101.00 Update 4 (Visual Studio 2013)
recvmsg is receiving into a buffer (streambuf) that was freed after throwing the exception in TCPClient::sendMessage (line 105, end of scope).
You forgot to cancel the asynchronous operation (async_read_until) started in line 97. Fix it:
else {
socket->cancel(); // ADDED
std::cout << "socket points to " << std::addressof(*socket) << std::endl;
throw std::runtime_error("timeout");
}
Or even, just
socket.reset(); // ADDED
Same goes for other timeout paths.
The other answer addresses what went wrong.
On a higher level, though, you're using futures, just to immediately await their return.
It struck me that this is actually not asynchrony at all, and you should be able to do:
without threading, and joining
without .stop()
without work and work.reset()
without a explicit constructor or destructor
without the unique_ptr<socket> and the lifetime management that came with it
without the future<>, and the .get() and future_status checking that come with it
All in all, you can do a lot simpler, e.g. using a simple helper function like this:
class TCPClient {
public:
void disconnect();
void connect(const std::string& address, const std::string& port);
std::string sendMessage(const std::string& msg);
private:
using error_code = boost::system::error_code;
template<typename AllowTime> void await_operation(AllowTime const& deadline_or_duration) {
using namespace boost::asio;
ioservice.reset();
{
high_resolution_timer tm(ioservice, deadline_or_duration);
tm.async_wait([this](error_code ec) { if (ec != error::operation_aborted) socket.cancel(); });
ioservice.run_one();
}
ioservice.run();
}
boost::asio::io_service ioservice { };
boost::asio::ip::tcp::socket socket { ioservice };
};
E.g. connect(...) used to be:
socket.reset(new ip::tcp::socket(ioservice));
ip::tcp::resolver::query query(address, port);
std::future<ip::tcp::resolver::iterator> conn_result = async_connect(*socket, ip::tcp::resolver(ioservice).resolve(query), use_future);
if (conn_result.wait_for(std::chrono::seconds(6)) != std::future_status::timeout) {
conn_result.get(); // throws boost::system::system_error if the operation fails
}
else {
socket->cancel();
// throw timeout_error("Timeout");
throw std::runtime_error("timeout");
}
It now becomes:
async_connect(socket,
ip::tcp::resolver(ioservice).resolve({address, port}),
[&](error_code ec, ip::tcp::resolver::iterator it) { if (ec) throw std::runtime_error(ec.message()); });
await_operation(std::chrono::seconds(6));
Like wise, sendMessage becomes:
streambuf response;
async_read_until(socket, response, '\n', [&](error_code ec, size_t bytes_read) {
if (ec) throw std::runtime_error(ec.message());
std::cout << "read " << bytes_read << " bytes" << std::endl;
});
await_operation(std::chrono::system_clock::now() + std::chrono::seconds(4));
return {std::istreambuf_iterator<char>(&response), {}};
Note these are significantly simpler. Note, also, that correct exception messages are now thrown, depending on the cause of the failures.
Full Demo
Live On Coliru
#ifndef __TCPCLIENT_H__
#define __TCPCLIENT_H__
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <iostream>
class TCPClient {
public:
void disconnect();
void connect(const std::string& address, const std::string& port);
std::string sendMessage(const std::string& msg);
private:
using error_code = boost::system::error_code;
template<typename AllowTime> void await_operation(AllowTime const& deadline_or_duration) {
using namespace boost::asio;
ioservice.reset();
{
high_resolution_timer tm(ioservice, deadline_or_duration);
tm.async_wait([this](error_code ec) { if (ec != error::operation_aborted) socket.cancel(); });
ioservice.run_one();
}
ioservice.run();
}
boost::asio::io_service ioservice { };
boost::asio::ip::tcp::socket socket { ioservice };
};
inline void TCPClient::connect(const std::string& address, const std::string& port) {
using namespace boost::asio;
async_connect(socket,
ip::tcp::resolver(ioservice).resolve({address, port}),
[&](error_code ec, ip::tcp::resolver::iterator it) { if (ec) throw std::runtime_error(ec.message()); });
await_operation(std::chrono::seconds(6));
}
inline void TCPClient::disconnect() {
using namespace boost::asio;
if (socket.is_open()) {
try {
socket.shutdown(ip::tcp::socket::shutdown_both);
socket.close();
}
catch (const boost::system::system_error& e) {
// ignore
std::cerr << "ignored error " << e.what() << std::endl;
}
}
}
inline std::string TCPClient::sendMessage(const std::string& msg) {
using namespace boost::asio;
streambuf response;
async_read_until(socket, response, '\n', [&](error_code ec, size_t bytes_read) {
if (ec) throw std::runtime_error(ec.message());
std::cout << "read " << bytes_read << " bytes" << std::endl;
});
await_operation(std::chrono::system_clock::now() + std::chrono::seconds(4));
return {std::istreambuf_iterator<char>(&response), {}};
}
#endif
#include <iostream>
//#include "TCPClient.hpp"
int main(/*int argc, char* argv[]*/) {
TCPClient client;
try {
client.connect("127.0.0.1", "27015");
std::cout << "Response: " << client.sendMessage("Hello!") << std::endl;
}
catch (const boost::system::system_error& e) {
std::cerr << e.what() << std::endl;
}
catch (const std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
BONUS
If you want even more convenience, have a generalized callback handler that just raises the exception:
struct raise {
template <typename... A> void operator()(error_code ec, A...) const {
if (ec) throw std::runtime_error(ec.message());
}
};
Now, the bodies become even simpler in absense of lambdas:
inline void TCPClient::connect(const std::string& address, const std::string& port) {
async_connect(socket, ip::tcp::resolver(ioservice).resolve({address, port}), raise());
await_operation(std::chrono::seconds(6));
}
inline std::string TCPClient::sendMessage(const std::string& msg) {
streambuf response;
async_read_until(socket, response, '\n', raise());
await_operation(std::chrono::system_clock::now() + std::chrono::seconds(4));
return {std::istreambuf_iterator<char>(&response), {}};
}
See the adapted demo: Live On Coliru too