I've put together an asynchronous UDP client (code below) which is throwing the error "vector iterator not dereferenceable" after about 4 seconds of running. There appears to be little information on the exact cause of this error within the boost asio context but it would seem that it may be due to a buffer going out of scope.
I'm using shared pointers and I can't see anywhere that would allow them to go out of scope.
Call stack:
std::_Debug_message(const wchar_t * message, const wchar_t * file, unsigned int line) Line 15 C++
std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<unsigned char> > >::operator*() Line 72 C++
std::_Vector_iterator<std::_Vector_val<std::_Simple_types<unsigned char> > >::operator*() Line 325 C++
boost::asio::detail::buffer_debug_check<std::_Vector_iterator<std::_Vector_val<std::_Simple_types<unsigned char> > > >::operator()() Line 532 C++
[External Code]
boost::asio::detail::buffer_cast_helper(const boost::asio::mutable_buffer & b) Line 146 C++
boost::asio::buffer_cast<void const *>(const boost::asio::mutable_buffer & b) Line 427 C++
boost::asio::detail::buffer_sequence_adapter<boost::asio::const_buffer,boost::asio::mutable_buffers_1>::validate(const boost::asio::mutable_buffers_1 & buffer_sequence) Line 207 C++
boost::asio::detail::win_iocp_socket_send_op<boost::asio::mutable_buffers_1,boost::_bi::bind_t<void,boost::_mfi::mf2<void,client_t,boost::system::error_code const &,unsigned int>,boost::_bi::list3<boost::_bi::value<client_t *>,boost::arg<1>,boost::arg<2> > > >::do_complete(boost::asio::detail::win_iocp_io_service * owner, boost::asio::detail::win_iocp_operation * base, const boost::system::error_code & result_ec, unsigned int bytes_transferred) Line 70 C++
boost::asio::detail::win_iocp_operation::complete(boost::asio::detail::win_iocp_io_service & owner, const boost::system::error_code & ec, unsigned int bytes_transferred) Line 46 C++
boost::asio::detail::win_iocp_io_service::do_one(bool block, boost::system::error_code & ec) Line 406 C++
boost::asio::detail::win_iocp_io_service::run(boost::system::error_code & ec) Line 164 C++
boost::asio::io_service::run() Line 59 C++
boost::_mfi::mf0<unsigned int,boost::asio::io_service>::operator()(boost::asio::io_service * p) Line 50 C++
boost::_bi::list1<boost::_bi::value<boost::asio::io_service *> >::operator()<unsigned int,boost::_mfi::mf0<unsigned int,boost::asio::io_service>,boost::_bi::list0>(boost::_bi::type<unsigned int> __formal, boost::_mfi::mf0<unsigned int,boost::asio::io_service> & f, boost::_bi::list0 & a, long __formal) Line 250 C++
boost::_bi::bind_t<unsigned int,boost::_mfi::mf0<unsigned int,boost::asio::io_service>,boost::_bi::list1<boost::_bi::value<boost::asio::io_service *> > >::operator()() Line 1223 C++
boost::detail::thread_data<boost::_bi::bind_t<unsigned int,boost::_mfi::mf0<unsigned int,boost::asio::io_service>,boost::_bi::list1<boost::_bi::value<boost::asio::io_service *> > > >::run() Line 117 C++
boost::`anonymous namespace'::thread_start_function(void * param) Line 303 C++
[External Code]
It looks like it's occurring during the 'do_send' function call 'async_send_to' but the shared pointer is stored in sended so I don't know how that could be getting deleted?
And the class:
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>
#include <boost/thread.hpp>
#include <boost/lockfree/spsc_queue.hpp>
#include <boost/atomic.hpp>
#include <string>
#include <vector>
namespace asio = boost::asio;
namespace posix_time = boost::posix_time;
namespace lockfree = boost::lockfree;
typedef std::vector<uint8_t> msg_t;
typedef boost::shared_ptr<msg_t> spmsg_t;
typedef boost::shared_ptr<asio::io_service::work> pwork_t;
typedef lockfree::spsc_queue<spmsg_t> msg_queue_t;
struct client_t {
asio::io_service &_svc;
asio::ip::udp::socket _socket;
asio::ip::udp::endpoint _ep;
boost::atomic<bool> _connected;
boost::atomic<bool> _closing;
boost::thread _worker;
pwork_t _work;
msg_queue_t _send_queue;
msg_queue_t _recv_queue;
boost::condition_variable m_cond;
boost::mutex m_Mutex;
client_t(asio::io_service &svc_) :_svc(svc_), _send_queue(1024), _recv_queue(1024), _socket(svc_, boost::asio::ip::udp::endpoint(boost::asio::ip::udp::v4(), 0)){}
void init(const std::string ip_, const std::string port_) {
_closing = false;
sending = false;
_connected = true;
boost::asio::ip::udp::resolver resolver(_svc);
_ep = *resolver.resolve({ boost::asio::ip::udp::v4(), ip_, port_ });
_work = pwork_t(new asio::io_service::work(_svc));
_worker.swap(boost::move(boost::thread(boost::bind(&asio::io_service::run, &_svc))));
_svc.post(boost::bind(&client_t::do_recv, this));
}
void destroy() {
_work.reset();
_worker.join();
_send_queue.reset();
_recv_queue.reset();
}
bool is_connected() {
return !_closing && _connected;
}
void disconnect() {
_closing = true;
}
boost::atomic<bool> sending;
spmsg_t sended;
void do_send() {
if (_send_queue.pop(sended)) {
_socket.async_send_to(asio::buffer(*sended), _ep, boost::bind(&client_t::handle_sended, this, asio::placeholders::error, asio::placeholders::bytes_transferred));
}
else {
sending.store(false);
if (_closing) {
_socket.shutdown(asio::ip::tcp::socket::shutdown_both);
_socket.close();
_send_queue.reset();
_recv_queue.reset();
_connected = false;
_closing = false;
}
}
}
void handle_sended(const boost::system::error_code &ec_, std::size_t bytes_transferred) {
if (ec_) {
sending.store(false);
printf("%s\n", ec_.message().c_str());
return;
}
do_send();
}
spmsg_t recv_msg;
void do_recv() {
recv_msg = boost::make_shared<msg_t>(2048);
_socket.async_receive_from(asio::buffer(recv_msg->data() + 4, 2044), _ep, boost::bind(&client_t::handle_recv, this, asio::placeholders::error, asio::placeholders::bytes_transferred));
}
void handle_recv(const boost::system::error_code &ec_, std::size_t bytes_transferred_) {
if (ec_) {
printf("%s\n",ec_.message().c_str());
return;
}
*(uint32_t*)recv_msg->data() = bytes_transferred_;
if (_recv_queue.push(recv_msg)) {
m_cond.notify_one();
do_recv();
}
else {
disconnect();
}
}
//spmsg_t get_sended(uint32_t len_) { return spmsg_t(new msg_t(len_ + 4)); }
bool send(unsigned char* msg, int msgSize) {
if (!is_connected())
return false;
spmsg_t msg_ = spmsg_t(new msg_t(msgSize));
memcpy(&msg_->data()[0], msg, msgSize);
if (!_send_queue.push(msg_))
return false;
//send_messages();
return true;
}
bool send_messages() {
if (!is_connected())
return false;
static bool f = false;
if (!sending.compare_exchange_strong(f, true))
return false;
_svc.post(boost::bind(&client_t::do_send, this));
}
spmsg_t recv() {
if (!is_connected())
return NULL;
spmsg_t recved;
if (_recv_queue.pop(recved))
return recved;
else{
boost::mutex::scoped_lock lock(m_Mutex);
if (m_cond.timed_wait(lock, boost::posix_time::milliseconds(5000)))
{
_recv_queue.pop(recved);
return recved;
}
else
return NULL;
}
}
};
Related
I'm working on a RS485 communication class and I'm trying to make a function that reads until a certain char is on the line, but with a time out. The problem is that my system timer immediately returns, doesn't matter which time out I enter. I tried changing the timer to be a member variable of the class, so it doesn't go out of scope, but that wasn't the problem. I tried different implementations of timers (deadline_timer mostly) but that didn't help. If I remove the timer from the code, then the read succeeds, but when I add it, even if I give it a timeout of 10 seconds (which should be waay more than enough), it will respond with an immediate timeout.
I tried making a simple version of the class here, but I guess that the options mostly depend on the type of machine you're talking to:
class RS485CommunicationLayer final {
public:
RS485CommunicationLayer(
const std::string& path,
/* options */
): io(), port(io), timer(port.get_io_service()) {
open(/* options */);
};
std::size_t write(const char* const buffer, const size_t size) {
/*impl*/
}
// THIS FUNCTION --v
void readUntil(std::vector<char>& buffer, char delim,std::chrono::microseconds timeout) {
boost::optional<boost::system::error_code> timer_result;
boost::optional<boost::system::error_code> read_result;
port.get_io_service().reset();
timer.expires_from_now(timeout);
boost::asio::async_read_until(port, asio::dynamic_buffer(buffer), delim, [&read_result] (const boost::system::error_code& error, size_t) { read_result.reset(error); });
timer.async_wait([&timer_result] (const boost::system::error_code& error) { timer_result.reset(error); });
while (port.get_io_service().run_one())
{
if (read_result)
timer.cancel();
else if (timer_result) {
port.cancel();
}
}
if (read_result)
throw boost::system::system_error(*read_result);
};
private:
asio::io_context io;
asio::serial_port port;
boost::asio::system_timer timer;
void open(/*args*/) {
port.open(path);
/*set options*/
}
};
Edit:
I also tried the following implementation after finding out that run_for() exists. But then the buffer stays empty weirdly enough.
void RS485CommunicationLayer::readUntil(std::vector<char>& buffer, char delim, std::chrono::microseconds timeout) {
boost::optional<boost::system::error_code> read_result;
boost::asio::async_read_until(port, asio::dynamic_buffer(buffer), delim, [&read_result] (const boost::system::error_code& error, size_t) { read_result.reset(error); });
port.get_io_service().run_for(timeout);
if (read_result)
throw boost::system::system_error(*read_result);
}
First off, get_io_service() indicates a Very Old(TM) boost version. Also, it just returns io.
Secondly, why so complicated? I don't even really have the energy to see whether there is a subtle problem with the run_one() loop (it looks fine at a glance).
I'd simplify:
size_t readUntil(std::vector<char>& buffer, char delim,
std::chrono::microseconds timeout) {
error_code read_result;
size_t msglen = 0;
io.reset();
asio::system_timer timer(io, timeout);
asio::async_read_until(port, asio::dynamic_buffer(buffer), delim,
[&](error_code ec, size_t n) {
timer.cancel();
read_result = ec;
msglen = n;
});
timer.async_wait([&](error_code ec) { if (!ec) port.cancel(); });
io.run();
if (read_result)
boost::throw_with_location(boost::system::system_error(read_result),
read_result.location());
return msglen;
}
You can just cancel the complementary IO object from the respective completion handlers.
The timer is per-op and local to the readUntil, so it doesn't have to be a member.
Let's also throw in the write side, which is all of:
size_t write(char const* const data, const size_t size) {
return asio::write(port, asio::buffer(data, size));
}
And I can demo it working:
Live On Coliru
#include <boost/asio.hpp>
#include <iomanip>
#include <iostream>
namespace asio = boost::asio;
using boost::system::error_code;
using namespace std::chrono_literals;
class RS485CommunicationLayer final {
public:
RS485CommunicationLayer(std::string const& path) : io(), port(io) { open(path); };
size_t write(char const* const data, const size_t size) {
return asio::write(port, asio::buffer(data, size));
}
size_t readUntil(std::vector<char>& buffer, char delim,
std::chrono::microseconds timeout) {
error_code read_result;
size_t msglen = 0;
io.reset();
asio::system_timer timer(io, timeout);
asio::async_read_until(port, asio::dynamic_buffer(buffer), delim,
[&](error_code ec, size_t n) {
timer.cancel();
read_result = ec;
msglen = n;
});
timer.async_wait([&](error_code ec) { if (!ec) port.cancel(); });
io.run();
if (read_result)
boost::throw_with_location(boost::system::system_error(read_result),
read_result.location());
return msglen;
}
private:
asio::io_context io;
asio::serial_port port;
void open(std::string path) {
port.open(path);
/*set options*/
}
void close();
};
int main(int argc, char** argv) {
RS485CommunicationLayer comm(argc > 1 ? argv[1] : "");
comm.write("Hello world\n", 12);
for (std::vector<char> response_buffer;
auto len = comm.readUntil(response_buffer, '\n', 100ms);) //
{
std::cout << "Received " << response_buffer.size() << " bytes, next "
<< quoted(std::string_view(response_buffer.data(), len - 1))
<< std::endl;
// consume
response_buffer.erase(begin(response_buffer), begin(response_buffer) + len);
}
}
Demo locally with a socat PTS tunnel:
socat -d -d pty,raw,echo=0 pty,raw,echo=0
And throwing dictionaries at the other end:
while true; do cat /etc/dictionaries-common/words ; done | pv > /dev/pts/10
i have a question about maximum size of buffer in handler function which is called in boost::bind method.
I have a tcp socket client:
chat_client(boost::asio::io_service& io_service,
tcp::resolver::iterator endpoint_iterator)
: io_service_(io_service),
socket_(io_service),
t(io_service)
{
boost::asio::async_connect(socket_, endpoint_iterator,
boost::bind(&chat_client::handle_connect, this,
boost::asio::placeholders::error));
}
in chat_client class, i create a method to write request to socket buffer
void set_timer(boost::posix_time::ptime time, boost::function<void(const
boost::system::error_code&)> handler)
{
t.expires_at(time);
t.async_wait(handler);
}
void write(const chat_message& msg)
{
set_timer(boost::posix_time::microsec_clock::universal_time() +
boost::posix_time::seconds(1),
boost::bind(&chat_client::do_write, this, msg,
boost::asio::placeholders::error));
io_service_.run();
}
void do_write(chat_message msg, const boost::system::error_code& error)
{
std::cout << "dcm" << std::endl;
bool write_in_progress = !write_msgs_.empty();
write_msgs_.push_back(msg);
if (!write_in_progress)
{
boost::asio::async_write(socket_,
boost::asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
boost::bind(&chat_client::handle_write, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
void handle_write(const boost::system::error_code& error, size_t
bytes_transferred)
{
if (!error)
{
write_msgs_.pop_front();
if (!write_msgs_.empty())
{
boost::asio::async_write(socket_,
boost::asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
boost::bind(&chat_client::handle_write, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
else
{
do_close();
}
}
My request content is packaged in an instance of chat_message class. And here is chat_message class:
class chat_message
{
public:
enum { header_length = 7 };
enum { max_body_length = 0x1FFFFF };
chat_message()
: body_length_(0)
{
}
const char* data() const
{
return data_;
}
char* data()
{
return data_;
}
size_t length() const
{
return header_length + body_length_;
}
const char* body() const
{
return data_ + header_length;
}
char* body()
{
return data_ + header_length;
}
size_t body_length() const
{
return body_length_;
}
void body_length(size_t new_length)
{
body_length_ = new_length;
if (body_length_ > max_body_length)
body_length_ = max_body_length;
}
bool decode_header()
{
using namespace std; // For strncat and atoi.
char header[header_length + 1] = "";
strncat(header, data_, header_length);
body_length_ = atoi(header);
if (body_length_ > max_body_length)
{
body_length_ = 0;
return false;
}
return true;
}
void encode_header()
{
using namespace std; // For sprintf and memcpy.
char header[header_length + 1] = "";
sprintf(header, "%4d", static_cast<int>(body_length_));
memcpy(data_, header, header_length);
}
private:
char data_[header_length + max_body_length];
size_t body_length_;
};
My issue is when i set max_body_length > 0xFFFFF, boost::bind() method in write() function lead to segmentation fault. So i doubt that, boost::bind() method has a maximum of buffersize. Can someone explain it for me? Thank you
The problem is not in boost::bind() but in the following:
char data_[header_length + max_body_length];
This line will compile alright but can crash when elements get accessed. I would strongly recommend not to use such a huge array of chars here. As one of the ways to keep it you might think about dynamic allocation. Or move from array at all. STL and BOOST libraries provide a lot of tools to safely handle strings and collections.
I am trying to implement double buffering for my network server when it sends to clients. The idea came from
boost::asio::async_write - ensure only one outstanding call
Unfortunately, as great as it looks, I am getting an error when I try to run it. AFAIK, I am not using any iterators, so I assume the async_write is when it attempts to complete, but I don't know what is really causing the problem.
The error occurs after the first async_write is posted.
The error is "vector iterator not dereferencable"
and comes from this source in vector's implementation
reference operator*() const
{ // return designated object
#if _ITERATOR_DEBUG_LEVEL == 2
const auto _Mycont = static_cast<const _Myvec *>(this->_Getcont());
if (_Mycont == 0
|| _Ptr == _Tptr()
|| _Ptr < _Mycont->_Myfirst
|| _Mycont->_Mylast <= _Ptr)
{ // report error
_DEBUG_ERROR("vector iterator not dereferencable");
_SCL_SECURE_OUT_OF_RANGE;
}
The call stack is:
msvcp140d.dll!00007ff9f9f40806() Unknown
ConsoleApplication2.exe!std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<char> > >::operator*() Line 74 C++
ConsoleApplication2.exe!boost::asio::detail::buffer_debug_check<std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<char> > > >::operator()() Line 532 C++
ConsoleApplication2.exe!std::_Invoker_functor::_Call<boost::asio::detail::buffer_debug_check<std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<char> > > > & __ptr64>(boost::asio::detail::buffer_debug_check<std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<char> > > > & _Obj) Line 1377 C++
ConsoleApplication2.exe!std::invoke<boost::asio::detail::buffer_debug_check<std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<char> > > > & __ptr64>(boost::asio::detail::buffer_debug_check<std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<char> > > > & _Obj) Line 1445 C++
ConsoleApplication2.exe!std::_Invoke_ret<void,boost::asio::detail::buffer_debug_check<std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<char> > > > & __ptr64>(std::_Forced<void,1> __formal, boost::asio::detail::buffer_debug_check<std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<char> > > > & <_Vals_0>) Line 1462 C++
ConsoleApplication2.exe!std::_Func_impl<boost::asio::detail::buffer_debug_check<std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<char> > > >,std::allocator<int>,void>::_Do_call() Line 214 C++
ConsoleApplication2.exe!std::_Func_class<void>::operator()() Line 280 C++
ConsoleApplication2.exe!boost::asio::detail::buffer_cast_helper(const boost::asio::const_buffer & b) Line 276 C++
ConsoleApplication2.exe!boost::asio::buffer_cast<void const * __ptr64>(const boost::asio::const_buffer & b) Line 435 C++
ConsoleApplication2.exe!boost::asio::buffer(const boost::asio::const_buffer & b, unsigned __int64 max_size_in_bytes) Line 751 C++
ConsoleApplication2.exe!boost::asio::detail::consuming_buffers_iterator<boost::asio::const_buffer,std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<boost::asio::const_buffer> > > >::consuming_buffers_iterator<boost::asio::const_buffer,std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<boost::asio::const_buffer> > > >(bool at_end, const boost::asio::const_buffer & first, std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<boost::asio::const_buffer> > > begin_remainder, std::_Vector_const_iterator<std::_Vector_val<std::_Simple_types<boost::asio::const_buffer> > > end_remainder, unsigned __int64 max_size) Line 62 C++
ConsoleApplication2.exe!boost::asio::detail::consuming_buffers<boost::asio::const_buffer,std::vector<boost::asio::const_buffer,std::allocator<boost::asio::const_buffer> > >::begin() Line 210 C++
ConsoleApplication2.exe!boost::asio::detail::buffer_sequence_adapter<boost::asio::const_buffer,boost::asio::detail::consuming_buffers<boost::asio::const_buffer,std::vector<boost::asio::const_buffer,std::allocator<boost::asio::const_buffer> > > >::validate(const boost::asio::detail::consuming_buffers<boost::asio::const_buffer,std::vector<boost::asio::const_buffer,std::allocator<boost::asio::const_buffer> > > & buffer_sequence) Line 145 C++
ConsoleApplication2.exe!boost::asio::detail::win_iocp_socket_send_op<boost::asio::detail::consuming_buffers<boost::asio::const_buffer,std::vector<boost::asio::const_buffer,std::allocator<boost::asio::const_buffer> > >,boost::asio::detail::write_op<boost::asio::basic_stream_socket<boost::asio::ip::tcp,boost::asio::stream_socket_service<boost::asio::ip::tcp> >,std::vector<boost::asio::const_buffer,std::allocator<boost::asio::const_buffer> >,boost::asio::detail::transfer_all_t,void <lambda>(const boost::system::error_code &, unsigned __int64) > >::do_complete(boost::asio::detail::win_iocp_io_service * owner, boost::asio::detail::win_iocp_operation * base, const boost::system::error_code & result_ec, unsigned __int64 bytes_transferred) Line 74 C++
ConsoleApplication2.exe!boost::asio::detail::win_iocp_operation::complete(boost::asio::detail::win_iocp_io_service & owner, const boost::system::error_code & ec, unsigned __int64 bytes_transferred) Line 47 C++
ConsoleApplication2.exe!boost::asio::detail::win_iocp_io_service::do_one(bool block, boost::system::error_code & ec) Line 406 C++
ConsoleApplication2.exe!boost::asio::detail::win_iocp_io_service::run(boost::system::error_code & ec) Line 164 C++
ConsoleApplication2.exe!boost::asio::io_service::run() Line 59 C++
ConsoleApplication2.exe!ConnectionManager::IoServiceThreadProc() Line 83 C++
[External Code]
My minimal compilable example, which is a network server that just tried to send a 'beep#' on a timer is:
Connection.h
#pragma once
#include <boost/asio.hpp>
#include <atomic>
#include <condition_variable>
#include <memory>
#include <mutex>
//--------------------------------------------------------------------
class ConnectionManager;
//--------------------------------------------------------------------
class Connection : public std::enable_shared_from_this<Connection>
{
public:
typedef std::shared_ptr<Connection> SharedPtr;
// Ensure all instances are created as shared_ptr in order to fulfill requirements for shared_from_this
static Connection::SharedPtr Create(ConnectionManager * connectionManager, boost::asio::ip::tcp::socket & socket);
Connection(const Connection &) = delete;
Connection(Connection &&) = delete;
Connection & operator = (const Connection &) = delete;
Connection & operator = (Connection &&) = delete;
~Connection();
// We have to defer the start until we are fully constructed because we share_from_this()
void Start();
void Stop();
void Send(const std::vector<char> & data);
private:
ConnectionManager * m_owner;
boost::asio::ip::tcp::socket m_socket;
std::atomic<bool> m_stopped;
boost::asio::streambuf m_receiveBuffer;
mutable std::mutex m_sendMutex;
std::vector<boost::asio::const_buffer> m_sendBuffers[2]; // Double buffer
int m_activeSendBufferIndex;
bool m_sending;
std::vector<char> m_allReadData;
Connection(ConnectionManager * connectionManager, boost::asio::ip::tcp::socket socket);
void DoReceive();
void DoSend();
};
//--------------------------------------------------------------------
Connection.cpp
#include "Connection.h"
#include "ConnectionManager.h"
#include "Logger.h"
#include <boost/bind.hpp>
#include <algorithm>
#include <cstdio>
//--------------------------------------------------------------------
Connection::SharedPtr Connection::Create(ConnectionManager * connectionManager, boost::asio::ip::tcp::socket & socket)
{
return Connection::SharedPtr(new Connection(connectionManager, std::move(socket)));
}
//--------------------------------------------------------------------
Connection::Connection(ConnectionManager * connectionManager, boost::asio::ip::tcp::socket socket)
:
m_owner (connectionManager)
, m_socket (std::move(socket))
, m_stopped (false)
, m_receiveBuffer ()
, m_sendMutex ()
, m_sendBuffers ()
, m_activeSendBufferIndex (0)
, m_sending (false)
, m_allReadData ()
{
}
//--------------------------------------------------------------------
Connection::~Connection()
{
// Boost uses RAII, so we don't have anything to do. Let thier destructors take care of business
}
//--------------------------------------------------------------------
void Connection::Start()
{
DoReceive();
}
//--------------------------------------------------------------------
void Connection::Stop()
{
// The entire connection class is only kept alive, because it is a shared pointer and always has a ref count
// as a consequence of the outstanding async receive call that gets posted every time we receive.
// Once we stop posting another receive in the receive handler and once our owner release any references to
// us, we will get destroyed.
m_stopped = true;
m_owner->OnConnectionClosed(shared_from_this());
}
//--------------------------------------------------------------------
void Connection::Send(const std::vector<char> & data)
{
std::lock_guard<std::mutex> lock(m_sendMutex);
// Append to the inactive buffer
m_sendBuffers[m_activeSendBufferIndex ^ 1].push_back(boost::asio::buffer(data));
//
DoSend();
}
//--------------------------------------------------------------------
void Connection::DoSend()
{
// Check if there is an async send in progress
// An empty active buffer indicates there is no outstanding send
if (m_sendBuffers[m_activeSendBufferIndex].empty())
{
m_activeSendBufferIndex ^= 1;
boost::asio::async_write(m_socket, m_sendBuffers[m_activeSendBufferIndex],
[this](const boost::system::error_code & errorCode, size_t bytesTransferred)
{
std::lock_guard<std::mutex> lock(m_sendMutex);
m_sendBuffers[m_activeSendBufferIndex].clear();
if (errorCode)
{
printf("An error occured while attemping to send data to a connection. Error Code: %d", errorCode.value());
// An error occurred
// We do not stop or close on sends, but instead let the receive error out and then close
return;
}
// Check if there is more to send that has been queued up on the inactive buffer,
// while we were sending what was on the active buffer
if (!m_sendBuffers[m_activeSendBufferIndex ^ 1].empty())
{
DoSend();
}
});
}
}
//--------------------------------------------------------------------
void Connection::DoReceive()
{
auto self(shared_from_this());
boost::asio::async_read_until(m_socket, m_receiveBuffer, '#',
[self](const boost::system::error_code & errorCode, size_t bytesRead)
{
if (errorCode)
{
printf("An error occured while attemping to receive data from connection. Error Code: %d", errorCode.value());
// Notify our masters that we are ready to be destroyed
self->m_owner->OnConnectionClosed(self);
// An error occured
return;
}
// Grab the read data
std::istream stream(&self->m_receiveBuffer);
std::string data;
std::getline(stream, data, '#');
data += "#";
printf("Received data from client: %s", data.c_str());
// Issue the next receive
if (!self->m_stopped)
{
self->DoReceive();
}
});
}
//--------------------------------------------------------------------
ConnectionManager.h
#pragma once
#include "Connection.h"
// Boost Includes
#include <boost/asio.hpp>
// Standard Includes
#include <thread>
#include <vector>
//--------------------------------------------------------------------
class ConnectionManager
{
public:
ConnectionManager(unsigned port, size_t numThreads);
ConnectionManager(const ConnectionManager &) = delete;
ConnectionManager(ConnectionManager &&) = delete;
ConnectionManager & operator = (const ConnectionManager &) = delete;
ConnectionManager & operator = (ConnectionManager &&) = delete;
~ConnectionManager();
void Start();
void Stop();
void OnConnectionClosed(Connection::SharedPtr connection);
protected:
boost::asio::io_service m_io_service;
boost::asio::ip::tcp::acceptor m_acceptor;
boost::asio::ip::tcp::socket m_listenSocket;
std::vector<std::thread> m_threads;
mutable std::mutex m_connectionsMutex;
std::vector<Connection::SharedPtr> m_connections;
boost::asio::deadline_timer m_timer;
void IoServiceThreadProc();
void DoAccept();
void DoTimer();
};
//--------------------------------------------------------------------
ConnectionManager.cpp
#include "ConnectionManager.h"
#include "Logger.h"
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include<cstdio>
#include <system_error>
//------------------------------------------------------------------------------
ConnectionManager::ConnectionManager(unsigned port, size_t numThreads)
:
m_io_service ()
, m_acceptor (m_io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))
, m_listenSocket(m_io_service)
, m_threads (numThreads)
, m_timer (m_io_service)
{
}
//------------------------------------------------------------------------------
ConnectionManager::~ConnectionManager()
{
Stop();
}
//------------------------------------------------------------------------------
void ConnectionManager::Start()
{
if (m_io_service.stopped())
{
m_io_service.reset();
}
DoAccept();
for (auto & thread : m_threads)
{
if (!thread.joinable())
{
thread.swap(std::thread(&ConnectionManager::IoServiceThreadProc, this));
}
}
DoTimer();
}
//------------------------------------------------------------------------------
void ConnectionManager::Stop()
{
{
std::lock_guard<std::mutex> lock(m_connectionsMutex);
m_connections.clear();
}
// TODO - Will the stopping of the io_service be enough to kill all the connections and ultimately have them get destroyed?
// Because remember they have outstanding ref count to thier shared_ptr in the async handlers
m_io_service.stop();
for (auto & thread : m_threads)
{
if (thread.joinable())
{
thread.join();
}
}
}
//------------------------------------------------------------------------------
void ConnectionManager::IoServiceThreadProc()
{
try
{
// Log that we are starting the io_service thread
{
printf("io_service socket thread starting.");
}
// Run the asynchronous callbacks from the socket on this thread
// Until the io_service is stopped from another thread
m_io_service.run();
}
catch (std::system_error & e)
{
printf("System error caught in io_service socket thread. Error Code: %d", e.code().value());
}
catch (std::exception & e)
{
printf("Standard exception caught in io_service socket thread. Exception: %s", e.what());
}
catch (...)
{
printf("Unhandled exception caught in io_service socket thread.");
}
{
printf("io_service socket thread exiting.");
}
}
//------------------------------------------------------------------------------
void ConnectionManager::DoAccept()
{
m_acceptor.async_accept(m_listenSocket,
[this](const boost::system::error_code errorCode)
{
if (errorCode)
{
printf(, "An error occured while attemping to accept connections. Error Code: %d", errorCode.value());
return;
}
// Create the connection from the connected socket
std::lock_guard<std::mutex> lock(m_connectionsMutex);
Connection::SharedPtr connection = Connection::Create(this, m_listenSocket);
m_connections.push_back(connection);
connection->Start();
DoAccept();
});
}
//------------------------------------------------------------------------------
void ConnectionManager::OnConnectionClosed(Connection::SharedPtr connection)
{
std::lock_guard<std::mutex> lock(m_connectionsMutex);
auto itConnection = std::find(m_connections.begin(), m_connections.end(), connection);
if (itConnection != m_connections.end())
{
m_connections.erase(itConnection);
}
}
//------------------------------------------------------------------------------
void ConnectionManager::DoTimer()
{
if (!m_io_service.stopped())
{
// Send messages every second
m_timer.expires_from_now(boost::posix_time::seconds(30));
m_timer.async_wait(
[this](const boost::system::error_code & errorCode)
{
std::lock_guard<std::mutex> lock(m_connectionsMutex);
for (auto connection : m_connections)
{
connection->Send(std::vector<char>{'b', 'e', 'e', 'p', '#'});
}
DoTimer();
});
}
}
main.cpp
#include "ConnectionManager.h"
#include <cstring>
#include <iostream>
#include <string>
int main()
{
ConnectionManager connectionManager(4000, 2);
connectionManager.Start();
std::this_thread::sleep_for(std::chrono::minutes(5));
connectionManager.Stop();
return 0;
}
I've got a vector of buffers and there are two of them, one is active and once is inactive. The inactive is appended to while there is an outstanding async write posted. Then the handler for the async write clears the active buffer, which should have been sent. It all looks OK to me. I've been looking at it all day yesterday.
Does Anyone else have any idea what I did wrong? I really am quite clueless on how to use these buffers properly.
Problem may be in this line
connection->Send(std::vector<char>{'b', 'e', 'e', 'p', '#'});
you create temporary object of vector which is passed to Send method by const reference, but in your Send method you are using boost::asio::buffer(data) but buffer creates object which contains only pointer to data and size of data. After calling Send method temporary vector is deleted, and your buffers are invalid when you are sending data in DoSend method using async_write.
I have a segmentation fault in the following code ( on _answers.push_back(tmp); ).
Gdb said
(gdb) p tmp
$7 = "HTTP/1.0 200 OK\r\nContent-Type: text/plain; charset=UTF-8\r\nSet-Cookie: color=black;path=/\r\nSet-Cookie: code=f69a2d941420d23be97bbb1ae963295647a91c4f3faf9c5fa80727399927d9d5;path=/\r\nSet-Cookie: game=c1e"...
(gdb) call _answers.size()
$8 = 271275648142580811
So I guess the array has been corrupted. But I don't know where it happened.
// Network.hpp
#pragma once
#include <utility>
#include <string>
#include <vector>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
class Network
{
public:
Network(std::string const &, std::string const &);
~Network() {};
void init();
void connect();
void update();
void sendQuery(const std::string);
bool isConnected();
void reset();
std::string getAnswer();
void handleRead(const boost::system::error_code &, size_t);
void handleWrite(const boost::system::error_code &);
boost::asio::io_service _io_service;
private:
boost::asio::ip::tcp::resolver _resolver;
boost::asio::ip::tcp::socket _sock;
boost::asio::ip::tcp::resolver::iterator _it;
char _buff[2048];
std::vector<std::string> _answers;
std::string const &_host;
std::string const &_port;
bool _answered;
};
// Network.cpp
Network::Network(std::string const &host, std::string const &port) : _resolver(_io_service), _sock(_io_service), _host(host), _port(port), _answered(true) {}
void Network::connect() {
_answers.reserve(2048);
boost::asio::ip::tcp::resolver::query query(_host, _port);
boost::asio::ip::tcp::resolver::iterator iterator = _resolver.resolve(query);
boost::asio::connect(_sock.lowest_layer(), iterator);
}
void Network::handleRead(const boost::system::error_code &err, size_t bread) {
_answered = true;
if (err && err.value() != 2)
throw Gomoku::NetworkException(err.message());
if (bread > 0) {
std::string tmp(_buff);
_answers.push_back(tmp);
}
memset(_buff, 0, 2048);
}
void Network::handleWrite(const boost::system::error_code &err) {
if (err)
throw Gomoku::NetworkException(err.message());
}
void Network::reset() {
_io_service.poll();
_io_service.reset();
_answers.clear();
_answered = true;
}
void Network::sendQuery(const std::string req) {
_io_service.poll();
_io_service.reset();
if (_answered == 0)
return;
_answered = false;
connect();
const char *str = new char[req.length()];
str = req.c_str();
boost::asio::async_write(_sock, boost::asio::buffer(str, req.length()), boost::bind(&Network::handleWrite, this, boost::asio::placeholders::error));
boost::asio::async_read(_sock, boost::asio::buffer(_buff, 2048), boost::bind(&Network::handleRead, this, boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred));
}
std::string Network::getAnswer() {
if (_answers.empty())
return "";
std::string tmp = _answers.back();
_answers.pop_back();
return tmp;
}
// Player.cpp
Player::Player(std::string const &host, std::string const &port) : _network(host, port) {
_myTurn = false;
_whiteScore = _blackScore = 0;
_host = host + ":" + port;
initMap();
}
void Player::connect() {
std::string str = "GET /players/connect/ HTTP/1.0\r\nHost: " + _host + "\r\nAccept: */*\r\n\r\n";
_network.sendQuery(str);
}
void Player::sendClick(std::pair<int, int> click, std::string const &header) {
std::stringstream ss;
ss << "POST /game/play/" << click.first << "/" << click.second << header << _cookie << "\r\n\r\n";
std::string req = ss.str();
_network.sendQuery(req);
_network._io_service.run();
_network._io_service.reset();
std::string ans = _network.getAnswer();
parseAnswer(ans);
req = "GET /game/map.txt" + header + _cookie + "\r\n\r\n";
_network.sendQuery(req);
}
I also saw the following code by following the segv trace (basic_string.h:400):
: _M_dataplus(_M_local_data(), __str._M_get_allocator()) // TODO A traits
At first glance: std::string tmp(_buff); is wrong, because _buff may not be null terminated, thus reading 271275648142580811 bytes into memory.
Additionally, sendQuery's parameter is a local string, so as soon as the function exits, the string is free'd, and async_write continues to read from invalid memory, which is undefined behavior. Anything can happen.
Also, all requests use the same incoming buffer, so if multiple occur at the same time, you get undefined or useless behavior.
I am trying to create a class that will read and parse data from a Serial port, however I keep getting an instantiated error and I don't know why. The class takes in a serial port and its io_service. I am using boost. I am getting a ton of errors, but I think its because its cumulative (I think, I am not sure if that is correct). Here is the first one:
In file included from /usr/include/boost/bind.hpp:22:0,
from ../Sources/Magnetic Compensator Core.cpp:17:
/usr/include/boost/bind/bind.hpp: In instantiation of ‘boost::_bi::result_traits&, const boost::system::error_code&, unsigned int)>’:
/usr/include/boost/bind/bind_template.hpp:15:48: instantiated from ‘boost::_bi::bind_t&, const boost::system::error_code&, unsigned int), boost::_bi::list4, boost::reference_wrapper >, boost::arg<1>, boost::arg<2> > >’
Here is the code for the class:
class mag_serial
{
bool data_available;
boost::asio::serial_port& ser_port;
boost::asio::deadline_timer timeout;
char my_buffer[1];
std::string str;
std::string st;
void read_callback(bool& data_available, boost::asio::deadline_timer& timeout, const boost::system::error_code& error, std::size_t bytes_transferred)
{
data_available = true;
if(str.length() > 1)
{
if (!(str.at(str.length() - 1) == temp))//&str.at(str.length() - 1) != "#")
{
str.append(my_buffer,bytes_transferred);
if(str.at(str.length() - 1) == quit)
{
cout << "I am quitting";
Stop();
ser_port.cancel();
ser_port.close();
return;
}
i++;
}
else if (str.at(str.length() - 1) == temp)
{
st = str;//.substr(1, str.size());
// Processing Functions
}
}
else
{
str.append(my_buffer,bytes_transferred);
if(str.at(0) == quit)
{
cout << "I am quitting";
Stop();
ser_port.cancel();
ser_port.close();
return;
}
}
ser_port.async_read_some(boost::asio::buffer(my_buffer),
boost::bind(&mag_serial::read_callback, boost::ref(data_available),
boost::ref(timeout),boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
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;
}
}
public:
mag_serial(boost::asio::serial_port& ser_port, boost::asio::io_service& io_svc): ser_port(ser_port), timeout(ser_port.get_io_service()){}
void read_mag_serial_thread()
{
bool data_available = false;
ser_port.async_read_some(boost::asio::buffer(my_buffer),
boost::bind(&mag_serial::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(1));
timeout.async_wait(boost::bind(&mag_serial::wait_callback, boost::ref(ser_port),boost::asio::placeholders::error()));
io_svc.run();
if(!data_available)
{
ser_port.close();
cout << "ser_port was closed";
}
}
};
This won't compile
ser_port.async_read_some(boost::asio::buffer(my_buffer),
boost::bind(&mag_serial::read_callback, boost::ref(data_available),
boost::ref(timeout),boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
the member function mag_serial::read_callback needs an instance to bind to.
ser_port.async_read_some(
boost::asio::buffer(my_buffer),
boost::bind(
&mag_serial::read_callback,
this,
boost::ref(data_available),
boost::ref(timeout),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
)
);
Here's a coliru, I didn't attempt to fix the formatting.