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
Related
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 ©Singleton) = 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
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
My problem is related to moving Boost.Asio sockets using std::move.
I'm working on a server in C++ with switchable implementations and libraries (Boost.Asio, POSIX sockets, Winsock sockets). To achieve this, I have HttpServer and HttpClient interfaces which are implemented by classes like PosixHttpServer, PosixHttpClient, BoostHttpServer, BoostHttpClient. After binding and listening on the server, the HttpClient would be created by HttpServer::accept(). I made sure to disable copying on both HttpServer and HttpClient. I'm not planning to use async operations right now.
// main.cpp
using HttpServerPtr = std::unique_ptr<HttpServer>;
using HttpClientPtr = std::unique_ptr<HttpClient>;
...
int main(int argc, char* argv[])
{
...
//HttpServerPtr server = std::make_unique<PosixHttpServer>();
HttpServerPtr server = std::make_unique<BoostHttpServer>();
if (!server->bind(server_port)
{
...
}
if (!server->listen())
{
...
}
std::cout << "Listening to client connections at "
<< server->getAddress()
<< " on port " << std::to_string(server_port)
<< std::endl;
while (1)
{
HttpClientPtr client = server->accept();
if (!client)
{
std::cerr << "failed to accept" << std::endl;
continue;
}
std::cout << "Got connection from "
<< client->getAddress() << std::endl;
// TODO: Write HTTP response
if (!client->write("Hello World!"))
{
std::cerr << "failed to write" << std::endl;
}
}
return EXIT_SUCCESS;
}
To decouple the client socket from the HttpServer, the client boost::asio::ip::tcp::socket would be stored as a member of HttpClient. In BoostHttpServer::accept(), I am able to pass the Boost.Asio socket to the HttpClient if I use std::shared_ptr (_io_service and _acceptor are private members of HttpServer).
// boosthttpserver.cpp
using tcp = boost::asio::ip::tcp;
bool BoostHttpServer::bind(const uint16_t port)
{
try
{
tcp::resolver resolver(_io_service);
tcp::resolver::query query(tcp::v4(), boost::asio::ip::host_name(), std::to_string(port));
tcp::resolver::iterator available_endpoint = resolver.resolve(query);
tcp::resolver::iterator end;
for (; available_endpoint != end; ++available_endpoint)
{
tcp::endpoint localhost = *available_endpoint;
_acceptor.open(localhost.protocol());
_acceptor.bind(localhost);
_address = localhost.address().to_string();
break;
}
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
return true;
}
bool BoostHttpServer::listen()
{
try
{
_acceptor.listen();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
return false;
}
return true;
}
HttpClientPtr BoostHttpServer::accept()
{
try
{
auto client_socket = std::make_shared<tcp::socket>(_io_service);
_acceptor.accept(*client_socket);
return std::make_unique<BoostHttpClient>(client_socket);
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
return nullptr;
}
}
// boosthttpclient.hpp
class BoostHttpClient : public HttpClient
{
public:
BoostHttpClient(std::shared_ptr<boost::asio::ip::tcp::socket> socket);
BoostHttpClient(const BoostHttpClient&) = delete;
BoostHttpClient& operator=(const BoostHttpClient&) = delete;
virtual bool write(const std::string& message) override;
virtual std::string getAddress() const override;
private:
boost::asio::ip::tcp::socket _socket;
const std::string _address;
};
// boosthttpclient.cpp
BoostHttpClient::BoostHttpClient(
std::shared_ptr<boost::asio::ip::tcp::socket> socket
)
: _socket(socket)
, _address(socket->remote_endpoint().address().to_string())
{
}
This is the application output when I start the server and use telnet localhost 8080 for example:
Listening to connections at 127.0.0.1 on port 8080
Got connection from 127.0.0.1
However, I want to spare the heap allocation, create client_socket on the stack and transfer the ownership of it to the BoostHttpClient with std::move.
// boosthttpserver.cpp
using tcp = boost::asio::ip::tcp;
...
HttpClientPtr BoostHttpServer::accept()
{
try
{
tcp::socket client_socket(_io_service);
_acceptor.accept(client_socket);
return std::make_unique<BoostHttpClient>(std::move(client_socket));
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
return nullptr;
}
}
// boosthttpclient.hpp
class BoostHttpClient : public HttpClient
{
public:
BoostHttpClient(boost::asio::ip::tcp::socket&& socket);
BoostHttpClient(const BoostHttpClient&) = delete;
BoostHttpClient& operator=(const BoostHttpClient&) = delete;
virtual bool write(const std::string& message) override;
virtual std::string getAddress() const override;
private:
boost::asio::ip::tcp::socket _socket;
const std::string _address;
};
// boosthttpclient.cpp
BoostHttpClient::BoostHttpClient(boost::asio::ip::tcp::socket&& socket)
: _socket(std::move(socket))
, _address(socket.remote_endpoint().address().to_string())
{
}
However, the socket is gone after the move. As if its destructor got called and got closed.
Listening to connections at 127.0.0.1 on port 8080
remote_endpoint: Bad file descriptor
failed to accept
I thought that it could be as easy as passing other non-copyable objects like std::thread or std::mutex as constructor parameters, but cleary there's something I don't understand what happens at std::move(client_socket). Why doesn't it get transferred and why does it closes itself?
I use Boost 1.64.0, gcc version 7.3.1 20180712 (Red Hat 7.3.1-6) and C++17 if that matters.
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.
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