Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 131 additions & 7 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ CClient::CClient ( const quint16 iPortNumber,
strClientName ( strNClientName ),
pSignalHandler ( CSignalHandler::getSingletonP() ),
pSettings ( nullptr ),
eConnectionState ( CS_DISCONNECTED ),
Channel ( false ), /* we need a client channel -> "false" */
CurOpusEncoder ( nullptr ),
CurOpusDecoder ( nullptr ),
Expand Down Expand Up @@ -145,7 +146,7 @@ CClient::CClient ( const quint16 iPortNumber,
// The first ConClientListMesReceived handler performs the necessary cleanup and has to run first:
QObject::connect ( &Channel, &CChannel::ConClientListMesReceived, this, &CClient::OnConClientListMesReceived );

QObject::connect ( &Channel, &CChannel::Disconnected, this, &CClient::Disconnected );
QObject::connect ( &Channel, &CChannel::Disconnected, this, &CClient::Stop );

QObject::connect ( &Channel, &CChannel::NewConnection, this, &CClient::OnNewConnection );

Expand Down Expand Up @@ -618,6 +619,9 @@ bool CClient::SetServerAddr ( QString strNAddr )
// apply address to the channel
Channel.SetAddress ( HostAddress );

// By default, set server name to HostAddress. If using the Connect() method, this may be overwritten
SetConnectedServerName ( HostAddress.toString() );

return true;
}
else
Expand Down Expand Up @@ -905,13 +909,10 @@ void CClient::OnHandledSignal ( int sigNum )
{
case SIGINT:
case SIGTERM:
// if connected, terminate connection (needed for headless mode)
if ( IsRunning() )
{
Stop();
}
// tear down any pending or established connection first, so the server
// is notified we are leaving (Disconnect() is a no-op if not connected)
Disconnect();

// this should trigger OnAboutToQuit
QCoreApplication::instance()->exit();
break;

Expand Down Expand Up @@ -1003,6 +1004,9 @@ void CClient::OnClientIDReceived ( int iServerChanID )
SetRemoteChanGain ( iChanID, 0, false );
}

// the server has assigned us a channel ID, so the connection is established
SetConnectionState ( CS_CONNECTED );

emit ClientIDReceived ( iChanID );
}

Expand Down Expand Up @@ -1045,8 +1049,19 @@ void CClient::Start()
// Disable hibernation or display dimming if the app is running on Windows
SetThreadExecutionState ( ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED );
#endif

// the connection is requested now but not yet established: the transition
// to CS_CONNECTED happens when the server assigns our channel ID
// (see OnClientIDReceived)
SetConnectionState ( CS_CONNECTING );

emit Connecting ( GetConnectedServerName() );
}

/// @method
/// @brief Stops client and disconnects from server
/// @emit Disconnected
/// Use to set CClientDlg to show not being connected
void CClient::Stop()
{
// stop audio interface
Expand Down Expand Up @@ -1088,6 +1103,115 @@ void CClient::Stop()
// Allow hibernation or display dimming if the app is running again (Windows)
SetThreadExecutionState ( ES_CONTINUOUS );
#endif

SetConnectionState ( CS_DISCONNECTED );

// emit Disconnected() to inform UI of disconnection
emit Disconnected();
}

/// @method
/// @brief Disconnects from the server if a connection is requested or established.
/// Idempotent: a no-op when already disconnected.
/// @emit Disconnected
void CClient::Disconnect()
{
// Key off the connection state, not IsRunning() (which tracks the audio
// device): the two diverge while connecting and in headless mode, and on
// SIGTERM we must still send the disconnect message to the server.
if ( GetConnectionState() != CS_DISCONNECTED )
{
Stop();
}
}

/// @method
/// @brief Connects to strServerAddress. If a connection is currently requested
/// or established, that connection is terminated first.
///
/// If strDirectoryAddress is given, the server is assumed to be behind a
/// firewall/NAT that requires directory-assisted UDP hole punching (the
/// same mechanism the GUI directory list uses). We first ask that
/// directory to poke its registered servers towards our socket, wait
/// briefly for their replies to open the firewall, and only then connect.
/// strServerAddress is always connected to verbatim and need not appear
/// in the directory's server list.
/// @emit Connecting (strServerName) if SetServerAddr was valid. emit happens through Start().
/// Use to set CClientDlg to show being connected
/// @emit ConnectingFailed (error) if an error occurred
/// Use to display error message in CClientDlg
/// @param strServerAddress - the server address to connect to
/// @param strServerName - the human readable server name passed to Connecting()
/// @param strDirectoryAddress - optional directory to hole-punch through first
void CClient::Connect ( QString strServerAddress, QString strServerName, QString strDirectoryAddress )
{
if ( strDirectoryAddress.isEmpty() )
{
// no hole punching requested: connect straight away
connectToServer ( strServerAddress, strServerName );
return;
}

CHostAddress haDirectoryAddress;

// directories are queried over IPv4 only (same as jamulusclient/pollServerList)
if ( !NetworkUtil::ParseNetworkAddress ( strDirectoryAddress, haDirectoryAddress, false ) )
{
emit ConnectingFailed ( tr ( "Received invalid directory address. Please check for typos in the provided directory address." ) );
return;
}

// Ask the directory for its server list. As a side effect the directory
// tells each registered server to send us an "empty message", which opens
// the server's firewall for our socket (UDP hole punching).
CreateCLReqServerListMes ( haDirectoryAddress );

// Defer the actual connect so the servers' replies have time to arrive and
// open the firewall before we send our first packet.
QTimer::singleShot ( HOLE_PUNCH_CONNECT_DELAY_MS, this, [this, strServerAddress, strServerName]() {
connectToServer ( strServerAddress, strServerName );
} );
}

/// @brief Performs the actual connect. See Connect(), which either calls this
/// directly or after a directory hole-punch delay.
void CClient::connectToServer ( QString strServerAddress, QString strServerName )
{
try
{
// disconnect from any current server first so that connecting to a
// different server while connected behaves as a reconnect
Disconnect();

// Set server address and connect if valid address was supplied
if ( SetServerAddr ( strServerAddress ) )
{
SetConnectedServerName ( strServerName );
Start();
}
else
{
throw CGenErr ( tr ( "Received invalid server address. Please check for typos in the provided server address." ) );
}
}
catch ( const CGenErr& generr )
{
Stop();
emit ConnectingFailed ( generr.GetErrorText() );
}
}

/// @method
/// @brief Updates the connection state and, if it changed, notifies observers.
/// @emit ConnectionStateChanged (state) when the state actually changes
/// @param eNewConnectionState - the state to transition to
void CClient::SetConnectionState ( const EConnectionState eNewConnectionState )
{
if ( eConnectionState != eNewConnectionState )
{
eConnectionState = eNewConnectionState;
emit ConnectionStateChanged ( eConnectionState );
}
}

void CClient::Init()
Expand Down
38 changes: 37 additions & 1 deletion src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,17 @@
#endif

/* Definitions ****************************************************************/
// Client connection state -----------------------------------------------------
enum EConnectionState
{
// a connection is "requested" as soon as the client starts the audio
// stream towards the configured server and "established" once the server
// has assigned a channel ID to the client
CS_DISCONNECTED = 0, // no connection requested or established
CS_CONNECTING = 1, // connection requested but not yet established
CS_CONNECTED = 2 // connection established and current
};

// audio in fader range
#define AUD_FADER_IN_MIN 0
#define AUD_FADER_IN_MAX 100
Expand Down Expand Up @@ -159,6 +170,15 @@ class CClient : public QObject

void Start();
void Stop();
void Disconnect();
void Connect ( QString strServerAddress, QString strServerName, QString strDirectoryAddress = "" );

// The ConnectedServerName is emitted by Connecting() to update the UI with a human readable server name
void SetConnectedServerName ( const QString strServerName ) { strConnectedServerName = strServerName; };
QString GetConnectedServerName() const { return strConnectedServerName; };

EConnectionState GetConnectionState() const { return eConnectionState; }

bool IsRunning() { return Sound.IsRunning(); }
bool IsCallbackEntered() const { return Sound.IsCallbackEntered(); }
bool SetServerAddr ( QString strNAddr );
Expand Down Expand Up @@ -353,6 +373,17 @@ class CClient : public QObject
void FreeClientChannel ( const int iServerChannelID );
int FindClientChannel ( const int iServerChannelID, const bool bCreateIfNew ); // returns a client channel ID or INVALID_INDEX
bool ReorderLevelList ( CVector<uint16_t>& vecLevelList ); // modifies vecLevelList, passed by reference
// human-readable name of the current server, shown in the UI
QString strConnectedServerName;

// current connection state; set only via SetConnectionState()
EConnectionState eConnectionState;

void SetConnectionState ( const EConnectionState eNewConnectionState );

// performs the actual connect; Connect() calls this directly, or after a
// directory hole-punch delay when a directory address was supplied
void connectToServer ( QString strServerAddress, QString strServerName );

// only one channel is needed for client application
CChannel Channel;
Expand Down Expand Up @@ -464,7 +495,8 @@ protected slots:
{
if ( InetAddr == Channel.GetAddress() )
{
emit Disconnected();
// Stop client in case it received a Disconnection request
Stop();
}
}
void OnCLPingReceived ( CHostAddress InetAddr, int iMs );
Expand Down Expand Up @@ -507,7 +539,11 @@ protected slots:

void CLChannelLevelListReceived ( CHostAddress InetAddr, CVector<uint16_t> vecLevelList );

void ConnectionStateChanged ( EConnectionState eConnectionState );
void Connecting ( QString strServerName );
void ConnectingFailed ( QString errorMessage );
void Disconnected();

void SoundDeviceChanged ( QString strError );
void ControllerInFaderLevel ( int iChannelIdx, int iValue );
void ControllerInPanValue ( int iChannelIdx, int iValue );
Expand Down
Loading
Loading