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 * @var size_t
*/ */
size_t _reallocate = 0; 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 * Helper method to report an error
* @return bool Was an error reported? * @return bool Was an error reported?
@ -65,6 +93,16 @@ private:
// some errors are ok and do not (necessarily) mean that we're disconnected // some errors are ok and do not (necessarily) mean that we're disconnected
if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return false; 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 // we have an error - report this to the user
_handler->onError(_connection, strerror(errno)); _handler->onError(_connection, strerror(errno));
@ -110,14 +148,8 @@ public:
*/ */
virtual ~TcpConnected() noexcept 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 the socket
close(_socket); close();
} }
/** /**
@ -145,7 +177,7 @@ public:
auto result = _out.sendto(_socket); auto result = _out.sendto(_socket);
// are we in an error state? // 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 // if buffer is empty by now, we no longer have to check for
// writability, but only for readability // writability, but only for readability
@ -255,28 +287,47 @@ public:
return TcpState::reportNegotiate(heartbeat); 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 * Report to the handler that the connection was nicely closed
* This is the counter-part of the connection->close() call.
*/ */
virtual void reportClosed() override virtual void reportClosed() override
{ {
// we no longer have to monitor the socket // we will shutdown the socket in a very elegant way, we notify the peer
_handler->monitor(_connection, _socket, 0); // 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 // if the user was already notified of an final state, we do not have to proceed
close(_socket); if (_finalized) return;
// socket is closed now // remember that this is the final call to user space
_socket = -1; _finalized = true;
// copy the handler (if might destruct this object) // pass to handler
auto *handler = _handler; _handler->onClosed(_connection);
// reset member before the handler can make a mess of it
_handler = nullptr;
// notify to handler
handler->onClosed(_connection);
} }
}; };

View File

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

View File

@ -36,22 +36,6 @@ protected:
TcpHandler *_handler; TcpHandler *_handler;
protected: 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 * Protected constructor
* @param connection Original TCP connection object * @param connection Original TCP connection object
@ -150,7 +134,7 @@ public:
virtual void reportError(const char *error) virtual void reportError(const char *error)
{ {
// pass to handler // pass to handler
reset(nullptr)->onError(_connection, error); _handler->onError(_connection, error);
} }
/** /**
@ -182,7 +166,7 @@ public:
virtual void reportClosed() virtual void reportClosed()
{ {
// pass to handler // pass to handler
reset(nullptr)->onClosed(_connection); _handler->onClosed(_connection);
} }
}; };