Reputation: 43
I am using boost.asio and I have a simple server that needs to handle multiple connections. Before starting the "connected" status, I need to perform a handshake with the client in a limited amount of time. I am using chained asynchronous operations for each step of the handshake as I need to use a timer and (as far as I know I can't do that with synchronous reads and writes). I need a way to block each connection until the deadline timer ends or the handshake succeeds, without blocking the server.
Is there any way to achieve this?
UPDATE Some code to clarify things
A simple server
typedef boost::asio::ip::tcp::socket sock;
class server
{
public:
server() : _ios(), _acceptor(_ios)
{
boost::asio::ip::tcp::resolver resolver(_ios);
boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), PORT);
_acceptor.open(endpoint.protocol());
_acceptor.set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));
_acceptor.bind(endpoint);
_acceptor.listen();
do_accept();
}
void run()
{
_ios.run();
}
private:
void do_accept()
{
auto socket = std::make_shared<TSock>(_ios);
_acceptor.async_accept(*socket, std::bind(&server::handle_accept,
this, socket, std::placeholders::_1));
}
void handle_accept(std::shared_ptr<sock> socket, const boost::system::error_code& ec)
{
auto connection = std::make_shared<CCon>();
if (connection->Accept(socket))
{
std::cout << "Connection accepted" << std::endl;
}
else
{
std::cout << "Connection not accepted" << std::endl;
}
do_accept();
}
boost::asio::io_service _ios;
boost::asio::ip::tcp::acceptor _acceptor;
std::set<std::shared_ptr<CCon>> _connections;
};
int32_t main(int32_t argc, char *argv[])
{
server s;
s.run();
}
connection->Accept(socket) explained
bool CCon::Accept(<std::shared_ptr<sock>> tcpSocket)
{
// set handshake sequence
SendGreeting();
// I NEED TO WAIT HERE UNTIL connectionAccepted gets a value
if (connectionAccepted)
{
// Connection Accepted
return(true)
}
else
{
//Connection Rejected
return(false)
}
}
SendGreeting() contains
boost::asio::async_write(*tcpSocket,
boost::asio::buffer(oBuff,bytesBuffered),
std::bind(&CCon::WaitForResp,
this, std::placeholders::_1));
The problem is that WaitForResp never gets called. It only gets called if I restart the io_service
(stop()
-> reset()
-> run()
) after setting the new handler, which isn't a solution at all.
I think I am missing something about Asio and I'd be glad if anyone could help me with it.
Upvotes: 3
Views: 4303
Reputation:
Your example code isn't complete, so I can't guarantee my answer is a solution, but I'll point out a few things that I think are the problem.
Point 1 - The io_service
doesn't just keep running. If it thinks there's no more work to be done (nothing in the cue), then ::run()
will exit, and since you're expecting it not to, you're going to get unexpected behavior. You need to prevent it from running out of work, by creating and keeping an io_service::work
object alive. From the docs:
boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);
...
More on that here.
Secondly, I'm concerned about how the CCon
object lifetime is maintained. Who is responsible for keeping it alive? I ask because you're binding to this
inside of it, so you're spawning async callbacks internally but apparently relying on an external object to make sure that this
is alive if and when the callback returns. If you're failing to do this correctly, you're going to get undefined behavior, which doesn't necessarily mean that you're going to get a big crash and error code, but maybe somehow the handler completes or hangs forever, or some other strange inconclusive behavior because of the undefined state.
Based on the incomplete code here, it looks like nothing is keeping CCon
alive externally, which means that things are probably falling out of scope and getting destroyed. I'm going to guess that you always get "connection not accepted" in your server, if anything at all. If you break down the code you have here, you spawn a CCon
wrapped in a shared_ptr
, then call ::Accept()
, which in turn invokes non-blocking async methods which may complete immediately, or 100 years from now. Since they're non-blocking, the code is going to step into the if(connectionAccepted)
statement immediately and therefore return one way another immediately, at which time your shared_ptr
of CCon
falls out of scope in the server handle_accept
, the ref count gets decremented to 0 and the destructor is invoked.
What you should be doing is making CCon
inherit from std::enable_shared_from_this
or boost::enable_shared_from_this
and in all of the async callbacks that CCon
binds internally, you should bind to shared_from_this()
, which will feed another shared_ptr
into the cue from the bind, incrementing the ref count of your shared CCon
object and guaranteeing the lifetime of CCon
to at least extend into the completion handler.
Third, I think your design approach isn't the best solution, as a side note. You shouldn't be forcing every other possible incoming connection to wait for the previous connection to go through some authentication process. You're holding up the acceptor from doing its job for new clients until the previous client goes does some talking with the server. That's a completely unnecessary bottleneck.
I believe you're doing this because you want to, at the ::server
object level, determine if clients have properly passed the authentication process. You should instead just pass a function or something to the CCon::Accept(...)
method which it can invoke to report things to the server, if you really need the server to know anything at all. Copy the asio model and change your Accept
method to AsyncAccept
, make it non-blocking and give it a callback for when it completes, at which time you can check its final state.
Upvotes: 2