This is a boost::asio udp echo demo based on a boost asio example.
The meat of this version using C++ lambda is less than half the size of the boost example one, but gcc tells me that received is not visible in recv_from.
It pains me to have to write this in a more verbose manner. Can some C++ guru help me with a trick to define mutually recursive lambdas?
class server {
public:
server(io_service& io_service, short port)
: socket_(io_service, udp::endpoint(udp::v4(), port)) {
auto recv_from = [&,received]() {
socket_.async_receive_from(buffer(data_, max_length), sender_endpoint_,
received);
};
auto received = [&,recv_from](const error_code& error, size_t bytes_transferred) {
if (!error && bytes_transferred > 0) {
socket_.async_send_to(buffer(data_, bytes_transferred), sender_endpoint_,
[&](const error_code&, size_t) {
recv_from();
});
} else {
recv_from(); // loop
}
};
recv_from();
}
private:
udp::socket socket_;
udp::endpoint sender_endpoint_;
enum { max_length = 1024 };
char data_[max_length];
};
Edit, solution: I needed to add this:
std::function<void(const error_code&, size_t)> received;
to make it easy for the type-inference engine (I'm spoiled having programmed Haskell)
Edit2: There are lifetime issues so that won't work.
Answering my own question:
There are actually no less than three problems with my code.
I have been careful to copy the received and recv_from into the corresponding closures so that they would be available when the constructor goes out of scope.
Unfortunately, the closures go out of scope at the same time as the constructor. Thus the [&, xxx] copying of xxx makes no sense.
The type of at least(?) one of the lambdas must be fixed to please the type inference engine.
But that doesn't solve issue #1. To fix the lifetime issue, I should have stored the closure objects in the server object.
So I think this is close to what I need to do:
class server {
public:
server(io_service& io_service, short port)
: socket_(io_service, udp::endpoint(udp::v4(), port)) {
recv_from = [&]() {
socket_.async_receive_from(buffer(data_, max_length), sender_endpoint_,
received);
};
received = [&](const error_code& error, size_t bytes_transferred) {
if (!error && bytes_transferred > 0) {
socket_.async_send_to(buffer(data_, bytes_transferred), sender_endpoint_,
[&](const error_code&, size_t) {
recv_from();
});
} else {
recv_from(); // loop
}
};
recv_from();
}
private:
udp::socket socket_;
udp::endpoint sender_endpoint_;
std::function<void(const error_code&, size_t)> received;
std::function<void()> recv_from;
enum { max_length = 1024 };
char data_[max_length];
};
Related
I'm trying to understand the thing with std::enable_shared_from_this in case of TCP connections and I see it like when first connection is accepted in the serve func, object of the class Session is created and later invocations just create shared_ptr to the same object isn't it? If I get it well, I'm not sure is it completely correct to move everytime socket in serve? The below example is like original one from the book besides connections int I've added:
using namespace boost::asio;
int connections{};
struct Session : std::enable_shared_from_this<Session> {
explicit Session(ip::tcp::socket socket) : socket{ std::move(socket) } {}
void read() {
async_read_until(socket, dynamic_buffer(message), '\n',
[self=shared_from_this()] (boost::system::error_code ec,
std::size_t length) {
if(ec || self->message == "\n") {
std::cout<<"Ended connection as endline was sent\n" ;
return;
}
boost::algorithm::to_upper(self->message);
self->write();
});
}
void write() {
async_write(socket, buffer(message),
[self=shared_from_this()] (boost::system::error_code ec,
std::size_t length) {
if(ec) return;
self->message.clear();
self->read();
});
}
private:
ip::tcp::socket socket;
std::string message;
};
void serve(ip::tcp::acceptor& acceptor) {
acceptor.async_accept([&acceptor](boost::system::error_code ec,
ip::tcp::socket socket) {
serve(acceptor);
if (ec) return;
auto session = std::make_shared<Session>(std::move(socket));
std::cout<<"Connection established no "<<++connections<<"\n";
session->read();
});
}
int main(){
try{
io_context io_context;
ip::tcp::acceptor acceptor{ io_context,
ip::tcp::endpoint(ip::tcp::v4(), 1895)};
serve(acceptor);
io_context.run();
} catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
}
Socket is moved at each invocation of serve because it is a fresh socket for a newly established connection. Note that it is passed by value and unless moved to some long-living object (session in this case) it will be immediately destroyed after going out of scope, ending the connection.
"object of the class Session is created and later invocations just create shared_ptr to the same object isn't it" - nope, each make_shared invocation creates a new session object - one per connection. shared_from_this spawns pointer to the current object.
I've been debugging this for 2 days now. I've tried simpler code bases which supposedly reproduce the bug... But they work just fine. I'm not sure if the bug is in how I'm misusing the boost asio functions, or some other C++ feature.
I'm writing a TCP server, where the message structure is defined as:
uint64_t size // Size of the message, in bytes
uint64_t timestamp // Some timestamp
char *text // Some ASCII data
The code
The Header class, which represents the size and timestamp, is declared as:
class Header {
private:
uint64_t buf[2];
void *data() const {
return (void *) &buf[0];
}
public:
Header() {
buf[0] = 0UL;
buf[1] = 0UL;
}
// Reads the header from the socket
void readHeader(boost::asio::ip::tcp::socket &socket, std::function<void (void)> cb);
std::size_t getHeaderSize() const {
return sizeof(buf);
}
uint64_t getDatagramSize() const {
return buf[0];
}
uint64_t getTimestamp() const {
return buf[1];
}
};
The class ImuDatagram holds the whole message, and has methods to read the datagram from the socket.
class ImuDatagram {
public:
Header header;
std::string theJson;
void readDatagram(boost::asio::ip::tcp::socket &socket, std::function<void (void)> cb);
void readBody(boost::asio::ip::tcp::socket &socket, std::function<void (void)> cb);
};
The methods are defined as follows:
void Header::readHeader(boost::asio::ip::tcp::socket &socket,
std::function<void (void)> cb)
{
boost::asio::mutable_buffer asio_buf = boost::asio::buffer(data(), getHeaderSize());
boost::asio::async_read(socket, asio_buf,
[this, cb](const boost::system::error_code &ec, std::size_t bytes_transferred){
// TAG1
// bytes_transferred: 16 (as expected)
// getDatagramSize() == 116 (as expected)
// getTimestamp() == ... (as expected)
cb();
});
}
void ImuDatagram::readBody(boost::asio::ip::tcp::socket &socket, std::function<void (void)> cb) {
uint64_t dgSize = header.getDatagramSize();
uint64_t jsonSize = dgSize - header.getHeaderSize();
// TAG2
// This will throw std::bad_alloc.
theJson.reserve(jsonSize);
boost::asio::async_read(socket, boost::asio::buffer(theJson, jsonSize),
[this, &socket, cb](const boost::system::error_code &ec, std::size_t bytes_transferred) {
// Error checks omitted
cb();
});
}
void ImuDatagram::readDatagram(boost::asio::ip::tcp::socket &socket, std::function<void (void)> cb) {
header.readHeader(socket, [this, &socket, cb](){
readBody(socket, cb);
});
}
Finally, the main that gets everything going is:
tcp::acceptor imuAcceptor(ioc, tcp::endpoint(tcp::v4(), 8081));
imuAcceptor.async_accept(imuSocket, [this](const boost::system::error_code& ec)
{
// Error checks omitted
ImuDatagram imu;
imu.readDatagram(socket, [this, &imu, &socket]() {
// Do work
});
}
Problem Description
Consider TAG1 in Header::readHeader. 16 bytes were read (the size of the header's buffer), as expected. I confirmed that theJson's buffer wasn't overrun. Nothing wrong yet.
We then move to TAG2 in ImuDatagram::readBody. As indicated, this throws std::bad_alloc.
As part of my debugging, I inserted a protection byte after the header to see if it would get written. At TAG1, it is not written. At TAG2, it was written. At first, I thought async_read was writing past the buffer. But it isn't, as verified with the protection byte I added.
Can someone please tell me where this (obscure to me) bug is? I wrote a simpler version of this using STDIN instead of a socket, and it worked just fine. My suspicion is that the problem doesn't come from the socket though.
Thank you.
I'm checking this SSL server example and wondering why using shared_ptr. It start with the following method (do_accept()) and continuously using auto self(shared_from_this()) in the session class to extend its lifespan between handlers.
Q: Is it possible to use a tcp::socket member inside session class and avoid shared_ptr? What modification must be applied?
void do_accept()
{
acceptor_.async_accept(
[this](const boost::system::error_code& error, tcp::socket socket)
{
if (!error)
{
std::make_shared<session>(std::move(socket), context_)->start();
}
do_accept();
}
);
}
Inside session class:
void do_handshake()
{
auto self(shared_from_this());
socket_.async_handshake(boost::asio::ssl::stream_base::server,
[this, self](const boost::system::error_code& error)
{
if (!error)
{
do_read();
}
}
);
}
I need to establish up to three different TCP connections to different servers. All three connections requiring different protocols, different handshakes and different heartbeats. Studying http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/example/cpp11/chat/chat_client.cpp, reading stuff here and following Chris Kohlhoffs advices I tried to implement it as below.
The problem is that with this architecture I'm getting a bad_weak_pointer exception at calling shared_from_this() in doConnect() no matter what I'm doing.
Importent These are just snippets of a not running code, which can contain bugs! Importent
I'm having a base class which is containing some basic methods.
Connection.h
class Connection : public std::enable_shared_from_this<Connection>
{
public:
//! Ctor
inline Connection();
//! Dtor
inline virtual ~Connection();
inline void setReconnectTime(const long &reconnectAfterMilisec)
{
m_reconnectTime = boost::posix_time::milliseconds(reconnectAfterMilisec);
}
inline void setHandshakePeriod(const long &periodInMilisec)
{
m_handshakePeriod = boost::posix_time::milliseconds(periodInMilisec);
}
virtual void doConnect() = 0;
virtual void stop() = 0;
//... and some view more...
}
I have then my three classes which are derived from the base class. Here just one (and also the core part) to depict the approach.
ConnectionA.h
//queues which containing also the age of the messages
typedef std::deque<std::pair<handshakeMsg, boost::posix_time::ptime>> handskMsg_queue;
typedef std::deque<std::pair<errorcodeMsg, boost::posix_time::ptime>> ecMsg_queue;
typedef std::deque<std::pair<A_Msg, boost::posix_time::ptime>> A_Msg_queue;
class ConnectionA : public Connection
{
public:
ConnectionA();
ConnectionA(const std::string& IP, const int &port);
ConnectionA& operator=(const ConnectionA &other);
virtual ~ConnectionA();
virtual void stop() override;
virtual void doConnect() override;
void doPost(std::string &message);
void doHandshake();
void sendErrorCode(const int &ec);
std::shared_ptr<boost::asio::io_service>m_ioS;
private:
std::shared_ptr<tcp::socket> m_socket;
std::shared_ptr<boost::asio::deadline_timer> m_deadlineTimer; // for reconnetions
std::shared_ptr<boost::asio::deadline_timer> m_handshakeTimer; // for heartbeats
void deadlineTimer_handler(const boost::system::error_code& error);
void handshakeTimer_handler(const boost::system::error_code& error);
void doRead();
void doWrite();
std::string m_IP;
int m_port;
handskMsg_queue m_handskMsgQueue;
ecMsg_queue m_ecMsgQueue;
A_Msg_queue m_AMsgQueue;
}
ConnectionA.cpp
ConnectionA::ConnectionA(const std::string &IP, const int &port)
: m_ioS()
, m_socket()
, m_deadlineTimer()
, m_handshakeTimer()
, m_IP(IP)
, m_port(port)
, m_handskMsgQueue(10)
, m_ecMsgQueue(10)
, m_AMsgQueue(10)
{
m_ioS = std::make_shared<boost::asio::io_service>();
m_socket = std::make_shared<tcp::socket>(*m_ioS);
m_deadlineTimer = std::make_shared<boost::asio::deadline_timer>(*m_ioS);
m_handshakeTimer = std::make_shared<boost::asio::deadline_timer> (*m_ioS);
m_deadlineTimer->async_wait(boost::bind(&ConnectionA::deadlineTimer_handler, this, boost::asio::placeholders::error));
m_handshakeTimer->async_wait(boost::bind(&ConnectionA::handshakeTimer_handler, this, boost::asio::placeholders::error));
}
ConnectionA::~ConnectionA()
{}
void ConnectionA::stop()
{
m_ioS->post([this]() { m_socket->close(); });
m_deadlineTimer->cancel();
m_handshakeTimer->cancel();
}
void ConnectionA::doConnect()
{
if (m_socket->is_open()){
return;
}
tcp::resolver resolver(*m_ioS);
std::string portAsString = std::to_string(m_port);
auto endpoint_iter = resolver.resolve({ m_IP.c_str(), portAsString.c_str() });
m_deadlineTimer->expires_from_now(m_reconnectTime);
// this gives me a bad_weak_pointer exception!!!
auto self = std::static_pointer_cast<ConnectionA>(static_cast<ConnectionA*>(this)->shared_from_this());
boost::asio::async_connect(*m_socket, endpoint_iter, [this, self](boost::system::error_code ec, tcp::resolver::iterator){
if (!ec)
{
doHandshake();
doRead();
}
else {
// don't know if async_connect can fail but set the socket to open
if (m_socket->is_open()){
m_socket->close();
}
}
});
}
void ConnectionA::doRead()
{
auto self(shared_from_this());
boost::asio::async_read(*m_socket,
boost::asio::buffer(m_readBuf, m_readBufSize),
[this, self](boost::system::error_code ec, std::size_t){
if(!ec){
// check server answer for errors
}
doRead();
}
else {
stop();
}
});
}
void ConnectionA::doPost(std::string &message)
{
A_Msg newMsg (message);
auto self(shared_from_this());
m_ioS->post([this, self, newMsg](){
bool writeInProgress = false;
if (!m_A_MsgQueue.empty()){
writeInProgress = true;
}
boost::posix_time::ptime currentTime = time_traits_t::now();
m_AMsgQueue.push_back(std::make_pair(newMsg,currentTime));
if (!writeInProgress)
{
doWrite();
}
});
}
void ConnectionA::doWrite()
{
while (!m_AMsgQueue.empty())
{
if (m_AMsgQueue.front().second + m_maxMsgAge < time_traits_t::now()){
m_AMsgQueue.pop_front();
continue;
}
if (!m_socket->is_open()){
continue;
}
auto self(shared_from_this());
boost::asio::async_write(*m_socket,
boost::asio::buffer(m_AMsgQueue.front().first.data(),
m_AMsgQueue.front().first.A_lenght),
[this, self](boost::system::error_code ec, std::size_t /*length*/)
{
if (!ec) // successful
{
m_handshakeTimer->expires_from_now(m_handshakePeriod); // reset timer
m_AMsgQueue.pop_front();
doWrite();
}
else {
if (m_socket->is_open()){
m_socket->close();
}
}
});
}
}
void ConnectionA::deadlineTimer_handler(const boost::system::error_code& error){
if (m_stopped){
return;
}
m_deadlineTimer->async_wait(boost::bind(&ConnectionA::deadlineTimer_handler, this, boost::asio::placeholders::error));
if (!error && !m_socket->is_open()) // timer expired and no connection was established
{
doConnect();
}
else if (!error && m_socket->is_open()){ // timer expired and connection was established
m_deadlineTimer->expires_at(boost::posix_time::pos_infin); // to reactivate timer call doConnect()
}
}
And finally there is also another class which encapsulate these classes make it more comfortable to use:
TcpConnect.h
class CTcpConnect
{
public:
/*! Ctor
*/
CTcpConnect();
//! Dtor
~CTcpConnect();
void initConnectionA(std::string &IP, const int &port);
void initConnectionB(std::string &IP, const int &port);
void initConnectionC(std::string &IP, const int &port);
void postMessageA(std::string &message);
void run();
void stop();
private:
ConnectionA m_AConnection;
ConnectionB m_BConnection;
ConnectionC m_CConnection;
}
TcpConnect.cpp
CTcpConnect::CTcpConnect()
: m_AConnection()
, m_BConnection()
, m_CConnection()
{}
CTcpConnect::~CTcpConnect()
{}
void CTcpConnect::run(){
[this](){ m_AConnection.m_ioS->run(); };
[this](){ m_BConnection.m_ioS->run(); };
[this](){ m_CConnection.m_ioS->run(); };
}
void CTcpConnect::stop(){
m_AConnection.stop();
m_BConnection.stop();
m_CConnection.stop();
}
void CTcpConnect::initConnectionA(std::string &IP, const int &port)
{
m_AConnection = ConnectionA(IP, port);
m_AConnection.setMaxMsgAge(30000);
//... set some view parameter more
m_AConnection.doConnect();
}
// initConnectionB & initConnectionC are quite the same
void CTcpConnect::postMessageA(std::string &message)
{
m_AConnection.doWrite(message);
}
In the beginning I tried also to have only one io_service (for my approach this would be fine), but holding the service just as reference gave some headache, because my implementation requires also a default constructor for the connections. Now each connection has its own io-service.
Any ideas how I can bring this code to run?
Feel free to make suggestion for other architectures. If you could came up this some snippets would be even the better. I'm struggling with this implementation for weeks already. I'm grateful for every hint.
BTW I'm using boost 1.61 with VS12.
This is the problem:
m_AConnection = ConnectionA(IP, port);
That is, ConnectionA derives from Connection which derives from enable_shared_from_this. That means that ConnectionA must be instantiated as a shared pointer for shared_from_this to work.
Try this:
void CTcpConnect::initConnectionA(std::string &IP, const int &port)
{
m_AConnection = std::make_shared<ConnectionA>(IP, port);
m_AConnection->setMaxMsgAge(30000);
//... set some view parameter more
m_AConnection->doConnect();
}
EDIT1:
You are right. That was the issue. Now I realised that the way I'm calling io-service.run() is total crap.
It is very uncommon to use more than one io_service, and extremely uncommon to use one per connection :)
However, do you know if I need the cast then calling shared_from_this()? I noticed the asynch_connect() works fine with and without the cast.
Many Asio examples use shared_from_this() for convenience, I for example don't use it in my projects at all. There are certain rules that you need to be careful when working with Asio. For example, one is that the reading and writing buffers must not be destructed before the corresponding callback is executed, if the lambda function captures a shared pointer to the object that holds the buffers, this condition holds trivially.
You could for example do something like this as well:
auto data = std::make_shared<std::vector<uint8_t>>(10);
async_read(socket,
boost::asio::const_buffer(*data),
[data](boost::system::error_code, size_t) {});
It would be valid but would have the performance drawback that you'd be allocating a new data inside std::vector on each read.
Another reason why shared_from_this() is useful can be seen when you look at some some of your lambdas, they often have the form:
[this, self,...](...) {...}
That is, you very often want to use this inside them. If you did not capture self as well, you'd need to use other measures to make sure this has not been destroyed when the handler is invoked.
In boost::asio standard examples after async_accept() the socket object is moving to the session object (which handles all async_read() calls) by initializing it as following:
std::make_shared<session>(std::move(socket_))->start();
And when constructing a session it's moving again (isn't it reduntantly?):
session(tcp::socket socket)
: socket_(std::move(socket))
Then reading from a client is done as following:
boost::asio::async_read(socket_, ...
And all goes well. But when I trying to make async_read() not from the session object but directly from the async_accept() and use it's socket object, CPU is raising to 100% immediately after client connects. Why?
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
class Server
{
public:
Server(boost::asio::io_service& io_service,
const tcp::endpoint& endpoint)
: acceptor_(io_service, endpoint),
socket_(io_service)
{
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec)
{
if (!ec) {
char* buf = new char[5];
boost::asio::async_read(socket_,
boost::asio::buffer(buf, 5),
[this, buf](boost::system::error_code ec, std::size_t)
{
if (!ec) {
std::cout.write(buf, 5);
std::cout << std::endl;
}
delete[] buf;
});
}
do_accept();
});
}
tcp::acceptor acceptor_;
tcp::socket socket_;
};
int main(int argc, char* argv[])
{
int port = 22222;
boost::asio::io_service io_service;
tcp::endpoint endpoint(tcp::v4(), port);
new Server(io_service, endpoint);
io_service.run();
}
Boost 1.49
EDIT
Thanks for the answers! I ended up by moving socket_ before using it:
tcp::socket *socket = new tcp::socket(std::move(socket_));
Also the same problem is discussed at Repeated std::move on an boost::asio socket object in C++11
If the peer socket passed to basic_socket_acceptor::async_accept() is not open, then it will be opened during the async_accept() operation. Otherwise, if the peer is already open, then handler will be posted into the io_service for invocation with an error code of boost::asio::error::already_open. Hence, the posted code causes a tight asynchronous call chain to form:
The async_accept() operation is invoked the first time, causing socket_ to be opened.
The async_accept() handler invokes do_accept(), initiating an async_accept() operation.
socket_ is already open, causing the async_accept() operation to post its handler into the io_service with an error of boost::asio::error::already_open.
The asynchronous call chain starts back at step 2.
This behavior is not observed in the official examples because the socket's move operator causes the the moved-from object to be in the same state as if it was constructed using basic_stream_socket(io_service&) constructor. Thus, the moved-from object is in a closed state, and ready for accepting.
You're using the single socket_ in all places, so when a connection is accepted, your handler calls do_accept() again which is using the same socket_, then it's accepted again and again...
You probably need to always use a new socket like below:
void do_accept()
{
boost::shared_ptr<tcp::socket> psocket(new tcp::socket(io_service));
acceptor_.async_accept(*psocket, boost::bind(&Server::handleAccept, this, psocket, _1));
}
void handleAccept(boost::shared_ptr<tcp::socket> psocket, const boost::system::error_code& ec)
{
if (!ec) {
char* buf = new char[5];
boost::asio::async_read(
*psocket,
boost::asio::buffer(buf, 5),
[this, buf](boost::system::error_code ec, std::size_t)
{
if (!ec) {
std::cout.write(buf, 5);
std::cout << std::endl;
}
delete[] buf;
});
}
do_accept();
}