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();
}
}
);
}
Related
I am using boost-beast library for a websocket connection.
You can refer to this example for the understanding what is happening. I have used the same example, but changed a few things:
split the on_handshake(beast::error_code ec) into three functions A(beast::error_code ec),B(beast::error_code ec) and C(beast::error_code ec)
B() sends binary data, and A() and C() are sending text.
A() calls B() as callback and B() calls C() as callback.
Now I am stuck at a point where I want to do this:
void session::A(beast::error_code ec) {
if (ec)
return (fail(ec, "handshake"));
ws_.async_write(net::buffer(SOMETEXT),bind(&session::B, shared_from_this(), placeholders::_1));
}
void session::B(beast::error_code ec) {
if (ec)
return (fail(ec, "A_FAILED"));
if(condition1) {
ws_.binary(true);
ws_.async_write(net::buffer(SOMEBINARY),bind(&session::C, shared_from_this(), placeholders::_1));
} else {
session::on_write(ec,<WHAT SHOULD I WRITE HERE>);
}
}
void session::C(beast::error_code ec) {
if (ec)
return (fail(ec, "B_FAILED"));
ws_.binary(false);
ws_.async_write(net::buffer(SOMETEXT),bind(&session::on_write, shared_from_this(), placeholders::_1, placeholders::_2));
}
Here is the Read function:
void
on_write(
beast::error_code ec,
std::size_t bytes_transferred)
{
boost::ignore_unused(bytes_transferred);
if(ec)
return fail(ec, "write");
// Read a message into our buffer
ws_.async_read(
buffer_,
std::bind(
&session::on_read,
shared_from_this(),
std::placeholders::_1,
std::placeholders::_2));
}
QUESTION:- Please check the function B() and my question there. Any advice or answer is appreciated.
You can just put a 0 in the argument and mark it as unused
e.g. session::on_write(ec, 0 /* field unused */);
Or you can give your function signatures default parameters:
void session::B(
beast::error_code ec = {},
std::size_t bytes_transferred = 0);
In boost async-tcp-echo-server example there is a server class which creates a session on a new connection:
acceptor.async_accept(socket, [this](boost::system::error_code ec) {
if (!ec)
std::make_shared<session>(std::move(socket))->start();
do_accept();
});
session::start() function body:
void start() { do_read(); }
session::do_read method is a private member function:
void do_read()
{
auto self(shared_from_this());
socket.async_read_some(boost::asio::buffer(data, sizeof(data)),
[this, self](boost::system::error_code ec, std::size_t length) {
if (!ec)
do_write(length);
});
}
Please, correct me if I am wrong.
The session class inherits from std::enable_shared_from_this so a control block is already created when calling shared_from_this() and no undefined behavior will occur. In the do_read function shared_from_this() function is used to allow do_write() method to be called on object which still exists in memory. If shared_from_this() wouldn't be used the object could be deleted when reaching end of scope.
Why is this captured in lambda expression?
Is do_write() method called on this or self?
In C++14 could I replace:
auto self(shared_from_this());
socket.async_read_some(boost::asio::buffer(data, sizeof(data)),
[this, self] ...
with:
socket.async_read_some(boost::asio::buffer(data, sizeof(data)),
[this, shared_from_this()] ...
?
Or even with that:
socket.async_read_some(boost::asio::buffer(data, sizeof(data)),
[self = shared_from_this()](boost::system::error_code ec, std::size_t length) {
if (!ec)
self->do_write(length);
});
?
The self object is captured into the lambda as an ownership token: as long as the lambda lives, the token lives, and the object won't be destroyed.
Capturing this is redundant here, but without it one would have to write
if (!ec)
self->do_write(length);
instead of
if (!ec)
do_write(length);
which is the same as
if (!ec)
this->do_write(length);
Thus, this is captured mainly for readability.
In the Chat example of the library, I see that there is a "chat_message" header declaring a class. In that class, I can see the following functions -among others-:
const char* body() const
{
return data_ + header_length;
}
char* body()
{
return data_ + header_length;
}
std::size_t body_length() const
{
return body_length_;
}
I only see accessors here, no mutators. However, in the "chat_session" class, the handlers make use of this class even if it does not have any mutators. However, I guess that the member variables of "chat_message" are modified somehow. See this piece of code from "chat_session":
void handle_read_header(const boost::system::error_code& error)
{
if (!error && read_msg_.decode_header())
{
boost::asio::async_read(socket_,
boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
boost::bind(&chat_session::handle_read_body, shared_from_this(),
boost::asio::placeholders::error));
}
else
{
room_.leave(shared_from_this());
}
}
void handle_read_body(const boost::system::error_code& error)
{
if (!error)
{
room_.deliver(read_msg_);
boost::asio::async_read(socket_,
boost::asio::buffer(read_msg_.data(), chat_message::header_length),
boost::bind(&chat_session::handle_read_header, shared_from_this(),
boost::asio::placeholders::error));
}
else
{
room_.leave(shared_from_this());
}
}
I guess that in the "async_read"s the received data is passed somehow to the object, but I don't understand how.
Could you explain to me how is data passed from the "async_read" to the object?
Thank you very much indeed.
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();
}
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];
};