diff --git a/src/channel.cpp b/src/channel.cpp index cb0afb0041..c5e247ae44 100644 --- a/src/channel.cpp +++ b/src/channel.cpp @@ -400,22 +400,6 @@ void CChannel::OnChangeChanPan ( int iChanID, float fNewPan ) { SetPan ( iChanID void CChannel::OnChangeChanInfo ( CChannelCoreInfo ChanInfo ) { SetChanInfo ( ChanInfo ); } -bool CChannel::GetAddress ( CHostAddress& RetAddr ) -{ - QMutexLocker locker ( &Mutex ); - - if ( IsConnected() ) - { - RetAddr = InetAddr; - return true; - } - else - { - RetAddr = CHostAddress(); - return false; - } -} - void CChannel::OnNetTranspPropsReceived ( CNetworkTransportProps NetworkTransportProps ) { // only the server shall act on network transport properties message diff --git a/src/channel.h b/src/channel.h index 5d36a4d4bc..34cb091948 100644 --- a/src/channel.h +++ b/src/channel.h @@ -84,7 +84,6 @@ class CChannel : public QObject bool IsEnabled() { return bIsEnabled; } void SetAddress ( const CHostAddress NAddr ) { InetAddr = NAddr; } - bool GetAddress ( CHostAddress& RetAddr ); const CHostAddress& GetAddress() const { return InetAddr; } void ResetInfo() diff --git a/src/server.cpp b/src/server.cpp index 6635a3e796..504e736994 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -235,6 +235,7 @@ CServer::CServer ( const int iNewMaxNumChan, bUseDoubleSystemFrameSize ( bNUseDoubleSystemFrameSize ), bUseMultithreading ( bNUseMultithreading ), iMaxNumChannels ( iNewMaxNumChan ), + iCurNumChannels ( 0 ), Socket ( this, iPortNumber, iQosNumber, strServerBindIP, bNEnableIPv6 ), Logging(), iFrameCount ( 0 ), @@ -410,6 +411,7 @@ CServer::CServer ( const int iNewMaxNumChan, for ( i = 0; i < iMaxNumChannels; i++ ) { vecChannels[i].SetEnable ( true ); + vecChannelOrder[i] = i; } int iAvailableCores = QThread::idealThreadCount(); @@ -1054,10 +1056,15 @@ void CServer::DecodeReceiveData ( const int iChanCnt, const int iNumClients ) emit ClientDisconnected ( iCurChanID ); // TODO do this outside the mutex lock? } + FreeChannel ( iCurChanID ); // note that the channel is now not in use + // note that no mutex is needed for this shared resource since it is not a // read-modify-write operation but an atomic write and also each thread can // only set it to true and never to false bChannelIsNowDisconnected = true; + + // since the channel is no longer in use, we should return + return; } // get pointer to coded data @@ -1461,59 +1468,145 @@ void CServer::CreateOtherMuteStateChanged ( const int iCurChanID, const int iOth } } -int CServer::GetFreeChan() +int CServer::GetNumberOfConnectedClients() { - // look for a free channel - for ( int i = 0; i < iMaxNumChannels; i++ ) + QMutexLocker locker ( &MutexChanOrder ); + + return iCurNumChannels; +} + +// CServer::FindChannel() is called for every received audio packet or connected protocol +// packet, to find the channel ID associated with the source IP address and port. +// In order to search as efficiently as possible, a list of active channel IDs is stored +// in vecChannelOrder[], sorted by IP and port (according to CHostAddress::Compare()), +// and a binary search is used to find either the existing channel, or the position at +// which a new channel should be inserted. + +int CServer::FindChannel ( const CHostAddress& CheckAddr, const bool bAllowNew ) +{ + int iNewChanID = INVALID_CHANNEL_ID; + + QMutexLocker locker ( &MutexChanOrder ); + + int l = 0, r = iCurNumChannels, i; + + // use binary search to find the channel + while ( r > l ) { - if ( !vecChannels[i].IsConnected() ) + int t = ( r + l ) / 2; + int cmp = CheckAddr.Compare ( vecChannels[vecChannelOrder[t]].GetAddress() ); + + if ( cmp == 0 ) { - return i; + // address and port match + return vecChannelOrder[t]; } + + if ( cmp < 0 ) + { + l = t + 1; + } + else + { + r = t; + } + } + + // existing channel not found - return if we cannot create a new channel + if ( !bAllowNew || iCurNumChannels >= iMaxNumChannels ) + { + return INVALID_CHANNEL_ID; + } + + // allocate a new channel + i = iCurNumChannels++; // save index of free channel and increment count + iNewChanID = vecChannelOrder[i]; + InitChannel ( iNewChanID, CheckAddr ); + + // now l == r == position in vecChannelOrder to insert iNewChanID + // move channel IDs up by one starting at the top and working down + while ( i > r ) + { + int j = i--; + vecChannelOrder[j] = vecChannelOrder[i]; } + // insert the new channel ID in the correct place + vecChannelOrder[i] = iNewChanID; + + // DumpChannels ( __FUNCTION__ ); - // no free channel found, return invalid ID - return INVALID_CHANNEL_ID; + return iNewChanID; } -int CServer::GetNumberOfConnectedClients() +void CServer::InitChannel ( const int iNewChanID, const CHostAddress& InetAddr ) { - int iNumConnClients = 0; + // initialize new channel by storing the calling host address + vecChannels[iNewChanID].SetAddress ( InetAddr ); + + // reset channel info + vecChannels[iNewChanID].ResetInfo(); - // check all possible channels for connection status + // reset the channel gains/pans of current channel, at the same + // time reset gains/pans of this channel ID for all other channels for ( int i = 0; i < iMaxNumChannels; i++ ) { - if ( vecChannels[i].IsConnected() ) + vecChannels[iNewChanID].SetGain ( i, 1.0 ); + vecChannels[iNewChanID].SetPan ( i, 0.5 ); + + // other channels (we do not distinguish the case if + // i == iCurChanID for simplicity) + vecChannels[i].SetGain ( iNewChanID, 1.0 ); + vecChannels[i].SetPan ( iNewChanID, 0.5 ); + } +} + +// CServer::FreeChannel() is called to remove a channel from the list of active channels. +// The remaining ordered IDs are moved down by one space, and the freed ID is moved to the +// end, ready to be reused by the next new connection. + +void CServer::FreeChannel ( const int iCurChanID ) +{ + QMutexLocker locker ( &MutexChanOrder ); + + for ( int i = 0; i < iCurNumChannels; i++ ) + { + if ( vecChannelOrder[i] == iCurChanID ) { - // this channel is connected, increment counter - iNumConnClients++; + --iCurNumChannels; + + // move channel IDs down by one starting at the bottom and working up + while ( i < iCurNumChannels ) + { + int j = i++; + vecChannelOrder[j] = vecChannelOrder[i]; + } + // put deleted channel at the end ready for re-use + vecChannelOrder[i] = iCurChanID; + + // DumpChannels ( __FUNCTION__ ); + + return; } } - return iNumConnClients; + qWarning() << "FreeChannel() called with invalid channel ID"; } -int CServer::FindChannel ( const CHostAddress& CheckAddr ) +void CServer::DumpChannels ( const QString& title ) { - CHostAddress InetAddr; + qDebug() << qUtf8Printable ( title ); - // check for all possible channels if IP is already in use for ( int i = 0; i < iMaxNumChannels; i++ ) { - // the "GetAddress" gives a valid address and returns true if the - // channel is connected - if ( vecChannels[i].GetAddress ( InetAddr ) ) + int iChanID = vecChannelOrder[i]; + + if ( i == iCurNumChannels ) { - // IP found, return channel number - if ( InetAddr == CheckAddr ) - { - return i; - } + qDebug() << "----------"; } - } - // IP not found, return invalid ID - return INVALID_CHANNEL_ID; + qDebug() << qUtf8Printable ( QString ( "%1: [%2] %3" ).arg ( i, 3 ).arg ( iChanID ).arg ( vecChannels[iChanID].GetAddress().toString() ) ); + } } void CServer::OnProtcolCLMessageReceived ( int iRecID, CVector vecbyMesBodyData, CHostAddress RecHostAddr ) @@ -1543,48 +1636,13 @@ bool CServer::PutAudioData ( const CVector& vecbyRecBuf, const int iNum QMutexLocker locker ( &Mutex ); bool bNewConnection = false; // init return value - bool bChanOK = true; // init with ok, might be overwritten // Get channel ID ------------------------------------------------------ // check address - iCurChanID = FindChannel ( HostAdr ); - - if ( iCurChanID == INVALID_CHANNEL_ID ) - { - // a new client is calling, look for free channel - iCurChanID = GetFreeChan(); + iCurChanID = FindChannel ( HostAdr, true /* allow new */ ); - if ( iCurChanID != INVALID_CHANNEL_ID ) - { - // initialize current channel by storing the calling host - // address - vecChannels[iCurChanID].SetAddress ( HostAdr ); - - // reset channel info - vecChannels[iCurChanID].ResetInfo(); - - // reset the channel gains/pans of current channel, at the same - // time reset gains/pans of this channel ID for all other channels - for ( int i = 0; i < iMaxNumChannels; i++ ) - { - vecChannels[iCurChanID].SetGain ( i, 1.0 ); - vecChannels[iCurChanID].SetPan ( i, 0.5 ); - - // other channels (we do not distinguish the case if - // i == iCurChanID for simplicity) - vecChannels[i].SetGain ( iCurChanID, 1.0 ); - vecChannels[i].SetPan ( iCurChanID, 0.5 ); - } - } - else - { - // no free channel available - bChanOK = false; - } - } - - // Put received audio data in jitter buffer ---------------------------- - if ( bChanOK ) + // If channel is valid or new, put received audio data in jitter buffer ---------------------------- + if ( iCurChanID != INVALID_CHANNEL_ID ) { // put packet in socket buffer if ( vecChannels[iCurChanID].PutAudioData ( vecbyRecBuf, iNumBytesRead, HostAdr ) == PS_NEW_CONNECTION ) @@ -1603,8 +1661,6 @@ void CServer::GetConCliParam ( CVector& vecHostAddresses, CVector& veciJitBufNumFrames, CVector& veciNetwFrameSizeFact ) { - CHostAddress InetAddr; - // init return values vecHostAddresses.Init ( iMaxNumChannels ); vecsName.Init ( iMaxNumChannels ); @@ -1614,10 +1670,10 @@ void CServer::GetConCliParam ( CVector& vecHostAddresses, // check all possible channels for ( int i = 0; i < iMaxNumChannels; i++ ) { - if ( vecChannels[i].GetAddress ( InetAddr ) ) + if ( vecChannels[i].IsConnected() ) { // get requested data - vecHostAddresses[i] = InetAddr; + vecHostAddresses[i] = vecChannels[i].GetAddress(); vecsName[i] = vecChannels[i].GetName(); veciJitBufNumFrames[i] = vecChannels[i].GetSockBufNumFrames(); veciNetwFrameSizeFact[i] = vecChannels[i].GetNetwFrameSizeFact(); diff --git a/src/server.h b/src/server.h index f7ed776ee5..1f10747b00 100644 --- a/src/server.h +++ b/src/server.h @@ -257,8 +257,10 @@ class CServer : public QObject, public CServerSlots // access functions for actual channels bool IsConnected ( const int iChanNum ) { return vecChannels[iChanNum].IsConnected(); } - int GetFreeChan(); - int FindChannel ( const CHostAddress& CheckAddr ); + int FindChannel ( const CHostAddress& CheckAddr, const bool bAllowNew = false ); + void InitChannel ( const int iNewChanID, const CHostAddress& InetAddr ); + void FreeChannel ( const int iCurChanID ); + void DumpChannels ( const QString& title ); int GetNumberOfConnectedClients(); CVector CreateChannelList(); @@ -305,8 +307,13 @@ class CServer : public QObject, public CServerSlots // do not use the vector class since CChannel does not have appropriate // copy constructor/operator - CChannel vecChannels[MAX_NUM_CHANNELS]; - int iMaxNumChannels; + CChannel vecChannels[MAX_NUM_CHANNELS]; + int iMaxNumChannels; + + int iCurNumChannels; + int vecChannelOrder[MAX_NUM_CHANNELS]; + QMutex MutexChanOrder; + CProtocol ConnLessProtocol; QMutex Mutex; QMutex MutexWelcomeMessage; diff --git a/src/util.cpp b/src/util.cpp index 69b351047e..39674f007b 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -877,6 +877,50 @@ bool NetworkUtil::IsPrivateNetworkIP ( const QHostAddress& qhAddr ) return false; } +// CHostAddress methods +// Compare() - compare two CHostAddress objects, and return an ordering between them: +// 0 - they are equal +// <0 - this comes before other +// >0 - this comes after other +// The order is not important, so long as it is consistent, for use in a binary search. + +int CHostAddress::Compare ( const CHostAddress& other ) const +{ + // compare port first, as it is cheap, and clients will often use random ports + + if ( iPort != other.iPort ) + { + return (int) iPort - (int) other.iPort; + } + + // compare protocols before addresses + + QAbstractSocket::NetworkLayerProtocol thisProto = InetAddr.protocol(); + QAbstractSocket::NetworkLayerProtocol otherProto = other.InetAddr.protocol(); + + if ( thisProto != otherProto ) + { + return (int) thisProto - (int) otherProto; + } + + // now we know both addresses are the same protocol + + if ( thisProto == QAbstractSocket::IPv6Protocol ) + { + // compare IPv6 addresses + Q_IPV6ADDR thisAddr = InetAddr.toIPv6Address(); + Q_IPV6ADDR otherAddr = other.InetAddr.toIPv6Address(); + + return memcmp ( &thisAddr, &otherAddr, sizeof ( Q_IPV6ADDR ) ); + } + + // compare IPv4 addresses + quint32 thisAddr = InetAddr.toIPv4Address(); + quint32 otherAddr = other.InetAddr.toIPv4Address(); + + return (int) thisAddr - (int) otherAddr; +} + // Instrument picture data base ------------------------------------------------ CVector& CInstPictures::GetTable ( const bool bReGenerateTable ) { diff --git a/src/util.h b/src/util.h index 285ab1ab31..8ab704a163 100644 --- a/src/util.h +++ b/src/util.h @@ -720,6 +720,8 @@ class CHostAddress // compare operator bool operator== ( const CHostAddress& CompAddr ) const { return ( ( CompAddr.InetAddr == InetAddr ) && ( CompAddr.iPort == iPort ) ); } + int Compare ( const CHostAddress& other ) const; + QString toString ( const EStringMode eStringMode = SM_IP_PORT ) const { QString strReturn = InetAddr.toString();