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.
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 6 years ago.
Improve this question
I'm trying to adapt a sample code of a TCP Proxy in order to use it on my browser.
Here is the complete proxy i use :
#include <cstdlib>
#include <cstddef>
#include <iostream>
#include <string>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread/mutex.hpp>
namespace tcp_proxy
{
namespace ip = boost::asio::ip;
class bridge : public boost::enable_shared_from_this<bridge>
{
public:
typedef ip::tcp::socket socket_type;
typedef boost::shared_ptr<bridge> ptr_type;
bridge(boost::asio::io_service& ios)
: downstream_socket_(ios),
upstream_socket_(ios)
{}
socket_type& downstream_socket()
{
return downstream_socket_;
}
socket_type& upstream_socket()
{
return upstream_socket_;
}
void start(const std::string& upstream_host, unsigned short upstream_port)
{
upstream_socket_.async_connect(
ip::tcp::endpoint(
boost::asio::ip::address::from_string(upstream_host),
upstream_port),
boost::bind(&bridge::handle_upstream_connect,
shared_from_this(),
boost::asio::placeholders::error));
}
void handle_upstream_connect(const boost::system::error_code& error)
{
if (!error)
{
upstream_socket_.async_read_some(
boost::asio::buffer(upstream_data_,max_data_length),
boost::bind(&bridge::handle_upstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
downstream_socket_.async_read_some(
boost::asio::buffer(downstream_data_,max_data_length),
boost::bind(&bridge::handle_downstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
close();
}
private:
void handle_downstream_write(const boost::system::error_code& error)
{
if (!error)
{
upstream_socket_.async_read_some(
boost::asio::buffer(upstream_data_,max_data_length),
boost::bind(&bridge::handle_upstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
close();
}
void handle_downstream_read(const boost::system::error_code& error,
const size_t& bytes_transferred)
{
if (!error)
{
async_write(upstream_socket_,
boost::asio::buffer(downstream_data_,bytes_transferred),
boost::bind(&bridge::handle_upstream_write,
shared_from_this(),
boost::asio::placeholders::error));
}
else
close();
}
void handle_upstream_write(const boost::system::error_code& error)
{
if (!error)
{
downstream_socket_.async_read_some(
boost::asio::buffer(downstream_data_,max_data_length),
boost::bind(&bridge::handle_downstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
close();
}
void handle_upstream_read(const boost::system::error_code& error,
const size_t& bytes_transferred)
{
if (!error)
{
async_write(downstream_socket_,
boost::asio::buffer(upstream_data_,bytes_transferred),
boost::bind(&bridge::handle_downstream_write,
shared_from_this(),
boost::asio::placeholders::error));
}
else
close();
}
void close()
{
boost::mutex::scoped_lock lock(mutex_);
if (downstream_socket_.is_open())
{
downstream_socket_.close();
}
if (upstream_socket_.is_open())
{
upstream_socket_.close();
}
}
socket_type downstream_socket_;
socket_type upstream_socket_;
enum { max_data_length = 8192 }; //8KB
unsigned char downstream_data_[max_data_length];
unsigned char upstream_data_[max_data_length];
boost::mutex mutex_;
public:
class acceptor
{
public:
acceptor(boost::asio::io_service& io_service,
const std::string& local_host, unsigned short local_port,
const std::string& upstream_host, unsigned short upstream_port)
: io_service_(io_service),
localhost_address(boost::asio::ip::address_v4::from_string(local_host)),
acceptor_(io_service_,ip::tcp::endpoint(localhost_address,local_port)),
upstream_port_(upstream_port),
upstream_host_(upstream_host)
{}
bool accept_connections()
{
try
{
session_ = boost::shared_ptr<bridge>(new bridge(io_service_));
acceptor_.async_accept(session_->downstream_socket(),
boost::bind(&acceptor::handle_accept,
this,
boost::asio::placeholders::error));
}
catch(std::exception& e)
{
std::cerr << "acceptor exception: " << e.what() << std::endl;
return false;
}
return true;
}
private:
void handle_accept(const boost::system::error_code& error)
{
if (!error)
{
session_->start(upstream_host_,upstream_port_);
if (!accept_connections())
{
std::cerr << "Failure during call to accept." << std::endl;
}
}
else
{
std::cerr << "Error: " << error.message() << std::endl;
}
}
boost::asio::io_service& io_service_;
ip::address_v4 localhost_address;
ip::tcp::acceptor acceptor_;
ptr_type session_;
unsigned short upstream_port_;
std::string upstream_host_;
};
};
}
int main(int argc, char* argv[])
{
if (argc != 5)
{
std::cerr << "usage: tcpproxy_server <local host ip> <local port> <forward host ip> <forward port>" << std::endl;
return 1;
}
const unsigned short local_port = static_cast<unsigned short>(::atoi(argv[2]));
const unsigned short forward_port = static_cast<unsigned short>(::atoi(argv[4]));
const std::string local_host = argv[1];
const std::string forward_host = argv[3];
boost::asio::io_service ios;
try
{
tcp_proxy::bridge::acceptor acceptor(ios,
local_host, local_port,
forward_host, forward_port);
acceptor.accept_connections();
ios.run();
}
catch(std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
Actually, this sample code need as args the local host and local port, and the forward port and the forward host.
I would like to edit this code in order to get automatically the host who is asked in the browser...
From my research i have to used the remote_endpoint() property.
The problem is i don't know how to implement it in order to "trigger" this function eachtime use ask for another adress on the browser...
I tried this :
#include <cstdlib>
#include <cstddef>
#include <iostream>
#include <string>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread/mutex.hpp>
namespace tcp_proxy
{
namespace ip = boost::asio::ip;
class bridge : public boost::enable_shared_from_this<bridge>
{
public:
typedef ip::tcp::socket socket_type;
typedef boost::shared_ptr<bridge> ptr_type;
bridge(boost::asio::io_service& ios)
: downstream_socket_(ios),
upstream_socket_(ios)
{}
socket_type& downstream_socket()
{
return downstream_socket_;
}
socket_type& upstream_socket()
{
return upstream_socket_;
}
void start(const std::string& upstream_host, unsigned short upstream_port)
{
upstream_socket_.async_connect(
ip::tcp::endpoint(
boost::asio::ip::address::from_string(upstream_host),
upstream_port),
boost::bind(&bridge::handle_upstream_connect,
shared_from_this(),
boost::asio::placeholders::error));
}
void handle_upstream_connect(const boost::system::error_code& error)
{
if (!error)
{
upstream_socket_.async_read_some(
boost::asio::buffer(upstream_data_, max_data_length),
boost::bind(&bridge::handle_upstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
downstream_socket_.async_read_some(
boost::asio::buffer(downstream_data_, max_data_length),
boost::bind(&bridge::handle_downstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
close();
}
private:
void handle_downstream_write(const boost::system::error_code& error)
{
if (!error)
{
upstream_socket_.async_read_some(
boost::asio::buffer(upstream_data_, max_data_length),
boost::bind(&bridge::handle_upstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
close();
}
void handle_downstream_read(const boost::system::error_code& error,
const size_t& bytes_transferred)
{
if (!error)
{
async_write(upstream_socket_,
boost::asio::buffer(downstream_data_, bytes_transferred),
boost::bind(&bridge::handle_upstream_write,
shared_from_this(),
boost::asio::placeholders::error));
}
else
close();
}
void handle_upstream_write(const boost::system::error_code& error)
{
if (!error)
{
downstream_socket_.async_read_some(
boost::asio::buffer(downstream_data_, max_data_length),
boost::bind(&bridge::handle_downstream_read,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
close();
}
void handle_upstream_read(const boost::system::error_code& error,
const size_t& bytes_transferred)
{
if (!error)
{
async_write(downstream_socket_,
boost::asio::buffer(upstream_data_, bytes_transferred),
boost::bind(&bridge::handle_downstream_write,
shared_from_this(),
boost::asio::placeholders::error));
}
else
close();
}
void close()
{
boost::mutex::scoped_lock lock(mutex_);
if (downstream_socket_.is_open())
{
downstream_socket_.close();
}
if (upstream_socket_.is_open())
{
upstream_socket_.close();
}
}
socket_type downstream_socket_;
socket_type upstream_socket_;
enum { max_data_length = 8192 }; //8KB
unsigned char downstream_data_[max_data_length];
unsigned char upstream_data_[max_data_length];
boost::mutex mutex_;
public:
class acceptor
{
public:
acceptor(boost::asio::io_service& io_service,
const std::string& local_host, unsigned short local_port,
const std::string& upstream_host, unsigned short upstream_port)
: io_service_(io_service),
localhost_address(boost::asio::ip::address_v4::from_string(local_host)),
acceptor_(io_service_, ip::tcp::endpoint(localhost_address, local_port)),
upstream_port_(upstream_port),
upstream_host_(upstream_host)
{}
bool accept_connections()
{
try
{
session_ = boost::shared_ptr<bridge>(new bridge(io_service_));
acceptor_.async_accept(session_->downstream_socket(),
boost::bind(&acceptor::handle_accept,
this,
boost::asio::placeholders::error));
}
catch (std::exception& e)
{
std::cerr << "acceptor exception: " << e.what() << std::endl;
return false;
}
return true;
}
private:
void handle_accept(const boost::system::error_code& error)
{
if (!error)
{
session_->start(upstream_host_, upstream_port_);
if (!accept_connections())
{
std::cerr << "Failure during call to accept." << std::endl;
}
}
else
{
std::cerr << "Error: " << error.message() << std::endl;
}
}
boost::asio::io_service& io_service_;
ip::address_v4 localhost_address;
ip::tcp::acceptor acceptor_;
ptr_type session_;
unsigned short upstream_port_;
std::string upstream_host_;
};
};
}
int main(int argc, char* argv[])
{
if (argc != 5)
{
std::cerr << "usage: EvilProxy.exe <local host ip> <local port> <forward host ip> <forward port>" << std::endl;
return 1;
}
const unsigned short local_port = static_cast<unsigned short>(::atoi(argv[2]));
const unsigned short forward_port = static_cast<unsigned short>(::atoi(argv[4]));
const std::string local_host = argv[1];
const std::string forward_host = argv[3];
boost::asio::io_service ios;
boost::system::error_code ec;
boost::asio::ip::tcp::socket &mySocket = tcp_proxy::bridge(ios).downstream_socket();
try
{
std::string str_IpDest = mySocket.remote_endpoint().address().to_string(); // Here i try to get the adresse but with this code, the adresse is seeking only at the begining...it's too earl
tcp_proxy::bridge::acceptor acceptor(ios,
local_host, local_port,
str_IpDest, forward_port);
acceptor.accept_connections();
ios.run();
}
catch (std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
I'm doing this
std::string str_IpDest = mySocket.remote_endpoint().address().to_string();
But it's called to earl, i don't know how to trigger it each time user ask another adress.
I use boost:asio.
Anyon can help me please ?
The goal...After is to use SSL...but...after :)
Thanks a lot :)
Regards
You need some kind of protocol for communication between your browser and proxy. First your browser needs to send something like "connect to" message, then proxy should send info if connection was successful. I believe that HTTP has some headers specific for proxy handling.
What is the proper way to shutdown an asynchronous boost asio tcp server? My currently solution usually deadlocks in the destructor. Why?
class connection;
typedef std::set<shared_ptr<connection>> connection_set;
class connection : public enable_shared_from_this<connection>
{
shared_ptr<tcp::socket> socket_;
std::array<char, 8192> data_;
shared_ptr<connection_set> connection_set_;
public:
static shared_ptr<connection> create(shared_ptr<tcp::socket> socket, shared_ptr<connection_set> connection_set)
{
auto con = shared_ptr<connection>(new connection(std::move(socket), std::move(connection_set)));
con->read_some();
return con;
}
void on_next(const event& e)
{
// async_write_some ...
}
private:
connection(shared_ptr<tcp::socket> socket, shared_ptr<connection_set> connection_set)
: socket_(std::move(socket))
, connection_set_(std::move(connection_set))
{
}
void handle_read(const boost::system::error_code& error, size_t bytes_transferred)
{
if(!error)
{
on_read(std::string(data_.begin(), data_.begin() + bytes_transferred));
read_some();
}
else if (error != boost::asio::error::operation_aborted)
connection_set_->erase(shared_from_this());
else
read_some();
}
void handle_write(const shared_ptr<std::vector<char>>& data, const boost::system::error_code& error, size_t bytes_transferred)
{
if(!error)
{
}
else if (error != boost::asio::error::operation_aborted)
connection_set_->erase(shared_from_this());
}
void read_some()
{
socket_->async_read_some(boost::asio::buffer(data_.data(), data_.size()), std::bind(&connection::handle_read, shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
void on_read(std::string str)
{
// parse the string...
}
};
class tcp_observer
{
boost::asio::io_service service_;
tcp::acceptor acceptor_;
std::shared_ptr<connection_set> connection_set_;
boost::thread thread_;
public:
tcp_observer(unsigned short port)
: acceptor_(service_, tcp::endpoint(tcp::v4(), port))
, thread_(std::bind(&boost::asio::io_service::run, &service_))
{
start_accept();
}
~tcp_observer()
{
// Deadlocks...
service_.post([=]
{
acceptor_.close();
connection_set_->clear();
});
thread_.join();
}
void on_next(const event& e)
{
service_.post([=]
{
BOOST_FOREACH(auto& connection, *connection_set_)
connection->on_next(e);
});
}
private:
void start_accept()
{
auto socket = std::make_shared<tcp::socket>(service_);
acceptor_.async_accept(*socket, std::bind(&tcp_observer::handle_accept, this, socket, std::placeholders::_1));
}
void handle_accept(const shared_ptr<tcp::socket>& socket, const boost::system::error_code& error)
{
if (!acceptor_.is_open())
return;
if (!error)
connection_set_->insert(connection::create(socket, connection_set_));
start_accept();
}
};
This "deadlocks" because the connections are not going to be destroyed, since there are still pending operations where a shared_from_this() was passed.
Call on each connection's socket shutdown(..) and close(..). Then wait for completion which signals either eof or operation_aborted.
Ultimately I'm trying to transfer buffers from one machine to another. The code below takes stream of <id><size><data with size bytes> and reads the part in the handleReadHeader function, then reads the <size> number of bytes, then goes back and waits for another <id><size> pair.
I've pasted a lot of code, but really the only functions I'm suspicious of are:
Downlink::addMsgToQueue
Downlink::writeCallback
Downlink::startWrites()
Downlink::handleReadHeader
Downlink::handleReadFrameDataBGR
using namespace std;
using namespace boost;
using namespace boost::asio;
Downlink::Downlink() :
socket(nIO),
headerSize(sizeof(unsigned int)+1),
connected(false),
isWriting(false),
readHeaderBuffer(headerSize)
{}
Downlink::~Downlink() {
disconnect();
}
bool Downlink::connect(const std::string &robotHost, unsigned int port) {
disconnect();
ip::tcp::resolver resolver(nIO);
ip::tcp::resolver::query query(robotHost, lexical_cast<string>(port));
ip::tcp::resolver::iterator iterator = resolver.resolve(query);
ip::tcp::resolver::iterator end;
boost::system::error_code ec;
for(;iterator!=end;++iterator) {
socket.connect(*iterator, ec);
if(!ec)
break;
socket.close();
}
if(!socket.is_open())
return false;
async_read(socket, buffer(readHeaderBuffer),
bind(&Downlink::handleReadHeader, this, _1, _2));
//start network thread.
lock_guard<mutex> l(msgMutex);
outgoingMessages = queue<vector<char> >();
nIO.reset();
t = thread(bind(&boost::asio::io_service::run, &nIO));
connected = true;
return true;
}
bool Downlink::isConnected() const {
return connected;
}
void Downlink::disconnect() {
nIO.stop();
t.join();
socket.close();
connected = false;
isWriting = false;
nIO.reset();
nIO.run();
}
void Downlink::writeToLogs(const std::string &logMsg) {
vector<char> newMsg(logMsg.length()+headerSize);
newMsg[0] = MSG_WRITE_LOG;
const unsigned int msgLen(logMsg.length());
memcpy(&newMsg[1], &msgLen, sizeof(unsigned int));
vector<char>::iterator dataBegin = newMsg.begin();
advance(dataBegin, headerSize);
copy(logMsg.begin(), logMsg.end(), dataBegin);
assert(newMsg.size()==(headerSize+logMsg.length()));
addMsgToQueue(newMsg);
}
void Downlink::addMsgToQueue(const std::vector<char> &newMsg) {
lock_guard<mutex> l(msgMutex);
outgoingMessages.push(newMsg);
lock_guard<mutex> l2(outMutex);
if(!isWriting) {
nIO.post(bind(&Downlink::startWrites, this));
}
}
void Downlink::writeCallback(const boost::system::error_code& error,
std::size_t bytes_transferred) {
if(error) {
disconnect();
lock_guard<mutex> l(msgMutex);
outgoingMessages = queue<vector<char> >();
return;
}
{
lock_guard<mutex> l2(outMutex);
isWriting = false;
}
startWrites();
}
void Downlink::startWrites() {
lock_guard<mutex> l(msgMutex);
lock_guard<mutex> l2(outMutex);
if(outgoingMessages.empty()) {
isWriting = false;
return;
}
if(!isWriting) {
currentOutgoing = outgoingMessages.front();
outgoingMessages.pop();
async_write(socket, buffer(currentOutgoing),
bind(&Downlink::writeCallback, this, _1, _2));
isWriting = true;
}
}
void Downlink::handleReadHeader(const boost::system::error_code& error,
std::size_t bytes_transferred) {
//TODO: how to handle disconnect on errors?
cout<<"handleReadHeader"<<endl;
if(error) {
return;
}
assert(bytes_transferred==headerSize);
if(bytes_transferred!=headerSize) {
cout<<"got "<<bytes_transferred<<" while waiting for a header."<<endl;
}
currentPacketID = readHeaderBuffer[0];
memcpy(¤tPacketLength, &readHeaderBuffer[1], sizeof(unsigned int));
dataStream.resize(currentPacketLength);
switch(currentPacketID) {
case MSG_FRAME_BGR: {
cout<<"- >> gone to read frame. ("<<currentPacketLength<<")"<<endl;
async_read(socket, asio::buffer(dataStream),
boost::asio::transfer_at_least(currentPacketLength),
bind(&Downlink::handleReadFrameDataBGR, this, _1, _2));
} break;
default: {
cout<<"->>> gone to read other. ("<<currentPacketLength<<")"<<endl;
cout<<" "<<(int)currentPacketID<<endl;
async_read(socket, asio::buffer(dataStream),
boost::asio::transfer_at_least(currentPacketLength),
bind(&Downlink::handleReadData, this, _1, _2));
} break;
}
}
void Downlink::handleReadData(const boost::system::error_code& error,
std::size_t bytes_transferred) {
cout<<"handleReadData"<<endl;
if(error) {
return;
}
if(bytes_transferred!=currentPacketLength) {
cout<<"Got "<<bytes_transferred<<" wanted "<<currentPacketLength<<endl;
}
assert(bytes_transferred==currentPacketLength);
switch(currentPacketID) {
case MSG_ASCII: {
string msg(dataStream.begin(), dataStream.end());
textCallback(&msg);
} break;
case MSG_IMU: {
Eigen::Vector3d a,g,m;
unsigned int stamp;
memcpy(a.data(), &dataStream[0], sizeof(double)*3);
memcpy(m.data(), &dataStream[0]+sizeof(double)*3, sizeof(double)*3);
memcpy(g.data(), &dataStream[0]+sizeof(double)*6, sizeof(double)*3);
memcpy(&stamp, &dataStream[0]+sizeof(double)*9, sizeof(unsigned int));
imuCallback(a,m,g,stamp);
} break;
default:
//TODO: handle this better?
cout<<"Unknown packet ID."<<endl;
}
async_read(socket, buffer(readHeaderBuffer),
boost::asio::transfer_at_least(headerSize),
bind(&Downlink::handleReadHeader, this, _1, _2));
}
void Downlink::handleReadFrameDataBGR(const boost::system::error_code& error,
std::size_t bytes_transferred) {
cout<<"Got a frame"<<endl;
if(error) {
return;
}
if(bytes_transferred!=currentPacketLength) {
cout<<"Got "<<bytes_transferred<<" wanted "<<currentPacketLength<<endl;
}
assert(bytes_transferred==currentPacketLength);
unsigned int imageWidth, imageHeight, cameraID;
unsigned char *readOffset = (unsigned char*)&dataStream[0];
memcpy(&imageWidth, readOffset, sizeof(unsigned int));
readOffset += sizeof(unsigned int);
memcpy(&imageHeight, readOffset, sizeof(unsigned int));
readOffset += sizeof(unsigned int);
memcpy(&cameraID, readOffset, sizeof(unsigned int));
readOffset += sizeof(unsigned int);
cout<<"("<<imageWidth<<"x"<<imageHeight<<") ID = "<<cameraID<<endl;
frameCallback(readOffset, imageWidth, imageHeight, cameraID);
async_read(socket, buffer(readHeaderBuffer),
boost::asio::transfer_at_least(headerSize),
bind(&Downlink::handleReadHeader, this, _1, _2));
}
boost::signals2::connection Downlink::connectTextDataCallback(boost::signals2::signal<void (std::string *)>::slot_type s) {
return textCallback.connect(s);
}
boost::signals2::connection Downlink::connectIMUDataCallback(boost::signals2::signal<void (Eigen::Vector3d, Eigen::Vector3d, Eigen::Vector3d, unsigned int)>::slot_type s) {
return imuCallback.connect(s);
}
boost::signals2::connection Downlink::connectVideoFrameCallback(boost::signals2::signal<void (unsigned char *, unsigned int, unsigned int, unsigned int)>::slot_type s) {
return frameCallback.connect(s);
}
Here is the code on the other end. It's almost exactly the same as the other code, but the error could be in either end.
using namespace std;
using namespace boost;
using namespace boost::asio;
Uplink::Uplink(unsigned int port) :
socket(nIO),
acceptor(nIO),
endpoint(ip::tcp::v4(), port),
headerSize(sizeof(unsigned int)+1), //id + data size
headerBuffer(headerSize)
{
//move socket into accept state.
acceptor.open(endpoint.protocol());
acceptor.set_option(ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.listen(1); //1 means only one client in connect queue.
acceptor.async_accept(socket, bind(&Uplink::accept_handler, this, _1));
//start network thread.
nIO.reset();
t = thread(boost::bind(&boost::asio::io_service::run, &nIO));
}
Uplink::~Uplink() {
nIO.stop(); //tell the network thread to stop.
t.join(); //wait for the network thread to stop.
acceptor.close(); //close listen port.
socket.close(); //close active connections.
nIO.reset();
nIO.run(); //let clients know that we're disconnecting.
}
void Uplink::parse_header(const boost::system::error_code& error,
std::size_t bytes_transferred) {
if(error || bytes_transferred!=headerSize) {
disconnect();
return;
}
currentPacketID = headerBuffer[0];
memcpy(¤tPacketLength, &headerBuffer[1], sizeof(unsigned int));
//move to read data state
//TODO: move to different states to parse various packet types.
async_read(socket, asio::buffer(dataStream), transfer_at_least(currentPacketLength),
bind(&Uplink::parse_data, this, _1, _2));
}
void Uplink::parse_data(const boost::system::error_code& error,
std::size_t bytes_transferred) {
if(error) {
disconnect();
return;
}
if(bytes_transferred != currentPacketLength) {
cout<<"bytes_transferred != currentPacketLength"<<endl;
disconnect();
return;
}
//move back into the header reading state
async_read(socket, buffer(headerBuffer),
bind(&Uplink::parse_header, this, _1, _2));
}
void Uplink::disconnect() {
acceptor.close();
socket.close();
acceptor.open(endpoint.protocol());
acceptor.set_option(ip::tcp::acceptor::reuse_address(true));
acceptor.bind(endpoint);
acceptor.listen(1); //1 means only one client in connect queue.
acceptor.async_accept(socket, bind(&Uplink::accept_handler, this, _1));
}
void Uplink::accept_handler(const boost::system::error_code& error)
{
if (!error) {
//no more clents.
acceptor.close();
//move to read header state.
async_read(socket, buffer(headerBuffer),
bind(&Uplink::parse_header, this, _1, _2));
}
}
void Uplink::sendASCIIMessage(const std::string &m) {
//Format the message
unsigned int msgLength(m.length());
vector<char> outBuffer(msgLength+headerSize);
outBuffer[0] = MSG_ASCII;
memcpy(&outBuffer[1], &msgLength, sizeof(unsigned int));
vector<char>::iterator dataBegin(outBuffer.begin());
advance(dataBegin, headerSize);
copy(m.begin(), m.end(), dataBegin);
//queue the message
addToQueue(outBuffer);
}
void Uplink::sendIMUDataBlock(const nIMUDataBlock *d) {
//Format the message.
//a,g,m, 3 components each plus a stamp
const unsigned int msgLength(3*3*sizeof(double)+sizeof(unsigned int));
vector<char> outBuffer(msgLength+headerSize);
outBuffer[0] = MSG_IMU;
memcpy(&outBuffer[1], &msgLength, sizeof(unsigned int));
const Eigen::Vector3d a(d->getAccel());
const Eigen::Vector3d m(d->getMag());
const Eigen::Vector3d g(d->getGyro());
const unsigned int s(d->getUpdateStamp());
memcpy(&outBuffer[headerSize], a.data(), sizeof(double)*3);
memcpy(&outBuffer[headerSize+3*sizeof(double)], m.data(), sizeof(double)*3);
memcpy(&outBuffer[headerSize+6*sizeof(double)], g.data(), sizeof(double)*3);
memcpy(&outBuffer[headerSize+9*sizeof(double)], &s, sizeof(unsigned int));
/*
cout<<"----------------------------------------"<<endl;
cout<<"Accel = ("<<a[0]<<","<<a[1]<<","<<a[2]<<")"<<endl;
cout<<"Mag = ("<<m[0]<<","<<m[1]<<","<<m[2]<<")"<<endl;
cout<<"Gyro = ("<<g[0]<<","<<g[1]<<","<<g[2]<<")"<<endl;
cout<<"Stamp = "<<s<<endl;
cout<<"----------------------------------------"<<endl;
*/
//queue the message
addToQueue(outBuffer);
}
void Uplink::send_handler(const boost::system::error_code& error,
std::size_t bytes_transferred) {
{
lock_guard<mutex> l(queueLock);
lock_guard<mutex> l2(sendingLock);
if(outQueue.empty()) {
currentlySending = false;
return;
}
}
startSend();
}
void Uplink::addToQueue(const std::vector<char> &out) {
bool needsRestart = false;
{
lock_guard<mutex> l(queueLock);
lock_guard<mutex> l2(sendingLock);
outQueue.push(out);
needsRestart = !currentlySending;
}
if(needsRestart)
nIO.post(bind(&Uplink::startSend, this));
}
void Uplink::startSend() {
lock_guard<mutex> l(queueLock);
lock_guard<mutex> l2(sendingLock);
if(outQueue.empty())
return;
currentlySending = true;
currentWrite = outQueue.front();
outQueue.pop();
async_write(socket, buffer(currentWrite), bind(&Uplink::send_handler,
this, _1, _2));
}
void Uplink::sendVideoFrameBGR(const unsigned int width, const unsigned int height,
const unsigned int cameraID, const unsigned char *frameData) {
// image data image metadata header
const unsigned int packetSize(width*height*3 + sizeof(unsigned int)*3 + headerSize);
const unsigned int dataSize(width*height*3 + sizeof(unsigned int)*3);
vector<char> outgoingBuffer(packetSize);
outgoingBuffer[0] = MSG_FRAME_BGR;
memcpy(&outgoingBuffer[1], &dataSize, sizeof(unsigned int));
char *writePtr = &outgoingBuffer[headerSize];
memcpy(writePtr, &width, sizeof(unsigned int));
writePtr += sizeof(unsigned int);
memcpy(writePtr, &height, sizeof(unsigned int));
writePtr += sizeof(unsigned int);
memcpy(writePtr, &cameraID, sizeof(unsigned int));
writePtr += sizeof(unsigned int);
memcpy(writePtr, frameData, width*height*3*sizeof(char));
//TODO: can we avoid the whole image copy here?
//TODO: should come up with a better packet buffer build system.
//IDEA!: maybe have a "request buffer" funxction so the Uplink
//class can have sole ownership, rather than do the copy in "addtoQueue"
addToQueue(outgoingBuffer);
}
This program works most of the time, but only rarely, when sending a lot of data with no delay between packets it will fail.
For example:
sendVideoFrameBGR(...); //occasional fail
sendASCIIMessage("...");
sendVideoFrameBGR(...); //never fails.
sleep(1);
sendASCIIMessage("...");
after handling a video frame in Downlink it goes back to the hadleHeaderData and waits for a packet that is several megabytes in length and for a packet ID that doesn't exist. Somehow the stream is getting corrupted. I don't know why.
I don't really care much for the code I have written now, so if anybody knows of a good class or library to parse streams over TCP into buffer blocks for me I'd rather use that.
EDIT:
Here is the exact code that runs the sending of data:
if(frontImage) {
uplink.sendVideoFrameBGR(frontImage->width, frontImage->height, 0,
(unsigned char*)frontImage->imageData);
cout<<"Sent"<<endl;
//sleep(1); //works fine if this is uncommented !
}
uplink.sendASCIIMessage("Alive...");
sleep(1);
uplink.sendIMUDataBlock(imuDataBlock.get());
cout<<"Loop"<<endl;
sleep(1);
}
The problem is most likely that your ioservice object has more than one thread handling work.
When you call the second send function immediately after the first, the two function objects posted to the ioservice are probably being delegated to different threads. So basically, two writes are occurring on the same socket in parallel. This is most likely illegal. Using Winsock2 with non-blocking sockets, this would cause the outgoing data to be corrupted.
Even though you use a bool to check whether it's currently sending, the bool isn't checked until one of the ioservice threads is handling the function. If two ioservice threads are active when you post the two pieces of work, it could dispatch both sends at the same time, causing the two send functions to occur asynchronously on separate threads. The 'is currently sending' check may be returning false in both calls, since the two sends are running in parallel.