more elegant close procedure for tcp connections

This commit is contained in:
Emiel Bruijntjes 2018-03-08 10:37:49 +01:00
parent f23bcf19f1
commit 9a08c64ff7
3 changed files with 76 additions and 41 deletions

View File

@ -54,8 +54,36 @@ private:
* @var size_t
*/
size_t _reallocate = 0;
/**
* Have we already made the last report to the user (about an error or closed connection?)
* @var bool
*/
bool _finalized = false;
/**
* Close the connection
* @return bool
*/
bool close()
{
// do nothing if already closed
if (_socket < 0) return false;
// and stop monitoring it
_handler->monitor(_connection, _socket, 0);
// close the socket
::close(_socket);
// forget filedescriptor
_socket = -1;
// done
return true;
}
/**
* Helper method to report an error
* @return bool Was an error reported?
@ -65,6 +93,16 @@ private:
// some errors are ok and do not (necessarily) mean that we're disconnected
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return false;
// connection can be closed now
close();
// if the user has already been notified, we do not have to do anything else
if (_finalized) return true;
// update the _finalized member before we make the call to user space because
// the user space may destruct this object
_finalized = true;
// we have an error - report this to the user
_handler->onError(_connection, strerror(errno));
@ -110,14 +148,8 @@ public:
*/
virtual ~TcpConnected() noexcept
{
// skip if handler is already forgotten
if (_handler == nullptr) return;
// we no longer have to monitor the socket
_handler->monitor(_connection, _socket, 0);
// close the socket
close(_socket);
close();
}
/**
@ -145,7 +177,7 @@ public:
auto result = _out.sendto(_socket);
// are we in an error state?
if (result < 0 && reportError()) return nextState(monitor);
if (result < 0 && reportError()) return nextState(monitor);
// if buffer is empty by now, we no longer have to check for
// writability, but only for readability
@ -255,28 +287,47 @@ public:
return TcpState::reportNegotiate(heartbeat);
}
/**
* Report to the handler that the object is in an error state.
* @param error
*/
virtual void reportError(const char *error)
{
// close the socket
close();
// if the user was already notified of an final state, we do not have to proceed
if (_finalized) return;
// remember that this is the final call to user space
_finalized = true;
// pass to handler
_handler->onError(_connection, error);
}
/**
* Report to the handler that the connection was nicely closed
* This is the counter-part of the connection->close() call.
*/
virtual void reportClosed() override
{
// we no longer have to monitor the socket
_handler->monitor(_connection, _socket, 0);
// we will shutdown the socket in a very elegant way, we notify the peer
// that we will not be sending out more write operations
shutdown(_socket, SHUT_WR);
// we still monitor the socket for readability to see if our close call was
// confirmed by the peer
_handler->monitor(_connection, _socket, readable);
// close the socket
close(_socket);
// if the user was already notified of an final state, we do not have to proceed
if (_finalized) return;
// socket is closed now
_socket = -1;
// remember that this is the final call to user space
_finalized = true;
// copy the handler (if might destruct this object)
auto *handler = _handler;
// reset member before the handler can make a mess of it
_handler = nullptr;
// notify to handler
handler->onClosed(_connection);
// pass to handler
_handler->onClosed(_connection);
}
};

View File

@ -194,7 +194,7 @@ public:
if (_socket >= 0)
{
// if we need a secure connection, we move to the tls handshake
// @todo catch exception
// @todo catch possible exception
if (_secure) return new SslHandshake(_connection, _socket, _hostname, std::move(_buffer), _handler);
// otherwise we have a valid regular tcp connection

View File

@ -36,22 +36,6 @@ protected:
TcpHandler *_handler;
protected:
/**
* Helper function to reset the handler, and to return the old handler object
* @return TcpHandler* User-supplied handler that was just reset
*/
TcpHandler *reset(TcpHandler *handler)
{
// remember old handler
auto *oldhandler = _handler;
// install the new handler
_handler = handler;
// return the old handler
return oldhandler;
}
/**
* Protected constructor
* @param connection Original TCP connection object
@ -150,7 +134,7 @@ public:
virtual void reportError(const char *error)
{
// pass to handler
reset(nullptr)->onError(_connection, error);
_handler->onError(_connection, error);
}
/**
@ -182,7 +166,7 @@ public:
virtual void reportClosed()
{
// pass to handler
reset(nullptr)->onClosed(_connection);
_handler->onClosed(_connection);
}
};