C++ websocket client without receive callback - c++

We've a WebSocket server and clients in C#. The server is designed to slow down depending on how fast/slow the client reads and processes the messages. The C# clients reads one message at a time.
I'm looking to write a client in c++ and all the libraries I looked so far have a message handler or callback mechanism for receiving message from server.
This would mean that the client is receiving messages continuously, queue it and the client reads from the queue. This is not the behavior we're looking for. We require the client to read a message and process it and once the processing is complete, read the next message. Is there any library available we could use to achieve this?
I've so far checked cpprestsdk, websocketpp, libwebsocket

you can use Boost.ASIO Library.
I made a server that works with Websocket and multiple connections. Each connection uses an asynchronous way to receive data which will be handled before receiving the next message:
/**
* This method is used to read the incoming message on the WebSocket
* and handled it before reading the other message.
*/
void wscnx::read()
{
if (!is_ssl && !m_ws->is_open())
return;
else if (is_ssl && !m_wss->is_open())
return;
auto f_read = [self{shared_from_this()}](const boost::system::error_code &ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
// This indicates that the session was closed
if (ec == websocket::error::closed)
{
self->close(beast::websocket::close_code::normal, ec);
return;
}
if (ec)
{
self->close(beast::websocket::close_code::abnormal, ec);
return;
}
std::string data = beast::buffers_to_string(self->m_rx_buffer.data());
self->m_rx_buffer.consume(bytes_transferred);
if (!self->m_server.expired())
{
std::string_view vdata(data.c_str());
/*************************************
Here is where the datas are handled
**************************************/
self->m_server.lock()->on_data_rx(self->m_cnx_info.id, vdata, self->cnx_info());
}
/*************************************
Read the next message in the buffer.
**************************************/
self->read();
};//lambda
if (!is_ssl)
m_ws->async_read(m_rx_buffer, f_read);
else
m_wss->async_read(m_rx_buffer, f_read);
}
In this sample the connection could be plain or secure, using SSL, both of them are using a lambda function to receive the data.
I tested this service with a JS application, on the browser and there is no problem with the sequence to attend each message.

Related

How to send websocket message while waiting to receive?

My goal is to register to a websocket service to get real-time company quotations.
So I based my code on the following example, by mostly calling (again) async_read, once we receive a quotation to accept futures quotation:
https://www.boost.org/doc/libs/master/libs/beast/example/websocket/client/async-ssl/websocket_client_async_ssl.cpp
The problem is when I am waiting for a new quotation (who could take sometimes minutes or hours for small companies), the program is blocked waiting for a message and I do not have the opportunity to ask for another company.
I tried to use the "post" function to call again async_write in the good context thread but the program crashed.
Is there any way to force the completion of callback on_read, to have then the opportunity to send a new message?
Here is the function I modified (simplified without mutexes):
void
on_read(
beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(ec)
return fail2(ec, "read");
std::string mycontent = beast::buffers_to_string(buffer_.data());
cout << mycontent << endl;
buffer_.clear();
ws_.async_read(
buffer_,
beast::bind_front_handler(
&session::on_read,
shared_from_this()));
}
void subscribe(const std::string &symbol)
{
// We save the message in the queue
std::string text = "{\"action\": \"subscribe\", \"symbols\": \"" + symbol + "\"}";
msgqueue_.push_back(text);
boost::asio::post(ioc_, beast::bind_front_handler(&session::_subscription_to_post, shared_from_this()));
}
void _subscription_to_post()
{
if (msgqueue_.empty())
return;
// We send the message
ws_.async_write(
net::buffer(msgqueue_.front()),
beast::bind_front_handler(
&session::on_write,
shared_from_this()));
msgqueue_.pop_front();
}
And the program crashes immediately when trying to async_write.
The problem is when I am waiting for a new quotation [...] the program is blocked waiting for a message
It isn't technically blocked because you are using async_read
I tried to use the "post" function to call again async_write in the good context thread, but the program crashed.
That means you're doing something wrong. You can post a question with your self-contained minimal code, and we can tell you what is wrong.
In general, you can use a single read operation and a single write operation concurrently (as in: in flight, asynchronously, you still need to synchronize threads accessing all related resources).
Typically, you have a single async-read-chain active at all times, and a outbound message-queue that is drained by a single async-write chain (that obviously ends when the queue is empty, so needs to be initiated when the first outbound message is queued).
I have many answers on this site (literally dozens) that you may be able to find by search for outbox or outbox_ e.g. Keep in mind that the majority of them will deal with plain (ssl) sockets intead of websockets, but the pattern practically the same.
Is there any way to force the completion of callback on_read, to have then the opportunity to send a new message ?
You can technically cancel() it, which completes it with operation_aborted. But that is not what you need. You want full-duplex, so writing cancel() is the opposite of what you want.

Receiving large binary data over Boost::Beast websocket

I am trying to receive a large amount of data using a boost::beast::websocket, fed by another boost::beast::websocket. Normally, this data is sent to a connected browser but I'd like to set up a purely C++ unit test validating certain components of the traffic. I set the auto fragmentation to true from the sender with a max size of 1MB but after a few messages, the receiver spits out:
Read 258028 bytes of binary
Read 1547176 bytes of binary
Read 168188 bytes of binary
"Failed read: The WebSocket message exceeded the locally configured limit"
Now, I should have no expectation that a fully developed and well supported browser should exhibit the same characteristics as my possibly poorly architected unit test, which it does not. The browser has no issue reading 25MB messages over the websocket. My boost::beast::websocket on the other hand hits a limit.
So before I go down a rabbit hole, I'd like to see if anyone has any thoughts on this. My read sections looks like this:
void on_read(boost::system::error_code ec, std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if (ec)
{
m_log.error("Failed read: " + ec.message());
// Stop the websocket
stop();
return;
}
std::string data(boost::beast::buffers_to_string(m_buffer.data()));
// Yes I know this looks dangerous. The sender always sends as binary but occasionally sends JSON
if (data.at(0) == '{')
m_log.debug("Got message: " + data);
else
m_log.debug("Read " + utility::to_string(m_buffer.data().buffer_bytes()) + " of binary data");
// Do the things with the incoming doata
for (auto&& callback : m_read_callbacks)
callback(data);
// Toss the data
m_buffer.consume(bytes_transferred);
// Wait for some more data
m_websocket.async_read(
m_buffer,
std::bind(
&WebsocketClient::on_read,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
I saw in a separate example that instead of doing an async read, you can do a for/while loop reading some data until the message is done (https://www.boost.org/doc/libs/1_67_0/libs/beast/doc/html/beast/using_websocket/send_and_receive_messages.html). Would this be the right approach for an always open websocket that could send some pretty massive messages? Would I have to send some indicator to the client that the message is indeed done? And would I run into the exceeded buffer limit issue using this approach?
If your use pattern is fixed:
std::string data(boost::beast::buffers_to_string(m_buffer.data()));
And then, in particular
callback(data);
Then there will be no use at all reading block-wise, since you will be allocating the same memory anyways. Instead, you can raise the "locally configured limit":
ws.read_message_max(20ull << 20); // sets the limit to 20 miB
The default value is 16 miB (as of boost 1.75).
Side Note
You can probably also use ws.got_binary() to detect whether the last message received was binary or not.

C++ What is the best way to shutdown a Boost ASIO SSL socket?

I have a client application that uses Boost ASIO SSL sockets. I am running into connection errors that leave the socket in an unusable state until I restart the app. To begin, I make a connection request with this code:
void apiClient::connect(boost::asio::ip::tcp::resolver::iterator endpoint_list)
{
boost::asio::ip::tcp::resolver::iterator endpoint_iter = endpoint_list;
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iter;
if (!ssl_socket_->lowest_layer().is_open()) {
ssl_socket_->lowest_layer().async_connect(endpoint,
boost::bind(&apiClient::handle_connect, this,
boost::asio::placeholders::error, ++endpoint_iter));
}
}
Periodically I encounter an error returned to the connection handler, the error in ec.message is [A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond] Error code [10060]
Here is my connection handler:
void apiClient::handle_connect(const boost::system::error_code& ec,
boost::asio::ip::tcp::resolver::iterator endpoint_list)
{
bool found_error(false);
if (!ec) {
ssl_socket_->async_handshake(boost::asio::ssl::stream_base::client,
boost::bind(&apiClient::send_request, this,
boost::asio::placeholders::error));
}
else if (endpoint_list != boost::asio::ip::tcp::resolver::iterator()) {
std::wstring sMsg = boost::str(boost::wformat(L"API server connection failure to host [%s]. Trying next endpoint") % s2ws(endpoint_list->host_name()));
LogMsg(sMsg);
found_error = true;
epList = endpoint_list;
}
else {
std::wstring sMsg = boost::str(boost::wformat(L"API server connection failure. Error msg [%s] Error code [%d]") % s2ws(ec.message()) % ec.value());
LogMsg(sMsg);
found_error = true;
epList = start_endpoint_list;
}
if (found_error) {
requeue_request(SHUTDOWN_CONN);
}
}
In my requeue_request function I check the number of errors I have encountered for the request and if it exceeds a threshold, I discard the request and move on. If I have not exceeded the threshold, I shutdown the socket connection before attempting to reconnect and send the message again. My understanding is
that the emplace method recreates the socket instance.
void apiClient::reconnect(SHUTDOWN_CONN_ACTION action)
{
if (action == SHUTDOWN_CONN) {
shutdownSocket();
}
ssl_socket_.emplace(*io_context_, *ctx_);
connect_timer.expires_from_now(boost::posix_time::milliseconds(3000));
connect_timer.async_wait(boost::bind(&apiClient::handleConnectTimer, this, boost::asio::placeholders::error));
}
void CNXGHotKeyAPIClient::shutdownSocket()
{
boost::system::error_code ec;
ssl_socket_->next_layer().cancel(ec);
ssl_socket_->shutdown(ec);
ssl_socket_->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
ssl_socket_->lowest_layer().close(ec);
}
This design is not working for me. When I encounter any connection error, my application retries the connection 3 times, it fails with the same error all three times and the socket is left in a state where I cannot use it until I restart my application. The server I'm connecting to is honoring hundreds of
connection requests from python apps that do not use Boost so I don't think the server is at fault.
I have tried to find examples that use async_shutdown but I cannot find anything that helps me. The example at this link What is the proper way to securely disconnect an asio SSL socket?
uses the following code which I am unable to make work.
example code:
boost::system::error_code ec;
ssl_socket.cancel(ec);
ssl_socket.async_shutdown([](...) { ssl_socket.close(); };
ssl_socket.cancel() doesn't even exist in my version of Boost (v1.66) the closest thing I can find is ssl_socket.next_layer().cancel(ec)
My question is; how do you shutdown a socket after it has encountered an error so that it is not left in an unusable state? When my application encounters connection errors, many times I cannot reconnect until I restart the app. The app is still running and servicing user actions so I know it has not crashed, but the socket is in a state that is unusable. Thanks for any help
You can't shutdown an SSL socket. You have to close it.
You can't reconnect a socket after it has been connected, even if the connect attempt failed. You have to close it and create a new one.

How to recover from network interruption using boost::asio

I am writing a server that accepts data from a device and processes it. Everything works fine unless there is an interruption in the network (i.e., if I unplug the Ethernet cable, then reconnect it). I'm using read_until() because the protocol that the device uses terminates the packet with a specific sequence of bytes. When the data stream is interrupted, read_until() blocks, as expected. However when the stream starts up again, it remains blocked. If I look at the data stream with Wireshark, the device continues transmitting and each packet is being ACK'ed by the network stack. But if I look at bytes_readable it is always 0. How can I detect the interruption and how to re-establish a connection to the data stream? Below is a code snippet and thanks in advance for any help you can offer. [Go easy on me, this is my first Stack Overflow question....and yes I did try to search for an answer.]
using boost::asio::ip::tcp;
boost::asio::io_service IOservice;
tcp::acceptor acceptor(IOservice, tcp::endpoint(tcp::v4(), listenPort));
tcp::socket socket(IOservice);
acceptor.accept(socket);
for (;;)
{
len = boost::asio::read_until(socket, sbuf, end);
// Process sbuf
// etc.
}
Remember, the client initiates a connection, so the only thing you need to achieve is to re-create the socket and start accepting again. I will keep the format of your snippet but I hope your real code is properly encapsulated.
using SocketType = boost::asio::ip::tcp::socket;
std::unique_ptr<SocketType> CreateSocketAndAccept(
boost::asio::io_service& io_service,
boost::asio::ip::tcp::acceptor& acceptor) {
auto socket = std::make_unique<boost::asio::ip::tcp::socket>(io_service);
boost::system::error_code ec;
acceptor.accept(*socket.get(), ec);
if (ec) {
//TODO: Add handler.
}
return socket;
}
...
auto socket = CreateSocketAndAccept(IOservice, acceptor);
for (;;) {
boost::system::error_code ec;
auto len = boost::asio::read_until(*socket.get(), sbuf, end, ec);
if (ec) // you could be more picky here of course,
// e.g. check against connection_reset, connection_aborted
socket = CreateSocketAndAccept(IOservice, acceptor);
...
}
Footnote: Should go without saying, socket needs to stay in scope.
Edit: Based on the comments bellow.
The listening socket itself does not know whether a client is silent or whether it got cut off. All operations, especially synchronous, should impose a time limit on completion. Consider setting SO_RCVTIMEO or SO_KEEPALIVE (per socket, or system wide, for more info How to use SO_KEEPALIVE option properly to detect that the client at the other end is down?).
Another option is to go async and implement a full fledged "shared" socket server (BOOST example page is a great start).
Either way, you might run into data consistency issues and be forced to deal with it, e.g. when the client detects an interrupted connection, it would resend the data. (or something more complex using higher level protocols)
If you want to stay synchronous, the way I've seen things handled is to destroy the socket when you detect an interruption. The blocking call should throw an exception that you can catch and then start accepting connections again.
for (;;)
{
try {
len = boost::asio::read_until(socket, sbuf, end);
// Process sbuf
// etc.
}
catch (const boost::system::system_error& e) {
// clean up. Start accepting new connections.
}
}
As Tom mentions in his answer, there is no difference between inactivity and ungraceful disconnection so you need an external mechanism to detect this.
If you're expecting continuous data transfer, maybe a timeout per connection on the server side is enough. A simple ping could also work. After accepting a connection, ping your client every X seconds and declare the connection dead if he doesn't answer.

Boost ASIO async_read_some

I am having difficulties in implementing a simple TCP server. The following code is taken from boost::asio examples, "Http Server 1" to be precise.
void connection::start() {
socket_.async_read_some(
boost::asio::buffer(buffer_),
boost::bind(
&connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
void connection::handle_read(const boost::system::error_code& e, std::size_t bytes_transferred) {
if (!e && bytes_transferred) {
std::cout << " " << bytes_transferred <<"b" << std::endl;
data_.append(buffer_.data(), buffer_.data()+bytes_transferred);
//(1) what here?
socket_.async_read_some(
boost::asio::buffer(buffer_),
boost::bind(
&connection::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
}
else// if (e != boost::asio::error::operation_aborted)
{
std::cout << data_ << std::endl;
connection_manager_.stop(shared_from_this());
}
}
In the original code the buffer_ is big enough to keep the entire request. It's not what I need. I've changed the size to 32bytes.
The server compiles and listens at port 80 of localhost, so I try to connect to it via my web browser.
Now if the statement (1) is commented-out, then only the first 32bytes of the request are read and the connection hangs. Web browser keeps waiting for the response, the server does.. I dont know what.
If (1) is uncommented, then the entire request is read (and appeded to data_), but it never stops - I have to cancel the request in my browser and only then does the else { } part run - I see my request on stdout.
Question 1: How should I handle a large request?
Question 2: How should I cache the request (currently I append the buffer to a string)?
Question 3: How can I tell that the request is over? In HTTP there always is a response, so my web-browser keeps waiting for it and doesnt close the connection, but how can my server know that the request is over (and perhaps close it or reply some "200 OK")?
Suppose browser send you 1360 bytes of data, you say asio to read some data into your buffer that you say it only have 32 bytes.
then first time that you call it your handler will be called with 32 bytes start of data. here if you comment (1) then browser try to send rest of its data(actually browser already sent it and it is in the OS buffer that wait for you to peek it from there) and you are possibly blocked behind io_service::run for some miracle!!
if you uncomment (1) as you say your loop started, you read first block, then next and another and ... until the data that the browser sent finished, but after that when you say asio to read some more data it will wait for some more data that never come from the browser( since browser already sent its information and is waiting for your answer ) and when you cancel the request from the browser, it will close its socket and then your handler will be called whith an error that say I can't read more data, since the connection is closed.!!
but what you should do here to make it work is: you should learn HTTP format and thus know what is the data that your browser sent to you and provide a good answer for it and then your communication with the client will be proceeded. in this case end of buffer is \r\n\r\n and when you see it you shouldn't read any more data, you should process what you read till now and then send a response to the browser.