How can I do async write and read using websockets from the Beast library? I have tried to adapted the synchronous write/read example provided in the Beast documentation here, but the code below does not behave as expected.
I expected the following output :
*launch application*
Written data ...
Received data : Hello world!
*Ctrl-C*
Closing application ...
I got this :
*launch application*
*Ctrl-C*
Closing application ...
Code :
#include <beast/core/to_string.hpp>
#include <beast/websocket.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <string>
/// Block until SIGINT or SIGTERM is received.
void sig_wait(beast::websocket::stream<boost::asio::ip::tcp::socket&>& ws)
{
boost::asio::io_service ios;
boost::asio::signal_set signals(ios, SIGINT, SIGTERM);
signals.async_wait(
[&](boost::system::error_code const&, int)
{
ws.close(beast::websocket::close_code::normal);
std::cout << "Closing application ..." << std::endl;
});
ios.run();
}
int main(int argc, char *argv[])
{
// Normal boost::asio setup
std::string const host = "echo.websocket.org";
boost::asio::io_service ios;
boost::asio::ip::tcp::resolver r{ios};
boost::asio::ip::tcp::socket sock{ios};
boost::asio::ip::tcp::resolver::iterator iter (r.resolve(boost::asio::ip::tcp::resolver::query{host, "80"}));
boost::asio::connect(sock,iter);
// WebSocket connect and send message
beast::websocket::stream<boost::asio::ip::tcp::socket&> ws{sock};
ws.handshake(host, "/");
ws.async_write(boost::asio::buffer(std::string("Hello world!")),
[&](beast::error_code const&)
{
std::cout << "Written data ..." << '\n';
}
);
// Register handle for async_read
beast::streambuf sb;
beast::websocket::opcode op;
ws.async_read(op,sb,
[&](beast::error_code const&)
{
std::cout << "Received data : " << to_string(sb.data()) << '\n';
}
);
sig_wait(ws);
}
Side note: I am fairly new to the Boost library in general, so I may have gotten some of the basics wrong ...
You must call io_service::run(), that's the blocking call that will animate the io_service.
There are now asynchronous WebSocket client examples that you can study or copy: http://www.boost.org/doc/libs/develop/libs/beast/doc/html/beast/examples.html
Here is an asynchronous client example which calls io_service::run() from main:
http://www.boost.org/doc/libs/develop/libs/beast/example/websocket/client/async/websocket_client_async.cpp
Related
After I:
created a tcp-socket
put it in listening
received an Incoming "message" from the Client
"processed" the incoming accomplice and closed the socket...
Whether that it is possible to put this socket on listening again? Experiment shows that no - the accept() function - throws an error.
Is it possible, somehow, to "reset" a closed socket to its original state? In order not to release the previously allocated memory for this socket and not to create a new socket, which in the end will also have to be deleted.
PS:
my_socket_p = new boost::asio::ip::tcp::socket(io_context);
my_acceptor.accept(my_socket_p );
The socket you put in listening mode is typically not the socket that you close when you're done handling a request.
Asio makes this distinction more explicit than in the underlying sockets API. I discussed this before here: Design rationale behind that separate acceptor class exists in ASIO (see also e.g. What does it mean to "open" an acceptor?).
Simple example of a typical server that accepts requests and echoes them back reversed:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
int main() {
boost::asio::io_context ioc;
// listen on port 7878
tcp::acceptor acc(ioc, {{}, 7878});
acc.listen();
while (true) {
tcp::socket conn = acc.accept();
auto peer = conn.remote_endpoint();
try {
std::string request;
read_until(conn, asio::dynamic_buffer(request), "\n");
reverse(begin(request), end(request) - 1); // reverse until line-end
write(conn, asio::buffer("reversed: " + request));
conn.close();
} catch(boost::system::system_error const& se) {
std::cerr << "Error with peer " << peer << ": " << se.code().message() << std::endl;
}
}
}
With example clients:
netcat 127.0.0.1 7878 <<< "Hello world!"
netcat 127.0.0.1 7878 <<< "Bye world!"
Prints
reversed: !dlrow olleH
reversed: !dlrow eyB
Re-using?
There are other overloads of accept that take a socket by reference, and yes they can be re-used:
tcp::socket conn(ioc); // reused
while (true) {
acc.accept(conn);
auto peer = conn.remote_endpoint();
With the rest of the code un-changed: Live On Coliru
More Typical
More typically code would be asynchronous and the "connection handling" would be in some other class, e.g. Session. In such cases conn would be moved into Session. This is also explicitly documented as proper use, e.g.:
Following the move, the moved-from object is in the same state as if constructed using the basic_socket(const executor_type&) constructor.
So, you could write the code as such:
tcp::socket conn(ioc); // reused
while (true) {
acc.accept(conn);
std::make_shared<Session>(std::move(conn))->run();
}
You can see it Live On Coliru again, although I'll include the far more idiomatic version using the move-accept handler overload of async_accept instead here:
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::tcp;
using boost::system::error_code;
struct Session : std::enable_shared_from_this<Session> {
Session(tcp::socket s) : conn_(std::move(s)) {}
void run() { do_reverse_echo(); }
private:
void do_reverse_echo() {
async_read_until( //
conn_, asio::dynamic_buffer(buffer_), "\n",
[this, self = shared_from_this()](error_code ec, size_t) {
if (ec) {
std::cerr << "Error with peer " << peer_ << ": " << ec.message() << std::endl;
return;
}
reverse(begin(buffer_), end(buffer_) - 1); // reverse until line-end
buffer_ = "reversed: " + buffer_;
async_write(conn_, asio::buffer(buffer_), asio::detached);
});
}
tcp::socket conn_;
tcp::endpoint peer_{conn_.remote_endpoint()};
std::string buffer_;
};
struct Listener {
Listener(asio::any_io_executor ex, uint16_t port) : acc(ex, {{}, port}) {
acc.listen();
accept_loop();
}
private:
tcp::acceptor acc;
void accept_loop() {
acc.async_accept([this](error_code ec, tcp::socket conn) {
if (ec) {
std::cerr << "Stopping listener: " << ec.message() << std::endl;
return;
}
accept_loop();
std::make_shared<Session>(std::move(conn))->run();
});
}
};
int main() {
boost::asio::io_context ioc;
Listener s(ioc.get_executor(), 7878);
ioc.run();
}
Still with the same output, obviously.
I'm trying to create an s-function (using C++ Boost library) for UDP communication.
Implementing the sender was fairly straightforward, 15 min job. I'm struggling to get the receiver to work.
I created the following in Visual Studio:
#define _WIN32_WINNT 0x0501
#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
#include <boost/asio.hpp>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>
#include <iostream>
#include <stdio.h>
typedef unsigned char UINT8;
typedef unsigned short UINT16;
using boost::asio::ip::udp;
using namespace std;
std::vector<char> receive_buffer;
void process_received_frame(const boost::system::error_code& error, size_t received_frame_size) {
if (error) {
cout << "Receive failed: " << error.message() << "\n";
return;
}
size_t ByteCount = 0;
std::cout << endl << "Received byte stream (Handler) [" << received_frame_size << "]: ";
for (std::vector<char>::const_iterator iter = receive_buffer.cbegin(); iter != receive_buffer.cend(); iter++)
{
ByteCount++;
printf("%02X ", (UINT8)*iter);
if (ByteCount == received_frame_size)
{
break;
}
}
std::cout << endl;
}
int main(int argc, char *argv[])
{
boost::asio::io_service io_service;
udp::socket socket(io_service);
udp::endpoint remote_endpoint = udp::endpoint(boost::asio::ip::address_v4::from_string("127.0.0.1"), 19001);
socket.open(udp::v4());
socket.bind(udp::endpoint(remote_endpoint));
receive_buffer.resize(255);
try
{
socket.async_receive_from(boost::asio::buffer(receive_buffer),
remote_endpoint,
boost::bind(&process_received_frame, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
catch (const std::exception& exp)
{
printf("%s\n", exp.what());
}
//io_service.poll();
io_service.run();
cout << "End";
std::cin.ignore();
}
I tried sending UDP to localhost:19001 from Simulink and was able to receive the UDP packets in Visual Studio. The handler (process_received_frame) gets called and everything seems to work, as expected.
But, given that, io_service::run() works in blocking mode, it pauses execution if there is nothing received on port 19001. So I tried using io_service::poll() (commented in the code above) instead. However, when I use poll(), it does not execute the handler. If I try to display the contents of 'receive_buffer' from main(), I get all 0s. Interestingly, when I single-step through the code for accessing the elements of 'receive_buffer' I do get the right values.
Not sure what is it that I'm doing wrong. Quite likely to be a school-boy-error.
When I convert this to an s-function for MATLAB-Simulink, it does the same thing - all zeros.
Any help would be much appreciated.
Cheers,
In your handler function, you need to call socket.async_receive_from at the end after processing the answer. io_service.run() returns when no more handler are in its processing queue.
See the example from boost doc here: udp sync server example
EDIT
Rereading your question/comment, I'm not sure what your expected output or behavior is.
If you're only expecting a single UDP frame, then maybe call io_service.run_one().
If you don't want run() to block your main thread, you need to launch another thread to call run(). Something like:
boost::asio::io_service io_service;
// Process handlers in a background thread.
boost::thread t(boost::bind(&io_service::run, &io_service));
...
io_service::run() is always a blocking call. Completion handlers can only be called from threads currently calling run(). The only time run() is going to return is when there is no more handlers in the queue (you stopped calling async_receive) or if you explicitly cancel the run() command by calling stop() or reset()
I'm trying to build an application that reads from the ftrace pipes at the debug fs.
It seems that when trying to read asynchronously from trace_pipe or
trace_pipe_raw using boost::asio API, the events waiting in pipe are being
processed and printed to screen by the async_read handle, but new events that arrive after the program started don't trigger the async_read handle.
Running the sample code below, i'm getting a print of all events waiting in queue but i don't get any print for new events that arrive later.
The same sample works perfectly if i'm trying to read from manually created pipe using mkfifo but doesn't work for the ftrace pipes.
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <string>
#include <iostream>
namespace asio = boost::asio;
#ifdef BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR
typedef asio::posix::stream_descriptor stream_descriptor;
#endif
class PipeReader
{
typedef std::shared_ptr<PipeReader> PipeReaderPtr;
typedef std::weak_ptr<PipeReader> PipeReaderWeakPtr;
public:
static PipeReaderWeakPtr Create(asio::io_service& io_service, const std::string& path);
void HandleRead(PipeReaderPtr me, const boost::system::error_code &error);
private:
PipeReader(asio::io_service& io_service, const std::string& path);
stream_descriptor m_pipe;
char buf[4096];
};
PipeReader::PipeReaderWeakPtr PipeReader::Create(asio::io_service& io_service, const std::string& path)
{
PipeReaderPtr ptr(new PipeReader(io_service, path));
ptr->m_pipe.async_read_some(boost::asio::buffer(ptr->buf),
boost::bind(&PipeReader::HandleRead,
ptr.get(),
ptr,
asio::placeholders::error));
return ptr;
}
PipeReader::PipeReader(asio::io_service& io_service, const std::string& path)
: m_pipe(io_service)
{
int dev = open(path.c_str(), O_RDWR);
if (dev == -1) {
std::cout << "failed to open path - " << path << std::endl;
}
else
{
m_pipe.assign(dev);
}
}
void PipeReader::HandleRead(PipeReaderPtr me, const boost::system::error_code &error)
{
if (!error) {
std::string str(me->buf);
std::cout << "got message: " << str << std::endl;
m_pipe.async_read_some(boost::asio::buffer(me->buf),
boost::bind(&PipeReader::HandleRead,
this,
me,
asio::placeholders::error));
}
else
{
std::cout << "got error - " << error.message() << std::endl;
}
}
int main()
{
boost::asio::io_service io_service;
boost::asio::io_service::work dummy(io_service);
PipeReader::Create(io_service, "/sys/kernel/debug/tracing/trace_pipe");
io_service.run();
return 0;
}
I found the problem. it was a bug in the implementation of ftrace that caused the epoll to hang.
The bug was fixed at kernel 3.16.
correspondence thread,
commit in git hub
I try to run an async network thread using boost::asio and boost::thread.
But the async_accept returns immediately with error code 125 - operation canceled...
attached I a minimal sample of the Problem:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
class Server{
public:
Server()
{ }
void listen(unsigned int port)
{
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), port);
boost::asio::ip::tcp::acceptor acceptor(m_io_service, endpoint);
std::cout << "Waiting for incomming connection on port: " << port << std::endl;
acceptor.async_accept(*m_stream.rdbuf(), boost::bind( &Server::handleAccept, this, boost::asio::placeholders::error, boost::ref( acceptor ) ) );
m_listenThread = new boost::thread(boost::bind(&boost::asio::io_service::run, &m_io_service));
}
void stop()
{
m_listenThread->join();
}
private:
void handleAccept(const boost::system::error_code& error, boost::asio::ip::tcp::acceptor& acceptor)
{
std::cout << "receiverd incomming connection" << std::endl;
if(error)
std::cout << "ERROR: " << error.message() << "(" << error.value() << ")" << std::endl;
}
boost::asio::io_service m_io_service;
boost::asio::ip::tcp::iostream m_stream;
boost::thread* m_listenThread;
};
int main(int argc, char *argv[])
{
Server server;
server.listen(10000);
while(1);
}
acceptor::async_accept returns immediately, scheduling a call of the handler when either there is an error or a connection is accepted (1)
the listen() function is returning, which is causing the destruction of the acceptor (2)
When an acceptor (or socket, or deadline_timer) is destroyed, all pending handlers are scheduled on the io_service with an error code of asio::error::operation_aborted. This is to satisfy the postconditions of the async_ functions (i.e., "the handler will be called exactly once, as if by io_service.post()") (3)
Therefore, at point (2), your handler is being scheduled - just before the code returns to the main loop.
To fix:
ensure that the acceptor survives until the handler has been called. This is standard practice in asio async programming. The examples on the boost website will help you to make sense of the (sparse) asio documentation.
Don't lose hope. It took me a long time to learn how to use asio properly, and to realise how fantastically powerful it is.
I'm trying to create a server that receives connections via domain sockets. I can start the server and I can see the socket being created on the filesystem. But whenever I try to connect to it via socat I get the following error:
2015/03/02 14:00:10 socat[62720] E connect(3, LEN=19 AF=1 "/var/tmp/rpc.sock", 19): Connection refused
This is my Asio code (only the .cpp files). Despite the post title I'm using the Boost-free version of Asio but I don't think that would be a problem.
namespace myapp {
DomainListener::DomainListener(const string& addr) : socket{this->service}, Listener{addr} {
remove(this->address.c_str());
stream_protocol::endpoint ep(this->address);
stream_protocol::acceptor acceptor(this->service, ep);
acceptor.async_accept(this->socket, ep, bind(&DomainListener::accept_callback, this, _1));
}
DomainListener::~DomainListener() {
this->service.stop();
remove(this->address.c_str());
}
void DomainListener::accept_callback(const error_code& ec) noexcept {
this->socket.async_read_some(asio::buffer(this->data), bind(&DomainListener::read_data, this, _1, _2));
}
void DomainListener::read_data(const error_code& ec, size_t length) noexcept {
//std::cerr << "AAA" << std::endl;
//std::cerr << this->data[0] << std::endl;
//std::cerr << "BBB" << std::endl;
}
}
Listener::Listener(const string& addr) : work{asio::io_service::work(this->service)} {
this->address = addr;
}
void Listener::listen() {
this->service.run();
}
Listener::~Listener() {
}
In the code that uses these classes I call listen() whenever I want to start listening to the socket for connections.
I've managed to get this to work with libuv and changed to Asio because I thought it would make for more readable code but I'm finding the documentation to be very ambiguous.
The issue is most likely the lifetime of the acceptor.
The acceptor is an automatic variable in the DomainListener constructor. When the DomainListener constructor completes, the acceptor is destroyed, causing the acceptor to close and cancel outstanding operations, such as the async_accept operations. Cancelled operations will be provided an error code of asio::error::operation_aborted and scheduled for deferred invocation within the io_service. Hence, there may not be an active listener when attempting to connect to the domain socket. For more details on the affects of IO object destruction, see this answer.
DomainListener::DomainListener(const string&) : /* ... */
{
// ...
stream_protocol::acceptor acceptor(...);
acceptor.async_accept(..., bind(accept_callback, ...));
} // acceptor destroyed, and accept_callback likely cancelled
To resolve this, consider extending the lifetime of the acceptor by making it a data member for DomainListener. Additionally, checking the error_code provided to asynchronous operations can provide more insight into the asynchronous call chains.
Here is a complete minimal example demonstrating using domain sockets with Asio.
#include <cstdio>
#include <iostream>
#include <boost/array.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
/// #brief server demonstrates using domain sockets to accept
/// and read from a connection.
class server
{
public:
server(
boost::asio::io_service& io_service,
const std::string& file)
: io_service_(io_service),
acceptor_(io_service_,
boost::asio::local::stream_protocol::endpoint(file)),
client_(io_service_)
{
std::cout << "start accepting connection" << std::endl;
acceptor_.async_accept(client_,
boost::bind(&server::handle_accept, this,
boost::asio::placeholders::error));
}
private:
void handle_accept(const boost::system::error_code& error)
{
std::cout << "handle_accept: " << error.message() << std::endl;
if (error) return;
std::cout << "start reading" << std::endl;
client_.async_read_some(boost::asio::buffer(buffer_),
boost::bind(&server::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void handle_read(
const boost::system::error_code& error,
std::size_t bytes_transferred)
{
std::cout << "handle_read: " << error.message() << std::endl;
if (error) return;
std::cout << "read: ";
std::cout.write(buffer_.begin(), bytes_transferred);
std::cout.flush();
}
private:
boost::asio::io_service& io_service_;
boost::asio::local::stream_protocol::acceptor acceptor_;
boost::asio::local::stream_protocol::socket client_;
std::array<char, 1024> buffer_;
};
int main(int argc, char* argv[])
{
if (argc != 2)
{
std::cerr << "Usage: <file>\n";
return 1;
}
// Remove file on startup and exit.
std::string file(argv[1]);
struct file_remover
{
file_remover(std::string file): file_(file) { std::remove(file.c_str()); }
~file_remover() { std::remove(file_.c_str()); }
std::string file_;
} remover(file);
// Create and run the server.
boost::asio::io_service io_service;
server s(io_service, file);
io_service.run();
}
Coliru does not have socat installed, so the following commands use OpenBSD netcat to write "asio domain socket example" to the domain socket:
export SOCKFILE=$PWD/example.sock
./a.out $SOCKFILE &
sleep 1
echo "asio domain socket example" | nc -U $SOCKFILE
Which outputs:
start accepting connection
handle_accept: Success
start reading
handle_read: Success
read: asio domain socket example