I'm using OpenSSL 1.1.1b and Boost 1.68 to create a simple server using https.
I followed the examples provided by boost beast and in particular the advance server flex.
The application seems to work properly. I can accept https session and also wss sessions.
The problem is when I exit from the application where the Visual Leak Detector finds 16 memory leaks that target at:
c:\openssl-1.1.1b\crypto\mem.c (233): abc.exe!CRYPTO_zalloc
c:\openssl-1.1.1b\crypto\err\err.c (716): abc.exe!ERR_get_state + 0x17 bytes
c:\openssl-1.1.1b\crypto\err\err.c (443): abc.exe!ERR_clear_error + 0x5 bytes
c:\usr\work\abc_repo\ext\boost_1_68_0\boost\asio\ssl\detail\impl\engine.ipp (235): abc.exe!boost::asio::ssl::detail::engine::perform
c:\usr\work\abc_repo\ext\boost_1_68_0\boost\asio\ssl\detail\impl\engine.ipp (137): abc.exe!boost::asio::ssl::detail::engine::handshake
I modified the pattern of the http session from the original boost beast code but it should perform exactly the same things.
I've tried to understand if the memory leaks increase based on the number of connections but it seems not. I don't understand how to get rid of this problem.
Following the code I used.
First a based http session class
class CApplicationServerBaseHttpSession
{
public:
std::shared_ptr<CApplicationServerSharedState> m_state = nullptr;
CApplicationServerHttpQueue m_queue;
// The parser is stored in an optional container so we can
// construct it from scratch it at the beginning of each new message.
boost::optional<boost::beast::http::request_parser<boost::beast::http::string_body>> parser_;
protected:
boost::asio::steady_timer m_timer;
boost::beast::flat_buffer buffer_;
boost::log::sources::severity_channel_logger<boost::log::trivial::severity_level> m_Logger{boost::log::keywords::channel = LOG_APPLICATION_SERVER_CHANNEL_ID};
boost::asio::strand<boost::asio::io_context::executor_type> m_strand;
public:
// Construct the session
CApplicationServerBaseHttpSession(
boost::asio::io_context& ioc,
boost::beast::flat_buffer buffer,
std::shared_ptr<CApplicationServerSharedState> const& state)
: m_state(state)
, m_strand(ioc.get_executor())
, m_timer(ioc,
(std::chrono::steady_clock::time_point::max)()
)
, m_queue(*this)
, buffer_(std::move(buffer))
{
}
void DoRead();
void OnRead(boost::system::error_code ec);
void OnWrite(boost::system::error_code ec, bool close);
virtual void WriteRequestStringBody(boost::beast::http::response<boost::beast::http::string_body> & msg) = 0;
virtual void WriteRequestFileBody(boost::beast::http::response<boost::beast::http::file_body> & msg) = 0;
protected:
virtual void ReadRequest() = 0;
virtual void DoEof() = 0;
virtual std::string GetRemoteAddress() = 0;
virtual void MakeWebSocketSession(boost::beast::http::request<boost::beast::http::string_body> req) = 0;
};
Here the implementation:
void CApplicationServerBaseHttpSession::DoRead()
{
// Set the timer
m_timer.expires_after(std::chrono::seconds(OCV_HTTP_SESSION_TIMER_EXPIRE_AFTER));
// Construct a new parser for each message
parser_.emplace();
// Apply a reasonable limit to the allowed size
// of the body in bytes to prevent abuse.
parser_->body_limit(HTTP_BODY_LIMIT);
this->ReadRequest();
}
void CApplicationServerBaseHttpSession::OnRead(boost::system::error_code ec)
{
// Happens when the timer closes the socket
if(ec == boost::asio::error::operation_aborted)
return;
// This means they closed the connection
if(ec == http::error::end_of_stream)
return this->DoEof();
if(ec == boost::asio::ssl::error::stream_truncated){
// "stream truncated" means that the other end closed the connection abruptly.
return warning(ec, "Http read", m_Logger);
}
if(ec)
return fail(ec, "Http read", m_Logger);
// See if it is a WebSocket Upgrade
if(websocket::is_upgrade(parser_->get())) {
// Get a websocket request handler to execute operation as authentication and authorization
// If these steps are allowed than the websocket session will be started
std::shared_ptr<CApplicationServerWsApiBase> endpointWs = m_state->GetEndpointWs(parser_->get().target().to_string());
if(endpointWs) {
int endpointErrorDefault = endpointWs->HandleRequest(parser_->get());
if(endpointErrorDefault > 0) { // Success Auth
// Make timer expire immediately, by setting expiry to time_point::min we can detect
// the upgrade to websocket in the timer handler
m_timer.expires_at((std::chrono::steady_clock::time_point::min)());
// Transfer the stream to a new WebSocket session
return MakeWebSocketSession(parser_->release());
} else {
// Authentication or Authorization failed
m_queue(endpointWs->GetResponseError(parser_->get(), endpointErrorDefault));
return;
}
} else {
// Wrong endpoint called: BadRequest
std::shared_ptr<CApplicationServerApiBase> endpoint = m_state->GetEndpoint(ApiURI::REQUEST_NOT_IMPLEMENTED);
if(endpoint) {
endpoint->HandleRequest(m_state->GetDocRoot(), parser_->release(), m_queue);
}
return;
}
}
BOOST_LOG_SEV(m_Logger, boost::log::trivial::trace) <<
"Request From: " <<
this->GetRemoteAddress() <<
" Request Target: " <<
parser_->get().target().to_string();
std::shared_ptr<CApplicationServerApiBase> endpoint = m_state->GetEndpoint(parser_->get().target().to_string());
if(endpoint) {
endpoint->HandleRequest(m_state->GetDocRoot(), parser_->release(), m_queue);
}
// If we aren't at the queue limit, try to pipeline another request
if(!m_queue.IsFull()) {
DoRead();
}
}
void CApplicationServerBaseHttpSession::OnWrite(boost::system::error_code ec, bool close)
{
// Happens when the timer closes the socket
if(ec == boost::asio::error::operation_aborted)
return;
if(ec)
return fail(ec, "write", m_Logger);
if(close) {
// This means we should close the connection, usually because
// the response indicated the "Connection: close" semantic.
return this->DoEof();
}
// Inform the queue that a write completed
if(m_queue.OnWrite()) {
// Read another request
DoRead();
}
}
The https session:
class COcvApplicationServerHttpSessionSSL
: public std::enable_shared_from_this<COcvApplicationServerHttpSessionSSL>
, public CApplicationServerBaseHttpSession
{
public:
public:
COcvApplicationServerHttpSessionSSL(boost::asio::ip::tcp::socket&& socket,boost::asio::ssl::context& ctx, boost::beast::flat_buffer&& buffer, std::shared_ptr<CApplicationServerSharedState> const& state);
~COcvApplicationServerHttpSessionSSL();
// Called by the base class
boost::beast::ssl_stream<boost::asio::ip::tcp::socket>& Stream();
boost::beast::ssl_stream<boost::asio::ip::tcp::socket> ReleaseStream();
void DoTimeout();
// Start the asynchronous operation
void Run();
void OnHandshake(boost::system::error_code ec, std::size_t bytes_used);
void OnShutdown(boost::system::error_code ec);
void OnTimer(boost::system::error_code ec);
private:
public:
boost::beast::ssl_stream<boost::asio::ip::tcp::socket> m_stream;
bool m_eof = false;
protected:
// Inherited via COcvApplicationServerBaseHttpSession
virtual void ReadRequest() override;
virtual void WriteRequestStringBody(boost::beast::http::response<boost::beast::http::string_body> & msg) override;
virtual void WriteRequestFileBody(boost::beast::http::response<boost::beast::http::file_body> & msg) override;
virtual void DoEof() override;
virtual std::string GetRemoteAddress() override;
virtual void MakeWebSocketSession(boost::beast::http::request<boost::beast::http::string_body> req) override;
};
and at the end the implementatition
COcvApplicationServerHttpSessionSSL::COcvApplicationServerHttpSessionSSL(tcp::socket&& socket, ssl::context & ctx, beast::flat_buffer&& buffer, std::shared_ptr<CApplicationServerSharedState> const & state)
: CApplicationServerBaseHttpSession(
socket.get_executor().context(),
std::move(buffer),
state)
, m_stream(std::move(socket), ctx)
{
}
COcvApplicationServerHttpSessionSSL::~COcvApplicationServerHttpSessionSSL()
{
}
beast::ssl_stream<tcp::socket> & COcvApplicationServerHttpSessionSSL::Stream()
{
return m_stream;
}
beast::ssl_stream<tcp::socket> COcvApplicationServerHttpSessionSSL::ReleaseStream()
{
return std::move(m_stream);
}
void COcvApplicationServerHttpSessionSSL::DoTimeout()
{
// If this is true it means we timed out performing the shutdown
if(m_eof)
return;
// Start the timer again
m_timer.expires_at(
(std::chrono::steady_clock::time_point::max)());
OnTimer({});
DoEof();
}
std::string COcvApplicationServerHttpSessionSSL::GetRemoteAddress()
{
return Stream().next_layer().remote_endpoint().address().to_string();
}
void COcvApplicationServerHttpSessionSSL::MakeWebSocketSession(boost::beast::http::request<boost::beast::http::string_body> req)
{
std::make_shared<CApplicationServerWebSocketSessionSSL>(
std::move(m_stream), m_state)->Run(std::move(req));
}
void COcvApplicationServerHttpSessionSSL::Run()
{
// Make sure we run on the strand
if(!m_strand.running_in_this_thread())
return boost::asio::post(
boost::asio::bind_executor(
m_strand,
std::bind(
&COcvApplicationServerHttpSessionSSL::Run,
shared_from_this())));
// Run the timer. The timer is operated
// continuously, this simplifies the code.
OnTimer({});
// Set the timer
m_timer.expires_after(std::chrono::seconds(OCV_HTTP_SESSION_TIMER_EXPIRE_AFTER));
// Perform the SSL handshake
// Note, this is the buffered version of the handshake.
m_stream.async_handshake(
ssl::stream_base::server,
buffer_.data(),
boost::asio::bind_executor(
m_strand,
std::bind(
&COcvApplicationServerHttpSessionSSL::OnHandshake,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2)));
}
void COcvApplicationServerHttpSessionSSL::OnHandshake(boost::system::error_code ec, std::size_t bytes_used)
{
// Happens when the handshake times out
if(ec == boost::asio::error::operation_aborted)
return;
if(ec)
return fail(ec, "handshake", m_Logger);
// Consume the portion of the buffer used by the handshake
buffer_.consume(bytes_used);
DoRead();
}
void COcvApplicationServerHttpSessionSSL::OnShutdown(boost::system::error_code ec)
{
// Happens when the shutdown times out
if(ec == boost::asio::error::operation_aborted || ec == boost::asio::ssl::error::stream_truncated)
return;
if(ec)
return fail(ec, "shutdown HTTPS", m_Logger);
// At this point the connection is closed gracefully
}
void COcvApplicationServerHttpSessionSSL::OnTimer(boost::system::error_code ec)
{
if(ec && ec != boost::asio::error::operation_aborted)
return fail(ec, "timer", m_Logger);
// Check if this has been upgraded to Websocket
if(m_timer.expires_at() == (std::chrono::steady_clock::time_point::min)())
return;
// Verify that the timer really expired since the deadline may have moved.
if(m_timer.expiry() <= std::chrono::steady_clock::now())
return DoTimeout();
// Wait on the timer
m_timer.async_wait(
boost::asio::bind_executor(
m_strand,
std::bind(
&COcvApplicationServerHttpSessionSSL::OnTimer,
shared_from_this(),
std::placeholders::_1)));
}
void COcvApplicationServerHttpSessionSSL::ReadRequest()
{
// Read a request
http::async_read(
Stream(),
buffer_,
*parser_,
boost::asio::bind_executor(
m_strand,
std::bind(
&CApplicationServerBaseHttpSession::OnRead,
shared_from_this(),
std::placeholders::_1)));
}
void COcvApplicationServerHttpSessionSSL::WriteRequestStringBody(boost::beast::http::response<boost::beast::http::string_body> & msg)
{
boost::beast::http::async_write(
Stream(),
msg,
boost::asio::bind_executor(
m_strand,
std::bind(
&CApplicationServerBaseHttpSession::OnWrite,
shared_from_this(),
std::placeholders::_1,
msg.need_eof()
)
)
);
}
void COcvApplicationServerHttpSessionSSL::WriteRequestFileBody(boost::beast::http::response<boost::beast::http::file_body> & msg)
{
boost::beast::http::async_write(
Stream(),
msg,
boost::asio::bind_executor(
m_strand,
std::bind(
&CApplicationServerBaseHttpSession::OnWrite,
shared_from_this(),
std::placeholders::_1,
msg.need_eof()
)
)
);
}
void COcvApplicationServerHttpSessionSSL::DoEof()
{
m_eof = true;
// Set the timer
m_timer.expires_after(std::chrono::seconds(OCV_HTTP_SESSION_TIMER_EXPIRE_DO_EOF));
// Perform the SSL shutdown
m_stream.async_shutdown(
boost::asio::bind_executor(
m_strand,
std::bind(
&COcvApplicationServerHttpSessionSSL::OnShutdown,
shared_from_this(),
std::placeholders::_1)));
}
The Visual Leak Detector gives me the following:
c:\openssl-1.1.1b\crypto\mem.c (233): abc.exe!CRYPTO_zalloc
c:\openssl-1.1.1b\crypto\err\err.c (716): abc.exe!ERR_get_state + 0x17 bytes
c:\openssl-1.1.1b\crypto\err\err.c (443): abc.exe!ERR_clear_error + 0x5 bytes
c:\usr\work\abc_repo\ext\boost_1_68_0\boost\asio\ssl\detail\impl\engine.ipp (235): abc.exe!boost::asio::ssl::detail::engine::perform
c:\usr\work\abc_repo\ext\boost_1_68_0\boost\asio\ssl\detail\impl\engine.ipp (137): abc.exe!boost::asio::ssl::detail::engine::handshake
c:\usr\work\abc_repo\ext\boost_1_68_0\boost\asio\ssl\detail\buffered_handshake_op.hpp (70): abc.exe!boost::asio::ssl::detail::buffered_handshake_op<boost::asio::const_buffer>::process<boost::asio::const_buffer const * __ptr64> + 0x1F bytes
c:\usr\work\abc_repo\ext\boost_1_68_0\boost\asio\ssl\detail\buffered_handshake_op.hpp (48): abc.exe!boost::asio::ssl::detail::buffered_handshake_op<boost::asio::const_buffer>::operator()
c:\usr\work\abc_repo\ext\boost_1_68_0\boost\asio\ssl\detail\io.hpp (136): abc.exe!boost::asio::ssl::detail::io_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp>,boost::asio::ssl::detail::buffered_handshake_op<boost::asio::const_buffer>,boost::asio::executor_binder<std::_Binder<std::_Unforced,void (__cdecl CabcApplicationServerH + 0x50 bytes
c:\usr\work\abc_repo\ext\boost_1_68_0\boost\asio\ssl\detail\io.hpp (333): abc.exe!boost::asio::ssl::detail::async_io<boost::asio::basic_stream_socket<boost::asio::ip::tcp>,boost::asio::ssl::detail::buffered_handshake_op<boost::asio::const_buffer>,boost::asio::executor_binder<std::_Binder<std::_Unforced,void (__cdecl CabcApplicationServ + 0x87 bytes
c:\usr\work\abc_repo\ext\boost_1_68_0\boost\asio\ssl\stream.hpp (505): abc.exe!boost::asio::ssl::stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >::async_handshake<boost::asio::const_buffer,boost::asio::executor_binder<std::_Binder<std::_Unforced,void (__cdecl CabcApplicationServerHttpSessionSSL::*)(boost::system::erro + 0x5E bytes
c:\usr\work\abc_repo\ext\boost_1_68_0\boost\beast\experimental\core\ssl_stream.hpp (485): abc.exe!boost::beast::ssl_stream<boost::asio::basic_stream_socket<boost::asio::ip::tcp> >::async_handshake<boost::asio::const_buffer,boost::asio::executor_binder<std::_Binder<std::_Unforced,void (__cdecl CabcApplicationServerHttpSessionSSL::*)(boost::system::erro
c:\usr\work\abc_repo\util\capplicationserverhttpsession.cpp (343): abc.exe!CabcApplicationServerHttpSessionSSL::Run + 0x154 bytes
In some of the Leaks I also have:
c:\usr\work\abc_repo\ext\boost_1_68_0\boost\asio\ssl\detail\impl\engine.ipp (290): abc.exe!boost::asio::ssl::detail::engine::do_accept
Of course seems related to the ssl handshake but I check the session shutdown and seems ok.
Thank you in advance.
Every thread that uses async_handshake() leaks memory. I added OPENSSL_thread_stop() at the end of my thread procedure and it solved the issue.
Took it from here: https://github.com/openssl/openssl/issues/3033#issuecomment-289838302
Related
I am developing a QT 6 Widget based UDP audio application that repeatedly sends out a single UDP audio frame sample (4K bytes sine wave tone) to a remote UDP echo server at a predetermined rate - (right now the echo server is hosted locally though).
The UDP echo server is based on the asynchronous UDP echo server sample developed by the asio author (not me). This is shown below (slightly modified to include a hard coded 4K block for testing purposes). The application is also launched with a port parameter 1234 - so it listens on port 1234 for the incoming audio packet that it will echo back to client.
//
// async_udp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff 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)
//
#include <cstdlib>
#include <iostream>
#include <asio/ts/buffer.hpp>
#include <asio/ts/internet.hpp>
using asio::ip::udp;
class server {
public:
server(asio::io_context& io_context, short port)
: socket_(io_context, udp::endpoint(udp::v4(), port)) {
do_receive();
}
void do_receive() {
socket_.async_receive_from(
asio::buffer(data_, max_length), sender_endpoint_,
[this](std::error_code ec, std::size_t bytes_recvd) {
if (!ec && bytes_recvd > 0) {
do_send(bytes_recvd);
} else {
do_receive();
}
});
}
void do_send(std::size_t length) {
socket_.async_send_to(
asio::buffer(data_, length), sender_endpoint_,
[this](std::error_code /*ec*/, std::size_t /*bytes_sent*/) {
do_receive();
});
}
private:
udp::socket socket_;
udp::endpoint sender_endpoint_;
enum { max_length = 4096 };
char data_[max_length]{};
};
int main(int argc, char* argv[]) {
try {
if (argc != 2) {
std::cerr << "Usage: async_udp_echo_server <port>\n";
return 1;
}
asio::io_context io_context;
server s(io_context, std::atoi(argv[1]));
io_context.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
I currently have this working successfully in the client as a stand alone asio worker thread, however since I need to graphically display the returned audio packets, I cannot use the stand alone asio thread approach; I need to use QT with its signals/slots async magic instead.
For the purposes of illustration, I also include my working asio client code that runs in a separate joinable thread. This client thread uses a asio::steady_timer that fires an asynchronous 4k UDP packet repeatedly to the echo server. The code also compares the echoed back contents to this outgoing audio sample successfully.
void
RTPClient::start() {
mpSendEndpoint = std::make_unique<ip::udp::endpoint>(
ip::address::from_string(mConfig.mHostName),
mConfig.mPortNum);
mpSocket = std::make_unique<ip::udp::socket>(
mIOContext, mpSendEndpoint->protocol());
mpSocketTimer = std::make_unique<steady_timer>(
mIOContext);
mWorker = std::thread([this]() {
mIOContext.run();
});
if (!mShutdownFlag) {
// kick off the async chain by immediate timeout
mpSocketTimer->expires_after(std::chrono::seconds(0));
mpSocketTimer->async_wait([this]<typename T0>(T0&& ec) {
handle_timeout(std::forward<T0>(ec));
});
}
}
void
RTPClient::handle_timeout(const error_code& ec)
{
if (!ec && !mShutdownFlag) {
if (!mpAudioOutput) {
// check to see if there is new audio test data waiting in queue
if (const auto audioData = mIPCQueue->try_pop(); audioData) {
// new audio waiting, copy the data to mpAudioTXData and allocate an identically
// sized receive buffer to receive the echo replies from the server
mpAudioInput = std::make_unique<AudioDatagram>(audioData->first.size());
mpAudioOutput = std::make_unique<AudioDatagram>(std::move(audioData->first));
mAudioBlockUSecs = audioData->second;
} else {
mpSocketTimer->expires_after(seconds(1));
mpSocketTimer->async_wait([this]<typename T0>(T0&& ec) {
handle_timeout(std::forward<T0>(ec));
});
// nothing to send as waveform data not received from GUI.
// short circuit return with a 1 sec poll
return;
}
}
mpSocket->async_send_to(asio::buffer(
mpAudioOutput.get(), mpAudioOutput->size()),
*mpSendEndpoint, [this]<typename T0, typename T1>(T0&& ec, T1&& bytes_transferred) {
handle_send_to(std::forward<T0>(ec), std::forward<T1>(bytes_transferred));
});
}
}
void
RTPClient::handle_send_to(const error_code& ec, std::size_t bytes_transferred) {
if (!ec && bytes_transferred > 0 && !mShutdownFlag) {
mpSocketTimer->expires_after(microseconds(mAudioBlockUSecs));
mpSocketTimer->async_wait([this]<typename T0>(T0&& ec) {
handle_timeout(std::forward<T0>(ec));
});
mpSocket->async_receive_from(asio::buffer(
mpAudioInput.get(), mpAudioInput->size()), *mpSendEndpoint,
[this]<typename T0, typename T1>(T0&& ec, T1&& bytes_transferred) {
handle_receive(std::forward<T0>(ec), std::forward<T1>(bytes_transferred));
});
}
}
void
RTPClient::handle_receive(const error_code& ec, std::size_t bytes_transferred) {
if (!ec && bytes_transferred > 0) {
double foo = 0.0;
for (const auto next : *mpAudioOutput) {
foo += (double)next;
}
double bar = 0.0;
for (const auto next : *mpAudioInput) {
bar += (double)next;
}
if (foo != bar)
{
auto baz = 0;
(void)baz;
}
}
}
/**
* Shutdown the protocol instance by shutting down the IPC
* queue and closing the socket and associated timers etc.
*
* <p>This is achieved by setting a flag which is read by the
* busy loop as an exit condition.
*/
void
RTPClient::shutdown() {
// set the shared shutdown flag
mShutdownFlag = true;
// wake up any locked threads so they can see the above flag
if (mIPCQueue) {
mIPCQueue->shutdown();
}
// stop the socket timer - do not reset it
// as there are some time sensitive parts in the code
// where mpSocketTimer is dereferenced
if (mpSocketTimer) {
mpSocketTimer->cancel();
}
std::error_code ignoredError;
// close the socket if we created & opened it, making
// sure that we close down both ends of the socket.
if (mpSocket && mpSocket->is_open()) {
mpSocket->shutdown(ip::udp::socket::shutdown_both, ignoredError);
// reset so we will reallocate and then reopen
// via boost::async_connect(...) later.
mpSocket.reset();
}
// wait for the any other detached threads to see mShutdownFlag
// as it is running in a detached mWorkerThread which sleeps
// for 50ms CDU key polling requests.
std::this_thread::sleep_for(milliseconds(200));
}
I need to replace this separate asio client thread code with a QUdpSocket based client code to do the equivalent, as I need to use signals/slots to notify the GUI when the blocks arrive and display the returned waveform in a widget. To this end I have the following QT worker thread. I can see that the asio echo server receives the datagram, however I do not know how to receive the echoed contents back into the client. Is there some bind or connect call that I need to do on the client side. I am totally confused with when to call bind and when to call connect on UDP sockets.
// SYSTEM INCLUDES
//#include <..>
// APPLICATION INCLUDES
#include "RTPSession.h"
// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// FUNCTIONS
// NAMESPACE USAGE
using namespace std::chrono;
// STATIC VARIABLE INITIALIZATIONS
std::mutex RTPSession::gMutexGuard;
RTPSession::RTPSession(QObject* parent)
: QObject(parent)
, mpSocket{ std::make_unique<QUdpSocket>(parent) }
{
mpSocket->bind(45454, QUdpSocket::DefaultForPlatform);
connect(mpSocket.get(), &QUdpSocket::readyRead,
this, &RTPSession::processPendingDatagrams);
}
/**
* Thread function that listens RTP session updates.
*
* <p>The implementation polls for shutdown every second.
*
* #param rRTPInfo [in] qt thread parameters.
*/
void
RTPSession::doWork(
const std::tuple<int32_t, int32_t, int32_t>& /*rRTPInfo*/)
{
try {
// just dispatched, so reset exit flag
mExitWorkLoop = false;
int frameCounter = 0;
while (!mExitWorkLoop) {
constexpr auto gPollMillis = 1000;
// poll using shortest (non zero) interval in schedule
std::unique_lock<std::mutex> lk(gMutexGuard);
mCondVariable.wait_for(lk, milliseconds(gPollMillis),
[this] { return mExitWorkLoop; });
QByteArray datagram = "Broadcast message " + QByteArray::number(frameCounter++);
mpSocket->writeDatagram(datagram.data(), datagram.size(),
QHostAddress::LocalHost, 1234);
if (mpSocket->hasPendingDatagrams()) {
//mpSocket->readDatagram()
int t = 0;
(void)t;
}
// update GUI with the audio stats - add more later
emit updateProgress(frameCounter++);
}
} catch (const std::exception& rEx) {
// exit thread with the exception details
emit finishWork(tr("exiting worker, error:") + rEx.what());
}
// exit thread with status bar message
emit finishWork(tr("finished"));
}
void
RTPSession::shutdown()
{
// Critical section.
std::scoped_lock<std::mutex> lock(gMutexGuard);
mExitWorkLoop = true;
// Notify the potentially sleeping thread that is
// waiting for up to 1 second
mCondVariable.notify_one();
}
void
RTPSession::processPendingDatagrams() {
QByteArray datagram;
while (mpSocket->hasPendingDatagrams()) {
datagram.resize(int(mpSocket->pendingDatagramSize()));
mpSocket->readDatagram(datagram.data(), datagram.size());
//statusLabel->setText(tr("Received datagram: \"%1\"")
// .arg(datagram.constData()));
}
}
I 'm using a strand to avoid concurrent writes on TCP server using Boost.Asio. But it seems it only prevents concurrent execution of handlers.
Indeed if I do two successive async_write, one with a very big packet, and the other with a very small one, wireshark shows interleaves. As async_write is composed of multiple calls of async_write_some, it seems that the handler of my second write is allowed to be executed between two handlers of the first call. Which is very bad for me.
Wireshark output: [Packet 1.1] [Packet 1.2] [Packet 2] [Packet 1.3] ... [Packet 1.x]
struct Command
{
// Header
uint64_t ticket_id; // UUID
uint32_t data_size; // size of data
// data
std::vector<unsigned char> m_internal_buffer;
}
typedef std::shared_ptr<Command> command_type;
void tcp_server::write(command_type cmd)
{
boost::asio::async_write(m_socket, boost::asio::buffer(cmd->getData(), cmd->getTotalPacketSize()),
boost::asio::bind_executor(m_write_strand,
[this, cmd](const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (error)
{
// report
}
}
)
);
}
and the main:
int main()
{
tcp_server.write(big_packet); // Packet 1 = 10 MBytes !
tcp_server.write(small_packet); // Packet 2 = 64 kbytes
}
Is the strand not appropriate in my case ?
P.S: I saw that close topic here but it does not cover the same use case in my opinion.
You have to make sure your async operation is initiated from the strand. Your code currently doesn't show this to be the case. Hopefully this helps, otherwise, post a MCVE
So e.g.
void tcp_server::write(command_type cmd)
{
post(m_write_strand, [this, cmd] { this->do_write(cmd); });
}
Making up a MCVE from your question code:
Live On Coliru
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
using Executor = boost::asio::thread_pool::executor_type;
struct command {
char const* getData() const { return ""; }
size_t getTotalPacketSize() const { return 1; }
};
using command_type = command*;
struct tcp_server {
tcp_server(Executor ex) : m_socket(ex), m_write_strand(ex)
{
// more?
}
void write(command_type cmd);
void do_write(command_type cmd);
tcp::socket m_socket;
boost::asio::strand<Executor> m_write_strand;
};
void tcp_server::write(command_type cmd)
{
post(m_write_strand, [this, cmd] { this->do_write(cmd); });
}
void tcp_server::do_write(command_type cmd)
{
boost::asio::async_write(
m_socket,
boost::asio::buffer(cmd->getData(), cmd->getTotalPacketSize()),
bind_executor(m_write_strand,
[/*this, cmd*/](boost::system::error_code error,
size_t bytes_transferred) {
if (error) {
// report
}
}));
}
int main() {
boost::asio::thread_pool ioc;
tcp_server tcp_server(ioc.get_executor());
command_type big_packet{}, small_packet{};
tcp_server.write(big_packet); // Packet 1 = 10 MBytes !
tcp_server.write(small_packet); // Packet 2 = 64 kbytes
ioc.join();
}
I am following ASIO's async_tcp_echo_server.cpp example to write a server.
My server logic looks like this (.cpp part):
1.Server startup:
bool Server::Start()
{
mServerThread = std::thread(&Server::ServerThreadFunc, this, std::ref(ios));
//ios is asio::io_service
}
2.Init acceptor and listen for incoming connection:
void Server::ServerThreadFunc(io_service& service)
{
tcp::endpoint endp{ address::from_string(LOCAL_HOST),MY_PORT };
mAcceptor = acceptor_ptr(new tcp::acceptor{ service,endp });
// Add a job to start accepting connections.
StartAccept(*mAcceptor);
// Process event loop.Hang here till service terminated
service.run();
std::cout << "Server thread exiting." << std::endl;
}
3.Accept a connection and start reading from the client:
void Server::StartAccept(tcp::acceptor& acceptor)
{
acceptor.async_accept([&](std::error_code err, tcp::socket socket)
{
if (!err)
{
std::make_shared<Connection>(std::move(socket))->StartRead(mCounter);
StartAccept(acceptor);
}
else
{
std::cerr << "Error:" << "Failed to accept new connection" << err.message() << std::endl;
return;
}
});
}
void Connection::StartRead(uint32_t frameIndex)
{
asio::async_read(mSocket, asio::buffer(&mHeader, sizeof(XHeader)), std::bind(&Connection::ReadHandler, shared_from_this(), std::placeholders::_1, std::placeholders::_2, frameIndex));
}
So the Connection instance finally triggers ReadHandler callback where I perform actual read and write:
void Connection::ReadHandler(const asio::error_code& error, size_t bytes_transfered, uint32_t frameIndex)
{
if (bytes_transfered == sizeof(XHeader))
{
uint32_t reply;
if (mHeader.code == 12345)
{
reply = (uint32_t)12121;
size_t len = asio::write(mSocket, asio::buffer(&reply, sizeof(uint32_t)));
}
else
{
reply = (uint32_t)0;
size_t len = asio::write(mSocket, asio::buffer(&reply, sizeof(uint32_t)));
this->mSocket.shutdown(tcp::socket::shutdown_both);
return;
}
}
while (mSocket.is_open())
{
XPacket packet;
packet.dataSize = rt->buff.size();
packet.data = rt->buff.data();
std::vector<asio::const_buffer> buffers;
buffers.push_back(asio::buffer(&packet.dataSize,sizeof(uint64_t)));
buffers.push_back(asio::buffer(packet.data, packet.dataSize));
auto self(shared_from_this());
asio::async_write(mSocket, buffers,
[this, self](const asio::error_code error, size_t bytes_transfered)
{
if (error)
{
ERROR(200, "Error sending packet");
ERROR(200, error.message().c_str());
}
}
);
}
}
Now, here is the problem. The server receives data from the client and sends ,using sync asio::write, fine. But when it comes to to asio::async_read or asio::async_write inside the while loop, the method's lambda callback never gets triggered, unless I put io_context().run_one(); immediately after that. I don't understand why I see this behaviour. I do call io_service.run() right after acceptor init, so it blocks there till the server exit. The only difference of my code from the asio example, as far as I can tell, is that I run my logic from a custom thread.
Your callback isn't returning, preventing the event loop from executing other handlers.
In general, if you want an asynchronous flow, you would chain callbacks e.g. callback checks is_open(), and if true calls async_write() with itself as the callback.
In either case, the callback returns.
This allows the event loop to run, calling your callback, and so on.
In short, you should make sure your asynchronous callbacks always return in a reasonable time frame.
I am trying to read several bytes asynchronously from a serial port. Currently the code I am using (source is How do I perform a nonblocking read using asio?), only allows me to read 1 byte and then it exits. How do I get it so that the code keeps reading until there is no more incoming data in the serial port? Thanks :)
void foo()
{
boost::asio::io_service io_svc;
boost::asio::serial_port ser_port(io_svc, "/dev/ttyS0");
boost::asio::deadline_timer timeout(io_svc);
unsigned char my_buffer[2];
bool data_available = false;
ser_port.async_read_some(boost::asio::buffer(my_buffer),
boost::bind(&read_callback, boost::ref(data_available),
boost::ref(timeout),boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
timeout.expires_from_now(boost::posix_time::seconds(30));
timeout.async_wait(boost::bind(&wait_callback, boost::ref(ser_port),boost::asio::placeholders::error()));
io_svc.run();
io_svc.
if(!data_available)
{
ser_port.close();
cout << "ser_port was closed";
}
}
void read_callback(bool& data_available, boost::asio::deadline_timer& timeout, const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (error || !bytes_transferred)
{
// No data was read!
data_available = false;
return;
}
timeout.cancel();
data_available = true;
}
void wait_callback(boost::asio::serial_port& ser_port, const boost::system::error_code& error)
{
if (error)
{
// Data was read and this timeout was cancelled
return;
}
ser_port.cancel();
}
In your read_callback you cancel your timer and do not start a new read.
So what do you expect ASIO to do. You just canceled all handlers and like the documentation states, the run method will return when no handlers are left.
So if you want to have more data than just the one byte you receive you can do two things:
First just issue another call to asio to read more from the port.
void read_callback(bool& data_available, boost::asio::deadline_timer& timeout, const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (error || !bytes_transferred)
{
// No data was read!
data_available = false;
return;
}
// do something with the data you just read
ser_port.async_read_some(boost::asio::buffer(my_buffer),
boost::bind(&read_callback, boost::ref(data_available),
boost::ref(timeout),boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
//restart your timer
data_available = true;
}
or you can add use transfer_at_least to get at least the amount of data you need.
I want to create a simple HTTPClient with pion-net (Pion-net leverages boost::asio.).
Boot Version 1.49
Pion-net Version 4.11
My client should just be a able to:
send a HTTP Request (this is working)
receive the HTTP Response (not working)
asynchronous code is not a must, synchronous would be ok
This is what I got:
#include <iostream>
#include "boost/asio.hpp"
#include "boost/thread.hpp"
#include "pion/net/HTTPRequestWriter.hpp"
#include "pion/net/HTTPResponseReader.hpp"
void FinishedResponseReading(pion::net::HTTPResponsePtr httpResponsePtr,
pion::net::TCPConnectionPtr tcpConnectionPtr,
const boost::system::error_code& errorCode_ref)
{
// ***************************
// this code is never reached!
// ***************************
std::cout << errorCode_ref << std::endl;
std::cout << httpResponsePtr->getContent() << std::endl;
tcpConnectionPtr->finish();
tcpConnectionPtr->close();
}
void FinishedRequestSending(const boost::system::error_code& error_code_ref,
pion::net::TCPConnectionPtr tcpConnectionPtr,
pion::net::HTTPRequest* httpRequest_ptr)
{
// ***************************
// this code is never reached!
// ***************************
pion::net::HTTPResponseReader::FinishedHandler fh =
boost::bind(FinishedResponseReading, _1, _2, _3);
pion::net::HTTPResponseReaderPtr httpResponseReaderPtr =
pion::net::HTTPResponseReader::create(tcpConnectionPtr,
*httpRequest_ptr,
fh);
httpResponseReaderPtr->receive();
}
int main()
{
boost::asio::io_service io_service;
// create and configure HTTPRequest
pion::net::HTTPRequest* httpRequest_ptr = new pion::net::HTTPRequest();
pion::net::HTTPRequestPtr httpRequestPtr(httpRequest_ptr);
httpRequest_ptr->setResource("/someService");
httpRequest_ptr->setMethod("PUT");
// create TCPConnection
pion::net::TCPConnection* tcpConnection_ptr =
new pion::net::TCPConnection(io_service);
pion::net::TCPConnectionPtr tcpConnectionPtr(tcpConnection_ptr);
// create HTTPRequestWriter
pion::net::HTTPRequestWriterPtr httpRequestWriterPtr(
pion::net::HTTPRequestWriter::create(tcpConnectionPtr,
httpRequestPtr,
boost::bind(FinishedRequestSending, _1,
tcpConnectionPtr,
httpRequest_ptr)));
// needed?
tcpConnection_ptr->setLifecycle(pion::net::TCPConnection::LIFECYCLE_KEEPALIVE);
// connect to server
tcpConnection_ptr->connect("192.168.1.14", 8080);
// send payload
httpRequestWriterPtr << "{\"someService\": \"something\"}";
httpRequestWriterPtr->send();
// ***********************************
// working fine so far! server is getting payload and is sending a HTTP Response
// but FinishedRequestSending is never reached
// ***********************************
// this is just to not exit immediately
boost::this_thread::sleep(boost::posix_time::milliseconds(15000));
// cleanup
delete(httpRequest_ptr);
delete(tcpConnection_ptr);
return 0;
}
If you want to communicate synchronously you could do something like this:
int main()
{
boost::asio::io_service io_service
pion::net::TCPConnection tcpConnection(io_service);
pion::net::HTTPRequest httpRequest;
httpRequest.setResource("/server/resource");
httpRequest.setMethod("PUT");
httpRequest.setMinor(1);
httpRequest.setMajor(1);
httpRequest.setContent("test");
boost::system::error_code ec;
ec = tcpConnection.connect("192.168.1.1", 80); // blocks till connected or timed out
if (!ec)
{
httpRequest.send(tcpConnection, ec); // never blocks
if (!ec)
{
pion::net::HTTPResponse(httpRequest);
httpResponse.receive(tcpConnection, ec); // this might block forever :-(
// httpResponse.receive seems to be IO dependent, you can set your socket to timeout
if (!ec)
{
httpResponse.write(std::cout, ec);
}
}
}
}
if however you need a little more sophisticated approach you could pion::net::HTTPResponseReader to wait asynch for a server response.
header:
class MyHTTPClient {
public:
void close();
pion::net::HTTPResponsePtr blockingReceiveOrTimeout(pion::net::HTTPRequest httpRequest, boost::system::error_code& ec_ref);
MyHTTPClient(boost::asio::ip::address aServerIP, unsigned int aServerPort);
virtual ~MyHTTPClient();
private:
boost::asio::ip::address mp_serverIP;
unsigned int mp_serverPort;
boost::asio::io_service mp_ioService;
pion::net::TCPConnectionPtr mp_tcpConnectionPtr;
pion::net::HTTPResponsePtr mp_curr_httpResponsePtr;
boost::system::error_code mp_curr_errorCode;
void finishedReceiveResponse(pion::net::HTTPResponsePtr httpResponsePtr, const boost::system::error_code& error_code_ref);
};
cpp:
MyHTTPClient::MyHTTPClient(boost::asio::ip::address aServerIP, unsigned int aServerPort) : mp_serverIP(aServerIP), mp_serverPort(aServerPort)
{
mp_tcpConnectionPtr.reset(new pion::net::TCPConnection(mp_ioService));
mp_tcpConnectionPtr->setLifecycle(pion::net::TCPConnection::LIFECYCLE_KEEPALIVE);
}
MyHTTPClient::~MyHTTPClient()
{
mp_tcpConnectionPtr->close();
}
void MyHTTPClient::close()
{
mp_tcpConnectionPtr->close();
}
pion::net::HTTPResponsePtr MyHTTPClient::blockingReceiveOrTimeout(pion::net::HTTPRequest httpRequest, boost::system::error_code& error_code_ref)
{
// reinit
mp_curr_httpResponsePtr.reset();
mp_ioService.reset();
error_code_ref.clear();
// connect to server if not already connectec
if (!mp_tcpConnectionPtr->is_open())
{
error_code_ref = mp_tcpConnectionPtr->connect(mp_serverIP, mp_serverPort);
}
if (!error_code_ref)
{
// send Request
httpRequest.send(*mp_tcpConnectionPtr.get(), error_code_ref, false);
if (!error_code_ref)
{
// asynchronously wait for response (times out automatically)
pion::net::HTTPResponseReader::FinishedHandler responseReaderFinishHandler = boost::bind(&MyHTTPClient::finishedReceiveResponse, this, _1, _3);
const pion::net::HTTPRequest constHTTPRequest = httpRequest;
pion::net::HTTPResponseReaderPtr httpResponseReaderPtr = pion::net::HTTPResponseReader::create(
mp_tcpConnectionPtr,
constHTTPRequest,
responseReaderFinishHandler);
httpResponseReaderPtr->receive();
mp_ioService.run();
}
}
return mp_curr_httpResponsePtr;
}
void MyHTTPClient::finishedReceiveResponse(pion::net::HTTPResponsePtr httpResponsePtr, const boost::system::error_code& error_code_ref)
{
mp_curr_httpResponsePtr = httpResponsePtr;
}
Martin's code updated for PION 5.0.3:
httpClient.hpp:
#ifndef httpClient_HPP_INCLUDED
#define httpClient_HPP_INCLUDED
#include <pion/error.hpp>
#include <pion/http/response.hpp>
#include <pion/tcp/connection.hpp>
#include <pion/http/response_reader.hpp>
#include <boost/asio.hpp>
class httpClient
{
public:
void close();
pion::http::response_ptr blockingReceiveOrTimeout(pion::http::request httpRequest, boost::system::error_code& ec_ref);
httpClient(boost::asio::ip::address aServerIP, unsigned int aServerPort);
virtual ~httpClient();
private:
boost::asio::ip::address mp_serverIP;
unsigned int mp_serverPort;
boost::asio::io_service mp_ioService;
pion::tcp::connection_ptr mp_tcpConnectionPtr;
pion::http::response_ptr mp_curr_httpResponsePtr;
boost::system::error_code mp_curr_errorCode;
void finishedReceiveResponse(pion::http::response_ptr httpResponsePtr, const boost::system::error_code& error_code_ref);
};
#endif // httpClient_HPP_INCLUDED
httpClient.cpp:
#include "httpClient.hpp"
httpClient::httpClient(boost::asio::ip::address aServerIP, unsigned int aServerPort) : mp_serverIP(aServerIP), mp_serverPort(aServerPort)
{
mp_tcpConnectionPtr.reset(new pion::tcp::connection(mp_ioService));
mp_tcpConnectionPtr->set_lifecycle(pion::tcp::connection::LIFECYCLE_KEEPALIVE);
}
httpClient::~httpClient()
{
mp_tcpConnectionPtr->close();
}
void httpClient::close()
{
mp_tcpConnectionPtr->close();
}
pion::http::response_ptr httpClient::blockingReceiveOrTimeout(pion::http::request httpRequest, boost::system::error_code& error_code_ref)
{
// reinit
mp_curr_httpResponsePtr.reset();
mp_ioService.reset();
error_code_ref.clear();
// connect to server if not already connectec
if (!mp_tcpConnectionPtr->is_open())
{
error_code_ref = mp_tcpConnectionPtr->connect(mp_serverIP, mp_serverPort);
}
if (!error_code_ref)
{
// send Request
httpRequest.send(*mp_tcpConnectionPtr.get(), error_code_ref, false);
if (!error_code_ref)
{
// asynchronously wait for response (times out automatically)
pion::http::response_reader::finished_handler_t responseReaderFinishHandler = boost::bind(&httpClient::finishedReceiveResponse, this, _1, _3);
const pion::http::request constHTTPRequest = httpRequest;
pion::http::response_reader_ptr httpResponseReaderPtr = pion::http::response_reader::create(
mp_tcpConnectionPtr,
constHTTPRequest,
responseReaderFinishHandler);
httpResponseReaderPtr->receive();
mp_ioService.run();
}
}
return mp_curr_httpResponsePtr;
}
void httpClient::finishedReceiveResponse(pion::http::response_ptr httpResponsePtr, const boost::system::error_code& error_code_ref)
{
mp_curr_httpResponsePtr = httpResponsePtr;
}
for completness main.cpp:
#include <iostream>
#include "httpClient.hpp"
int main (int argc, char *argv[])
{
pion::http::request request;
std::string requestData = "asdf";
request.set_content(requestData);
httpClient client(boost::asio::ip::address::from_string("10.1.1.100"), 80);
pion::http::response_ptr response;
boost::system::error_code ec;
response = client.blockingReceiveOrTimeout(request, ec);
response->write(std::cout, ec);
return 0;
}
I am not familiar with pion-net. However, based on the version I think is being used per the naming conventions, a quick scan through the pion-net code looks as though the io_service.run() needs to be invoked in the application code. The only place where I found pion-net explicitly invoking io_service.run() was in the PionScheduler types used by the Server types.