How to add additional parameter in existing constructor. C++ ASIO - c++

I need help on constructing the endpoint with 3 arguements. Currently "IP Address" & "Port" are there in the constructor. But i need to include an integer arguement to identify the connection. Ideally as mentioned below.
asio::ip::tcp::endpoint endpoint(asio::ip::make_address("127.0.0.1", ec), 7497, clientId = 0);
Could you please help whether i have to modify the base class or can i inherit the base class & add this arguement? if inheriting is easy, please guide me on that.
int main(int argc)
{
int clientId = 0;
asio::error_code ec;
// Create a "context" - essentially the platform specific interface
asio::io_context context;
// Get th addres of somewhere w wish to connect to
asio::ip::tcp::endpoint endpoint(asio::ip::make_address("127.0.0.1", ec), 7497);
// Create a socket, the context will deliver the implementation
asio::ip::tcp::socket socket(context);
// tell socket to try and connect
socket.connect(endpoint, ec);
if (!ec)
{
std::cout << "Conected!" << std::endl;
}
else
{
std::cout << "Failed to connect to address:\n" << ec.message() << std::endl;
}
if (socket.is_open())
system("pause");
return 0;
}
Inherited class:
#pragma once
#include <iostream>
#ifdef _WIN32
#define _WIN32_WINNT 0x0A00
#endif
#define ASIO_STANDALONE
#include <asio.hpp>
#include <asio/ts/buffer.hpp>
#include <asio/ts/internet.hpp>
// Get th addres of somewhere w wish to connect to
class clien : public asio::ip::tcp::endpoint, asio::ip::address
{
public:
typedef asio::ip::tcp::endpoint endpoint;
char asio::ip::address;
int clientId;
//clien(int cid);
clien(str ep, int p, int cid) : asio::ip::tcp::endpoint(ep), char asio::ip::address, int cid;
{
clientId = cid;
}
};

since the class overloading way look to be the easiest:
class clien : public asio::ip::tcp::endpoint {
// inherit endpoint (because it the class you want to change)
protected:
int __cid; // the id
public:
int clientId() const { return __cid; } // getter
void clientId(int cid) { __cid = cid; } // setter
clien(str ep, int p, int cid) :
asio::ip::tcp::endpoint(ep, p), // construct the actual endpoint
__cid(cid){} // set the cid
};
i'm not familiar with the library so some type (for the constructor) might be wrong.

You can make a new type using endpoint and id
struct MyEndpoint{
asio::ip::tcp::endpoint endpoint{};
int someId{};
}
Create the endpoint
auto const someId = 42;
auto myEndpoint = MyEndpoint{ .endpoint = { boost::asio::ip::tcp::v4 (), 7497 }, .someId = someId };
Now you can get the id and the endpoint
myEndpoint.someId; // To get the id
myEndpoint.endpoint; // To get the endpoint
Another idea is to make a pair of id and endpoint
auto const someId=42;
auto idAndEndpoint= std::make_pair(someId, asio::ip::tcp::endpoint{(asio::ip::make_address("127.0.0.1", ec), 7497)});
Now you can get the id and the endpoint
auto &[id, endpoint] = idAndEndpoint;

Related

UDP clients pool sending but not receiving

I am creating an udp client pool. The servers will be some other applications running in different computers, and they are suppoused to be alive from beginning. Using a configurable file (not important to problem in example so not included) one to several clients are created so they connect to those servers (1 to 1 relation) in a bidirectional way, sending and receiving.
Sending can be sync because it uses small messages and blocking there in not a problem, but receiving must be async, because answerback can arrive much later after sending.
In my test with only one sockect, it is able to send, but it is not receiving anything at all.
Q1: Where is the problem and how to fix it?
Q2: I also wonder if the use of iterators from std::vector in the async calls can be problematic at the time new connections are pushed into vector due to its rearangment in memory. This may be a problem?
Q3: I really does not understand why in all examples sender and receiver endpoints (endpoint1 and endpoint2 in example struct Socket) are different, couldn't they be the same?
My code is next:
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::udp;
class Pool
{
struct Socket {
std::string id;
udp::socket socket;
udp::endpoint endpoint1;
udp::endpoint endpoint2;
enum { max_length = 1024 };
std::array<char, max_length> data;
};
public:
void create(const std::string& id, const std::string& host, const std::string& port)
{
udp::resolver resolver(io_context);
sockets.emplace_back(Socket{ id, udp::socket{io_context, udp::v4()}, *resolver.resolve(udp::v4(), host, port).begin() });
receive(id);
}
void send(const std::string& id, const std::string& msg)
{
auto it = std::find_if(sockets.begin(), sockets.end(), [&](auto& socket) { return id == socket.id; });
if (it == sockets.end()) return;
it->data = std::array<char, Socket::max_length>{ 'h', 'e', 'l', 'l', 'o' };
auto bytes = it->socket.send_to(boost::asio::buffer(it->data, 5), it->endpoint1);
}
void receive(const std::string& id)
{
auto it = std::find_if(sockets.begin(), sockets.end(), [&](auto& socket) { return id == socket.id; });
if (it == sockets.end()) return;
it->socket.async_receive_from(
boost::asio::buffer(it->data, Socket::max_length),
it->endpoint2,
[this, id](boost::system::error_code error, std::size_t bytes) {
if (!error && bytes)
bool ok = true;//Call to whatever function
receive(id);
}
);
}
void poll()
{
io_context.poll();
}
private:
boost::asio::io_context io_context;
std::vector<Socket> sockets;
};
int main()
{
Pool clients;
clients.create("ID", "localhost", "55000");
while (true) {
clients.poll();
clients.send("ID", "x");
Sleep(5000);
}
}
Q1: Where is the problem and how to fix it?
You don't really bind to any port, and then you have multiple sockets all receiving unbound udp packets. Likely they're simply competing and something gets lost in the confusion.
Q2: can std::vector be problematic
Yes. Use a std::deque (stable iterator/references as long as you only push/pop at either end). Otherwise, consider a std::list or other node-based container.
In your case map<id, socket> seems more intuitive.
Actually, map<endpoint, peer> would be a lot more intuitive. Or... you could do without the peers entirely.
Q3: I really does not understand why in all examples sender and
receiver endpoints (endpoint1 and endpoint2 in example struct Socket)
are different, couldn't they be the same?
Yeah, they could be "the same" if you don't care about overwriting the original endpoint you had sent to.
Here's my simplified take. As others have said, it's not possible/useful to have many UDP sockets "listening" on the same endpoint. That is, provided that you even bound to an endpoint.
So my sample uses a single _socket with local endpoint :8765.
It can connect to many client endpoints - I chose to replace the id string with the endpoint itself for simplicity. Feel free to add a map<string, endpoint> for some translation.
See it Live On Coliru
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
#include <set>
using boost::asio::ip::udp;
using namespace std::chrono_literals;
class Pool {
public:
using Message = std::array<char, 1024>;
using Peers = std::set<udp::endpoint>;
using Id = udp::endpoint;
Pool() { receive_loop(); }
Id create(const std::string& host, const std::string& port)
{
auto ep = *udp::resolver(_io).resolve(udp::v4(), host, port).begin();
/*auto [it,ok] =*/_peers.emplace(ep);
return ep;
}
void send(Id id, const std::string& msg)
{
/*auto bytes =*/
_socket.send_to(boost::asio::buffer(msg), id);
}
void receive_loop()
{
_socket.async_receive_from(
boost::asio::buffer(_incoming), _incoming_ep,
[this](boost::system::error_code error, std::size_t bytes) {
if (!error && bytes)
{
if (_peers.contains(_incoming_ep)) {
std::cout << "Received: "
<< std::quoted(std::string_view(
_incoming.data(), bytes))
<< " from " << _incoming_ep << "\n";
} else {
std::cout << "Ignoring message from unknown peer "
<< _incoming_ep << "\n";
}
}
receive_loop();
});
}
void poll() { _io.poll(); }
private:
boost::asio::io_context _io;
udp::socket _socket{_io, udp::endpoint{udp::v4(), 8765}};
Message _incoming;
udp::endpoint _incoming_ep;
Peers _peers;
};
int main(int argc, char** argv) {
Pool pool;
std::vector<Pool::Id> peers;
for (auto port : std::vector(argv + 1, argv + argc)) {
peers.push_back(pool.create("localhost", port));
}
int message_number = 0;
while (peers.size()) {
pool.poll();
auto id = peers.at(rand() % peers.size());
pool.send(id, "Message #" + std::to_string(++message_number) + "\n");
std::this_thread::sleep_for(1s);
}
}
Live on my machine with some remotes simulated like
sort -R /etc/dictionaries-common/words | while read word; do sleep 5; echo "$word"; done | netcat -u -l -p 8787 -w 1000
Also sending bogus messages from an "other" endpoint to simulate stray/unknown messages.

How to use boost::beast, download a file no blocking and with responses

I have started with this example so won't post all the code. My objective is to download a large file without blocking my main thread. The second objective is to get notifications so I can update a progress bar. I do have the code working a couple of ways. First is to just ioc.run(); and let it go to work, I get the file downloaded. But I can not find anyway to start the session without blocking.
The second way I can make the calls down to http::async_read_some and the call works but I can not get a response that I can use. I don't know if there is a way to pass a lambda that captures.
The #if 0..#else..#endif switches the methods. I'm sure there is a simple way but I just can not see it. I'll clean up the code when I get it working, like setting the local file name. Thanks.
std::size_t on_read_some(boost::system::error_code ec, std::size_t bytes_transferred)
{
if (ec);//deal with it...
if (!bValidConnection) {
std::string_view view((const char*)buffer_.data().data(), bytes_transferred);
auto pos = view.find("Content-Length:");
if (pos == std::string_view::npos)
;//error
file_size = std::stoi(view.substr(pos+sizeof("Content-Length:")).data());
if (!file_size)
;//error
bValidConnection = true;
}
else {
file_pos += bytes_transferred;
response_call(ec, file_pos);
}
#if 0
std::cout << "in on_read_some caller\n";
http::async_read_some(stream_, buffer_, file_parser_, std::bind(
response_call,
std::placeholders::_1,
std::placeholders::_2));
#else
std::cout << "in on_read_some inner\n";
http::async_read_some(stream_, buffer_, file_parser_, std::bind(
&session::on_read_some,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
#endif
return buffer_.size();
}
The main, messy but.....
struct lambda_type {
bool bDone = false;
void operator ()(const boost::system::error_code ec, std::size_t bytes_transferred) {
;
}
};
int main(int argc, char** argv)
{
auto const host = "reserveanalyst.com";
auto const port = "443";
auto const target = "/downloads/demo.msi";
int version = argc == 5 && !std::strcmp("1.0", argv[4]) ? 10 : 11;
boost::asio::io_context ioc;
ssl::context ctx{ ssl::context::sslv23_client };
load_root_certificates(ctx);
//ctx.load_verify_file("ca.pem");
auto so = std::make_shared<session>(ioc, ctx);
so->run(host, port, target, version);
bool bDone = false;
auto const lambda = [](const boost::system::error_code ec, std::size_t bytes_transferred) {
std::cout << "data lambda bytes: " << bytes_transferred << " er: " << ec.message() << std::endl;
};
lambda_type lambda2;
so->set_response_call(lambda);
ioc.run();
std::cout << "not in ioc.run()!!!!!!!!" << std::endl;
so->async_read_some(lambda);
//pseudo message pump when working.........
for (;;) {
std::this_thread::sleep_for(250ms);
std::cout << "time" << std::endl;
}
return EXIT_SUCCESS;
}
And stuff I've added to the class session
class session : public std::enable_shared_from_this<session>
{
using response_call_type = void(*)(boost::system::error_code ec, std::size_t bytes_transferred);
http::response_parser<http::file_body> file_parser_;
response_call_type response_call;
//
bool bValidConnection = false;
std::size_t file_pos = 0;
std::size_t file_size = 0;
public:
auto& get_result() { return res_; }
auto& get_buffer() { return buffer_; }
void set_response_call(response_call_type the_call) { response_call = the_call; }
I've updated this as I finally put it to use and I wanted the old method where I could download to a file or a string. Link to how asio works, great talk.
CppCon 2016 Michael Caisse Asynchronous IO with BoostAsio
As for my misunderstanding of how to pass a lambda, here is Adam Nevraumont's answer
There are two ways to compile this using a type to select the method. Both are shown at the beginning of main. You can construct either a file downloader or string downloader by selecting the type of beast parser. The parsers don't have the same constructs so an if constexpr compile time conditions are used. And I checked, a release build of the downloader is about 1K so pretty light weight for what it does. In the case of a small string you don't have to handle the call backs. either pass an empty lambda or add the likes of:
if(response_call)
response_call(resp_ok, test);
This looks to be a pretty clean way to get the job done so I've updated this post as of 11/27/2202.
The code:
//
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//------------------------------------------------------------------------------
//
// Example: HTTP SSL client, synchronous, usable in a thread with a message pump
// Added code to use from a message pump
// Also useable as body to a file download, or body to string
//
//------------------------------------------------------------------------------
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/ssl.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/ssl/error.hpp>
#include <boost/asio/ssl/stream.hpp>
#include <cstdlib>
#include <iostream>
#include <string>
#include <fstream>
//the boost shipped certificates
#include <boost/../libs/beast/example/common/root_certificates.hpp>
//TODO add your ssl libs as you would like
#ifdef _M_IX86
#pragma comment(lib, "libcrypto.lib")
#pragma comment(lib, "libssl.lib")
#elif _M_X64
#pragma comment(lib, "libcrypto-3-x64.lib")
#pragma comment(lib, "libssl-3-x64.lib")
#endif
namespace downloader {
namespace beast = boost::beast; // from <boost/beast.hpp>
namespace http = beast::http; // from <boost/beast/http.hpp>
namespace net = boost::asio; // from <boost/asio.hpp>
namespace ssl = net::ssl; // from <boost/asio/ssl.hpp>
using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
//specialization if using < c++17; see both 'if constexpr' below.
//this is not needed otherwise
//namespace detail {
// template<typename Type>
// void open_file(http::parser < false, Type>& p, const char* name, boost::system::error_code& file_open_ec) { }
// template<>
// void open_file(http::parser<false, http::file_body>& p, const char* name, boost::system::error_code& file_open_ec) {
// p.get().body().open(name, boost::beast::file_mode::write, file_open_ec);
// }
// template<typename Type>
// std::string get_string(http::parser < false, Type>& p) { return std::string{}; }
// template<>
// std::string get_string(http::parser<false, http::string_body>& p) {
// return p.get().body();
// }
//} //namespace detail
enum responses {
resp_null,
resp_ok,
resp_done,
resp_error,
};
using response_call_type = std::function< void(responses, std::size_t)>;
template<typename ParserType>
struct download {
//as these can be set with array initialization
const char* target_ = "/";
const char* filename_ = "test.txt";
const char* host_ = "lakeweb.net";
std::string body_;
using response_call_type = std::function< void(responses, std::size_t)>;
response_call_type response_call;
boost::asio::io_context ioc_;
ssl::context ctx_{ ssl::context::sslv23_client };
ssl::stream<tcp::socket> stream_{ ioc_, ctx_ };
tcp::resolver resolver_{ ioc_ };
boost::beast::flat_buffer buffer_;
uint64_t file_size_{};
int version{ 11 };
void set_response_call(response_call_type the_call) { response_call = the_call; }
uint64_t get_file_size() { return file_size_; }
void stop() { ioc_.stop(); }
bool stopped() { return ioc_.stopped(); }
std::string get_body() { return std::move(body_); }
void run() {
try {
// TODO should have a timer in case of a hang
load_root_certificates(ctx_);
// Set SNI Hostname (many hosts need this to handshake successfully)
if (!SSL_set_tlsext_host_name(stream_.native_handle(), host_)) {
boost::system::error_code ec{ static_cast<int>(::ERR_get_error()), boost::asio::error::get_ssl_category() };
throw boost::system::system_error{ ec };
}
//TODO resolve is depreciated, use endpoint
auto const results = resolver_.resolve(host_, "443");
boost::asio::connect(stream_.next_layer(), results.begin(), results.end());
stream_.handshake(ssl::stream_base::client);
// Set up an HTTP GET request message
http::request<http::string_body> req{ http::verb::get, target_, version };
req.set(http::field::host, host_);
req.set(http::field::user_agent, "mY aGENT");
// Send the HTTP request to the remote host
http::write(stream_, req);
// Read the header
boost::system::error_code file_open_ec;
http::parser<false, ParserType> p;
p.body_limit((std::numeric_limits<std::uint32_t>::max)());
//detail::open_file(p, filename_, file_open_ec);
//or => c++17
if constexpr (std::is_same_v<ParserType, http::file_body>)
p.get().body().open(filename_, boost::beast::file_mode::write, file_open_ec);
http::read_header(stream_, buffer_, p);
file_size_ = p.content_length().has_value() ? p.content_length().value() : 0;
//Read the body
uint64_t test{};
boost::system::error_code rec;
for (;;) {
test += http::read_some(stream_, buffer_, p, rec);
if (test >= file_size_) {
response_call(resp_done, 0);
break;
}
response_call(resp_ok, test);
}
// Gracefully close the stream
boost::system::error_code ec;
stream_.shutdown(ec);
if (ec == boost::asio::error::eof)
{
// Rationale:
// http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error
ec.assign(0, ec.category());
}
if (ec)
throw boost::system::system_error{ ec };
//value = detail::get_string(p);
//or => c++17
if constexpr (std::is_same_v<ParserType, http::string_body>)
body_ = p.get().body();
}
catch (std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
response_call(resp_error, -1);
}
ioc_.stop();
}
};
}//namespace downloadns
//comment to test with string body
#define THE_FILE_BODY_TEST
int main(int argc, char** argv)
{
using namespace downloader;
#ifdef THE_FILE_BODY_TEST
download<http::file_body> dl{"/Nasiri%20Abarbekouh_Mahdi.pdf", "test.pdf"};
#else //string body test
download<http::string_body> dl{ "/robots.txt" };
#endif
responses dl_response{ resp_null };
size_t cur_size{};
auto static const lambda = [&dl_response, &dl, &cur_size](responses response, std::size_t bytes_transferred) {
if ((dl_response = response) == resp_ok) {
cur_size += bytes_transferred;
size_t sizes = dl.get_file_size() - cur_size;//because size is what is left
//drive your progress bar from here in a GUI app
}
};
dl.set_response_call(lambda);
std::thread thread{ [&dl]() { dl.run(); } };
//thread has started, now the pseudo message pump
bool quit = false; //true: as if a cancel button was pushed; won't finish download
for (int i = 0; ; ++i) {
switch (dl_response) { //ad hoc as if messaged
case resp_ok:
std::cout << "from sendmessage: " << cur_size << std::endl;
dl_response = resp_null;
break;
case resp_done:
std::cout << "from sendmessage: done" << std::endl;
dl_response = resp_null;
break;
case resp_error:
std::cout << "from sendmessage: error" << std::endl;
dl_response = resp_null;
}//switch
if (!(i % 5))
std::cout << "in message pump, stopped: " << std::boolalpha << dl.stopped() << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (quit && i == 10) //the cancel message
dl.stop();
if (!(i % 20) && dl.stopped()) {//dl job was quit or error or finished
std::cout << "dl is stopped" << std::endl;
break;
}
}
#ifdef THE_FILE_BODY_TEST
std::cout << "file written named: 'test.txt'" << std::endl;
#else
std::string res = dl.get_body();
std::cout << "body retrieved:\n" << res << std::endl;
#endif
if (thread.joinable())//in the case a thread was never started
thread.join();
std::cout << "exiting, program all done" << std::endl;
return EXIT_SUCCESS;
}
I strongly recommend against using the low-level [async_]read_some function instead of using http::[async_]read as intended with http::response_parser<http::buffer_body>
I do have an example of that - which is a little bit complicated by the fact that it also uses Boost Process to concurrently decompress the body data, but regardless it should show you how to use it:
How to read data from Internet using muli-threading with connecting only once?
I guess I could tailor it to your specific example given more complete code, but perhaps the above is good enough? Also see "Relay an HTTP message" in libs/beast/example/doc/http_examples.hpp which I used as "inspiration".
Caution: the buffer arithmetic is not intuitive. I think this is unfortunate and should not have been necessary, so pay (very) close attention to these samples for exactly how that's done.

Websocket client in C++ using boost::Beast - throwing error at write operation

Below is a piece of code which I am trying hard to make it successful. I use the reference code available at git hub https://www.boost.org/doc/libs/1_70_0/libs/beast/doc/html/beast/quick_start/websocket_client.html.
The problem is when I call the connect method and do write operation in same function it works, however it wont work if I put that in different function.
I am new to boost and shared_pointer, please excuse if I am not making any sense.
// Sends a WebSocket message and prints the response
class CWebSocket_Sync : public std::enable_shared_from_this<CWebSocket_Sync>
{
tcp::resolver resolver_;
websocket::stream<tcp::socket> ws_;
boost::beast::multi_buffer buffer_;
std::string host_;
std::string text_;
public:
// Resolver and socket require an io_context
explicit
CWebSocket_Sync(boost::asio::io_context& ioc)
: resolver_(ioc)
, ws_(ioc)
{
}
void
connect(
char const* host,
char const* port,
char const* text)
{
// Save these for later
host_ = host;
text_ = text;
// Look up the domain name
auto const results = resolver_.resolve(host, port);
// Make the connection on the IP address we get from a lookup
auto ep = net::connect(ws_.next_layer(), results);
// Update the host_ string. This will provide the value of the
// Host HTTP header during the WebSocket handshake.
// See https://tools.ietf.org/html/rfc7230#section-5.4
host_ += ':' + std::to_string(ep.port());
// Perform the websocket handshake
ws_.handshake(host_, "/");
//ws_.write(net::buffer(std::string(text)));
//// This buffer will hold the incoming message
//beast::flat_buffer buffer;
//// Read a message into our buffer
//ws_.read(buffer);
}
void ServerCommand(char const* text)
{
ws_.write(net::buffer(std::string(text))); // <-- this line throw memory error
// This buffer will hold the incoming message
beast::flat_buffer buffer;
// Read a message into our buffer
ws_.read(buffer);
// The make_printable() function helps print a ConstBufferSequence
std::cout << beast::make_printable(buffer.data()) << std::endl;
}
void CloseConnection()
{
// Close the WebSocket connection
ws_.close(websocket::close_code::normal);
}
};
int main(int argc, char** argv)
{
auto const host = "127.0.0.1";
auto const port = "7011";
auto const loginCmd = "login"
boost::asio::io_context ioc;
std::make_shared<CWebSocket_Sync>(ioc)->connect(host, port, loginCmd);
std::make_shared<CWebSocket_Sync>(ioc)->ServerCommand(loginCmd);
std::make_shared<CWebSocket_Sync>(ioc)->CloseConnection();
return EXIT_SUCCESS;
}
std::make_shared<CWebSocket_Sync>(ioc)->connect(host, port);
std::make_shared<CWebSocket_Sync>(ioc)->ServerCommand(loginCmd);
std::make_shared<CWebSocket_Sync>(ioc)->CloseConnection();
Each line creates a new client (make_shared<CWebSocket_Sync>) and runs a single step on it. What you probably wanted:
auto client = std::make_shared<CWebSocket_Sync>(ioc);
client->connect(host, port);
client->ServerCommand(loginCmd);
client->CloseConnection();
Indeed this works:
#include <boost/beast.hpp>
#include <boost/beast/websocket.hpp>
#include <memory>
#include <iostream>
namespace net = boost::asio;
namespace beast = boost::beast;
namespace websocket = beast::websocket;
using net::ip::tcp;
// Sends a WebSocket message and prints the response
class CWebSocket_Sync : public std::enable_shared_from_this<CWebSocket_Sync> {
tcp::resolver resolver_;
websocket::stream<tcp::socket> ws_;
boost::beast::multi_buffer buffer_;
std::string host_;
public:
// Resolver and socket require an io_context
explicit CWebSocket_Sync(boost::asio::io_context& ioc)
: resolver_(ioc), ws_(ioc) { }
void connect(char const* host, char const* port) {
// Save these for later
host_ = host;
// Look up the domain name
auto const results = resolver_.resolve(host, port);
// Make the connection on the IP address we get from a lookup
auto ep = net::connect(ws_.next_layer(), results);
// Update the host_ string. This will provide the value of the
// Host HTTP header during the WebSocket handshake.
// See https://tools.ietf.org/html/rfc7230#section-5.4
host_ += ':' + std::to_string(ep.port());
// Perform the websocket handshake
ws_.handshake(host_, "/");
}
void ServerCommand(char const* text) {
ws_.write(net::buffer(std::string(text))); // <-- this line throw memory error
beast::flat_buffer buffer;
ws_.read(buffer);
std::cout << beast::make_printable(buffer.data()) << std::endl;
}
void CloseConnection() {
ws_.close(websocket::close_code::normal);
}
};
int main() {
auto const host = "127.0.0.1";
auto const port = "7011";
auto const loginCmd = "login";
boost::asio::io_context ioc;
auto client = std::make_shared<CWebSocket_Sync>(ioc);
client->connect(host, port);
client->ServerCommand(loginCmd);
client->CloseConnection();
}
SIMPLIFY
However, since nothing uses the shared lifetime and you're not using asynchronous calls anywhere, why not simply:
Live On Coliru
class CWebSocket_Sync {
and later:
CWebSocket_Sync client(ioc);
client.connect(host, port);
client.ServerCommand(loginCmd);
client.CloseConnection();

Unix domain sockets with Poco::Net::TCPServer

I created a TCPServer using the Poco::Net::TCPServer framework, that uses a unix domain socket and it seems to work. However if I close the server and start it again I get this error:
Net Exception: Address already in use: /tmp/app.SocketTest
What is the right way to deal with this error?
Are the TCPServerConnections, TCPServerConnectionFactory and sockets
automatically cleaned-up or do I need to implement their destructors or destroy them manually?
EDIT
I have two questions here. The first is answered by using remove() on the socket-file. The other question is, if the clean-up in the Poco::Net::TCPServer framework is automatic or if it has to be manually implemented to prevent memory-leak?
Here is the code for the TCPServer:
#include "Poco/Util/ServerApplication.h"
#include "Poco/Net/TCPServer.h"
#include "Poco/Net/TCPServerConnection.h"
#include "Poco/Net/TCPServerConnectionFactory.h"
#include "Poco/Util/Option.h"
#include "Poco/Util/OptionSet.h"
#include "Poco/Util/HelpFormatter.h"
#include "Poco/Net/StreamSocket.h"
#include "Poco/Net/ServerSocket.h"
#include "Poco/Net/SocketAddress.h"
#include "Poco/File.h"
#include <fstream>
#include <iostream>
using Poco::Net::ServerSocket;
using Poco::Net::StreamSocket;
using Poco::Net::TCPServer;
using Poco::Net::TCPServerConnection;
using Poco::Net::TCPServerConnectionFactory;
using Poco::Net::SocketAddress;
using Poco::Util::ServerApplication;
using Poco::Util::Option;
using Poco::Util::OptionSet;
using Poco::Util::HelpFormatter;
class UnixSocketServerConnection: public TCPServerConnection
/// This class handles all client connections.
{
public:
UnixSocketServerConnection(const StreamSocket& s):
TCPServerConnection(s)
{
}
void run()
{
try
{
/*char buffer[1024];
int n = 1;
while (n > 0)
{
n = socket().receiveBytes(buffer, sizeof(buffer));
EchoBack(buffer);
}*/
std::string message;
char buffer[1024];
int n = 1;
while (n > 0)
{
n = socket().receiveBytes(buffer, sizeof(buffer));
buffer[n] = '\0';
message += buffer;
if(sizeof(buffer) > n && message != "")
{
EchoBack(message);
message = "";
}
}
}
catch (Poco::Exception& exc)
{
std::cerr << "Error: " << exc.displayText() << std::endl;
}
std::cout << "Disconnected." << std::endl;
}
private:
inline void EchoBack(std::string message)
{
std::cout << "Message: " << message << std::endl;
socket().sendBytes(message.data(), message.length());
}
};
class UnixSocketServerConnectionFactory: public TCPServerConnectionFactory
/// A factory
{
public:
UnixSocketServerConnectionFactory()
{
}
TCPServerConnection* createConnection(const StreamSocket& socket)
{
std::cout << "Got new connection." << std::endl;
return new UnixSocketServerConnection(socket);
}
private:
};
class UnixSocketServer: public Poco::Util::ServerApplication
/// The main application class.
{
public:
UnixSocketServer(): _helpRequested(false)
{
}
~UnixSocketServer()
{
}
protected:
void initialize(Application& self)
{
loadConfiguration(); // load default configuration files, if present
ServerApplication::initialize(self);
}
void uninitialize()
{
ServerApplication::uninitialize();
}
void defineOptions(OptionSet& options)
{
ServerApplication::defineOptions(options);
options.addOption(
Option("help", "h", "display help information on command line arguments")
.required(false)
.repeatable(false));
}
void handleOption(const std::string& name, const std::string& value)
{
ServerApplication::handleOption(name, value);
if (name == "help")
_helpRequested = true;
}
void displayHelp()
{
HelpFormatter helpFormatter(options());
helpFormatter.setCommand(commandName());
helpFormatter.setUsage("OPTIONS");
helpFormatter.setHeader("A server application to test unix domain sockets.");
helpFormatter.format(std::cout);
}
int main(const std::vector<std::string>& args)
{
if (_helpRequested)
{
displayHelp();
}
else
{
// set-up unix domain socket
Poco::File socketFile("/tmp/app.SocketTest");
SocketAddress unixSocket(SocketAddress::UNIX_LOCAL, socketFile.path());
// set-up a server socket
ServerSocket svs(unixSocket);
// set-up a TCPServer instance
TCPServer srv(new UnixSocketServerConnectionFactory, svs);
// start the TCPServer
srv.start();
// wait for CTRL-C or kill
waitForTerminationRequest();
// Stop the TCPServer
srv.stop();
}
return Application::EXIT_OK;
}
private:
bool _helpRequested;
};
int main(int argc, char **argv) {
UnixSocketServer app;
return app.run(argc, argv);
}
You don't need to worry about deallocating memory. All is done by library.
TCPServer srv(new UnixSocketServerConnectionFactory, svs);
^^^
Instance of UnixSocketServerConnectionFactory is deleted by TCPServer according to poco ref
The server takes ownership of the TCPServerConnectionFactory and
deletes it when it's no longer needed.
TCPServerConnection* createConnection(const StreamSocket& socket)
{
std::cout << "Got new connection." << std::endl;
return new UnixSocketServerConnection(socket);
^^^
}
instances of UnixSocketServerConnection are deleted by Poco library code as well:
As soon as the run() method returns, the server connection object is
destroyed and the connection is automatically closed.
The problem with Poco::File was that the destructor of Poco::File cannot remove file, you have to do it explicitly by remove method.

RPC failure code 14 in our simple GRPC example program

We've made good progress in getting GRPC running under RHEL 7.
Our application has one rather complicated structure with three levels of nesting with the outer level implementing a "oneof" keyword.
We find that all our other structures run fine, but this one gives us an RPC failure with code=14.
We've simplified this part of the application as much as possible so it can hopefully be recompiled and run easily.
Here's the .proto file, updated to accommodate Uli's question:
syntax = "proto3";
option java_multiple_files = true;
option java_package = "io.grpc.examples.debug";
option java_outer_classname = "DebugProto";
option objc_class_prefix = "DEBUG";
package DEBUGpackage;
service DEBUGservice {
rpc DEBUG_val_container_get (input_int32_request) returns (outer_container) {}
}
message input_int32_request {
int32 ival = 1;
}
message inner_container {
repeated uint32 val_array = 1;
}
message middle_container {
inner_container vac = 1;
}
message other_container {
int32 other_val = 1;
}
message outer_container {
oneof reply {
middle_container r1 = 1;
other_container r2 = 2;
}
}
(Please note that the java lines in this prototype code are just in there because they are in the GRPC website examples. Our code is entirely C++, with no java. Don't know if that means we can do without some of these "option java..." lines).
Here's our client source code:
#include <iostream>
#include <memory>
#include <string>
#include <grpc++/grpc++.h>
#include <grpc/support/log.h>
#include <thread>
#include <unistd.h>
#include "debug.grpc.pb.h"
using grpc::Channel;
using grpc::ClientAsyncResponseReader;
using grpc::ClientContext;
using grpc::CompletionQueue;
using grpc::Status;
using DEBUGpackage::input_int32_request;
using DEBUGpackage::inner_container;
using DEBUGpackage::middle_container;
using DEBUGpackage::outer_container;
using DEBUGpackage::DEBUGservice;
class DEBUGClient {
public:
explicit DEBUGClient(std::shared_ptr<Channel> channel)
: stub_(DEBUGservice::NewStub(channel)) {}
void DEBUG_val_container_get() {
std::cout << "in DEBUG_val_container_get" << std::endl;
// Data we are sending to the server
input_int32_request val;
val.set_ival(0);
AsyncClientCall* call = new AsyncClientCall;
call->response_reader = stub_->AsyncDEBUG_val_container_get(&call->context, val, &cq_);
call->response_reader->Finish(&call->reply_, &call->status, (void*)call);
}
void AsyncCompleteRpc() {
void* got_tag;
bool ok = false;
while (cq_.Next(&got_tag, &ok)) {
AsyncClientCall* call = static_cast<AsyncClientCall*>(got_tag);
GPR_ASSERT(ok);
if (call->status.ok()) {
if (call->reply_.has_r1()) {
std::cout << call << " DEBUG received: "
<< call->reply_.r1().vac().val_array(0) << std::endl;
}
}
else {
std::cout << call << " RPC failed" << std::endl;
std::cout << " RPC failure code = " << call->status.error_code() << std::endl;
std::cout << " RPC failure message = " << call->status.error_message() << std::endl;
}
delete call;
}
}
private:
struct AsyncClientCall {
outer_container reply_;
ClientContext context;
Status status;
std::unique_ptr<ClientAsyncResponseReader<outer_container>> response_reader;
};
std::unique_ptr<DEBUGservice::Stub> stub_;
CompletionQueue cq_;
};
int main(int argc, char** argv) {
DEBUGClient DEBUG0(grpc::CreateChannel("172.16.17.46:50050", grpc::InsecureChannelCredentials()));
std::thread thread0_ = std::thread(&DEBUGClient::AsyncCompleteRpc, &DEBUG0);
DEBUG0.DEBUG_val_container_get();
sleep(1);
std::cout << "Press control-c to quit" << std::endl << std::endl;
thread0_.join(); //blocks forever
return 0;
}
And, here's our server source code:
#include <memory>
#include <iostream>
#include <string>
#include <thread>
#include <grpc++/grpc++.h>
#include <grpc/support/log.h>
#include "debug.grpc.pb.h"
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
using grpc::Server;
using grpc::ServerAsyncResponseWriter;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::ServerCompletionQueue;
using grpc::Status;
using DEBUGpackage::inner_container;
using DEBUGpackage::input_int32_request;
using DEBUGpackage::middle_container;
using DEBUGpackage::outer_container;
using DEBUGpackage::DEBUGservice;
std::string save_server_address;
class ServerImpl final {
public:
~ServerImpl() {
server_->Shutdown();
cq_->Shutdown();
}
void Run() {
std::string server_address("0.0.0.0:50050");
ServerBuilder builder;
builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
builder.RegisterService(&service_);
cq_ = builder.AddCompletionQueue();
server_ = builder.BuildAndStart();
std::cout << "Server listening on " << server_address << std::endl;
save_server_address = server_address;
HandleRpcs();
}
private:
class CallData {
public:
virtual void Proceed() = 0;
};
class DebugGetCallData final : public CallData{
public:
DebugGetCallData(DEBUGservice::AsyncService* service, ServerCompletionQueue* cq)
: service_(service), cq_(cq), responder_(&ctx_), status_(CREATE) {
Proceed();
}
void Proceed() {
if (status_ == CREATE) {
status_ = PROCESS;
service_->RequestDEBUG_val_container_get(&ctx_, &request_, &responder_, cq_, cq_, this);
} else if (status_ == PROCESS) {
new DebugGetCallData(service_, cq_);
char *portchar;
portchar = (char *) save_server_address.c_str();
long cq_addr = (long) cq_;
int cq_addr32 = (int) (cq_addr & 0xfffffff);
srand(cq_addr32);
fprintf(stderr, "%s task started\n", portchar); fflush(stderr);
unsigned int return_val = 10;
inner_container ic;
ic.add_val_array(return_val);
middle_container reply_temp;
reply_temp.set_allocated_vac(&ic);
reply_.set_allocated_r1(&reply_temp);
fprintf(stderr, "%s %s task done\n", portchar, "val_container_get"); fflush(stderr);
status_ = FINISH;
responder_.Finish(reply_, Status::OK, this);
} else {
GPR_ASSERT(status_ == FINISH);
}
}
private:
DEBUGservice::AsyncService* service_;
ServerCompletionQueue* cq_;
ServerContext ctx_;
input_int32_request request_;
outer_container reply_;
ServerAsyncResponseWriter<outer_container> responder_;
enum CallStatus { CREATE, PROCESS, FINISH };
CallStatus status_;
};
void HandleRpcs() {
new DebugGetCallData(&service_, cq_.get());
void* tag;
bool ok;
while (true) {
GPR_ASSERT(cq_->Next(&tag, &ok));
GPR_ASSERT(ok);
static_cast<CallData*>(tag)->Proceed();
}
}
std::unique_ptr<ServerCompletionQueue> cq_;
DEBUGservice::AsyncService service_;
std::unique_ptr<Server> server_;
};
int main() {
ServerImpl server;
server.Run();
return 0;
}
The output when I run it looks like this:
[fossum#netsres46 debug]$ DEBUG_client2
in DEBUG_val_container_get
0xb73ff0 RPC failed
RPC failure code = 14
RPC failure message = Endpoint read failed
Press control-c to quit
We ran the server under gdb, and found a place in the generated
file "debug.pb.cc" where if we just comment out one line, it all starts working.
Here's the pertinent piece of the generated file "debug.pb.cc":
middle_container::~middle_container() {
// ##protoc_insertion_point(destructor:DEBUGpackage.middle_container)
SharedDtor();
}
void middle_container::SharedDtor() {
if (this != internal_default_instance()) {
delete vac_; // comment out this one line, to make the problem go away
}
}
The "delete vac_" line appears to be an attempt to delete storage that either has already been deleted, or is about to be deleted somewhere else. Please, can someone look into this? [The files below are still the files we use to generate this code, and to debug the problem to this point]
I have no idea whether I've uncovered a bug in GRPC, or whether I've coded something wrong.
The issue is that you are allocated middle_container reply_tmp on the stack in your server. As a result it gets destructed as soon as you pass out of the scope. At that time, you have called Finish but not yet waited for its result. Since this is an async server, the data must remain alive until you've received the tag back for it. This is why manually editing your destructor works in your case; you're basically nullifying the destructor (and leaking memory as a result).