Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
6f5c808
Add --enabletcp option for server
softins Jan 21, 2025
1b99e62
Initial skeleton for TCP protocol server
softins Mar 22, 2024
67ad212
Add handling of CL msgs for Server and client list
softins Mar 25, 2024
f161d58
Create CLM_TCP_SUPPORTED and related methods
softins Jan 21, 2025
5156f49
Add CLM_TCP_SUPPORTED message generation when TCP enabled
softins Mar 9, 2026
fa91c4a
Add flag to request TCP client use
softins Mar 16, 2026
50d3a0b
Add handlers for TCP Supported message
softins Mar 16, 2026
f11002c
Propagate TCP client flag down to OnSendCLProtMessage
softins Mar 16, 2026
82436cd
Delete QTcpServer object when done
softins Mar 19, 2026
e1f0b44
Added some debug output
softins Mar 20, 2026
c0b5930
Update copyright years
softins Mar 20, 2026
fe107b9
Separate CTcpConnection code from CTcpServer
softins Mar 21, 2026
3ff35e9
Make CTcpConnection members private
softins Mar 22, 2026
68b2e4b
Add client-side TCP code
softins Mar 23, 2026
1a240ce
Request server list via TCP if required
softins Mar 23, 2026
53608c4
Add message context parameter for CLM_TCP_SUPPORTED
softins Mar 25, 2026
3e780eb
Add Qt version check for errorOccurred()
softins Mar 25, 2026
ea4a9a8
Fetch client list over TCP when necessary for a server
softins Mar 26, 2026
13ac448
Create CLM_CLIENT_ID and related methods
softins Apr 1, 2026
5f0c7ce
Add OnClientIDReceived slot to CChannel
softins Apr 1, 2026
ea1567c
Skeleton support for connected mode TCP
softins Apr 1, 2026
049c763
Move TCP debug message from connectdlg to client
softins Apr 2, 2026
f65206f
Add missing return
softins Apr 2, 2026
5d4bbc0
Remove unneeded #include from tcpconnection
softins Apr 2, 2026
9af106f
Send client list via TCP when connection available
softins Apr 4, 2026
a7c96bb
Add skeleton handler for CLClientID to CServer
softins Apr 4, 2026
136de93
Add disconnecFromHost method to CTcpConnection
softins Apr 5, 2026
e86cff2
Mods to CTcpConnection for future use
softins Apr 5, 2026
ae96f7c
In server, link TCP channel to UDP channel by client ID
softins Apr 5, 2026
cef9b56
Replace boolean TCP flag with multimode enum
softins Apr 5, 2026
a90ed45
Add creation of session-long TCP connection
softins Apr 6, 2026
0df0eca
Route CLConnClientList depending on whether connected
softins Apr 6, 2026
4542044
Be specific about bDisconAfterRecv for TCP
softins Apr 7, 2026
45f42ce
Rework TCP session-mode connection
softins Apr 7, 2026
5044eed
Minor comment updates
softins Apr 7, 2026
41adeb7
Add support for sending Empty Message over TCP
softins Apr 8, 2026
1704686
Implement keepalive over session long TCP connection
softins Apr 9, 2026
00d4f2b
Clarify comment
softins Apr 9, 2026
3eb801e
Make CTcpConnection work in serveronly mode.
softins Apr 9, 2026
be8acbb
Add timeout for TCP connection
softins Apr 9, 2026
fc2e58c
Add an idle timeout on the server side
softins May 21, 2026
e443c66
Add document describing TCP operation
softins May 28, 2026
2da453e
Update copyright headers for new source files
softins Jun 8, 2026
0a3009b
Use new way to discover IPv6 availability
softins Jun 8, 2026
8af225b
Only quote port number in TCP server start message.
softins Jun 9, 2026
b85b415
Small changes to address review comments
softins Jun 9, 2026
6c33975
Improve naming of TCP signals and slots
softins Jun 11, 2026
a41ab9f
Update protocol ID values
softins Jun 19, 2026
efdad4f
Add FS_TCP_ENABLED to server features
softins Jun 19, 2026
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
4 changes: 4 additions & 0 deletions Jamulus.pro
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,8 @@ HEADERS += src/plugins/audioreverb.h \
src/serverlogging.h \
src/settings.h \
src/socket.h \
src/tcpserver.h \
src/tcpconnection.h \
src/util.h \
src/recorder/jamrecorder.h \
src/recorder/creaperproject.h \
Expand Down Expand Up @@ -507,6 +509,8 @@ SOURCES += src/plugins/audioreverb.cpp \
src/settings.cpp \
src/signalhandler.cpp \
src/socket.cpp \
src/tcpserver.cpp \
src/tcpconnection.cpp \
src/util.cpp \
src/recorder/jamrecorder.cpp \
src/recorder/creaperproject.cpp \
Expand Down
215 changes: 215 additions & 0 deletions docs/TCP.md

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, makes sense. Next to try and work out how the fits around it 🙂 .

Large diffs are not rendered by default.

25 changes: 24 additions & 1 deletion src/channel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

// CChannel implementation *****************************************************
CChannel::CChannel ( const bool bNIsServer ) :
pTcpConnection ( nullptr ),
vecfGains ( MAX_NUM_CHANNELS, 1.0f ),
vecfPannings ( MAX_NUM_CHANNELS, 0.5f ),
iCurSockBufNumFrames ( INVALID_INDEX ),
Expand Down Expand Up @@ -103,7 +104,7 @@ CChannel::CChannel ( const bool bNIsServer ) :

QObject::connect ( &Protocol, &CProtocol::ChangeChanPan, this, &CChannel::OnChangeChanPan );

QObject::connect ( &Protocol, &CProtocol::ClientIDReceived, this, &CChannel::ClientIDReceived );
QObject::connect ( &Protocol, &CProtocol::ClientIDReceived, this, &CChannel::OnClientIDReceived );

QObject::connect ( &Protocol, &CProtocol::RawAudioSupported, this, &CChannel::RawAudioSupported );

Expand Down Expand Up @@ -736,3 +737,25 @@ void CChannel::UpdateSocketBufferSize()
SetSockBufNumFrames ( SockBuf.GetAutoSetting(), true );
}
}

void CChannel::OnClientIDReceived ( int iChanID )
{
qDebug() << Q_FUNC_INFO << "iChanID =" << iChanID;
emit ClientIDReceived ( iChanID );
}

void CChannel::CreateConClientListMes ( const CVector<CChannelInfo>& vecChanInfo, CProtocol& ConnLessProtocol )
{
if ( pTcpConnection )
{
qDebug() << "- sending client list via TCP";

ConnLessProtocol.CreateCLConnClientsListMes ( InetAddr, vecChanInfo, pTcpConnection );
}
else
{
qDebug() << "- sending client list via UDP";

Protocol.CreateConClientListMes ( vecChanInfo );
}
}
15 changes: 10 additions & 5 deletions src/channel.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ class CChannel : public QObject
void SetAddress ( const CHostAddress& NAddr ) { InetAddr = NAddr; }
const CHostAddress& GetAddress() const { return InetAddr; }

void SetTcpConnection ( CTcpConnection* pConnection ) { pTcpConnection = pConnection; }
CTcpConnection* GetTcpConnection() { return pTcpConnection; }

void ResetInfo()
{
bIsIdentified = false;
Expand Down Expand Up @@ -184,7 +187,7 @@ class CChannel : public QObject
void CreateReqChannelLevelListMes() { Protocol.CreateReqChannelLevelListMes(); }
//### TODO: END ###//

void CreateConClientListMes ( const CVector<CChannelInfo>& vecChanInfo ) { Protocol.CreateConClientListMes ( vecChanInfo ); }
void CreateConClientListMes ( const CVector<CChannelInfo>& vecChanInfo, CProtocol& ConnLessProtocol );

void CreateRecorderStateMes ( const ERecorderState eRecorderState ) { Protocol.CreateRecorderStateMes ( eRecorderState ); }

Expand All @@ -209,7 +212,8 @@ class CChannel : public QObject
}

// connection parameters
CHostAddress InetAddr;
CHostAddress InetAddr;
CTcpConnection* pTcpConnection;

// channel info
CChannelCoreInfo ChannelInfo;
Expand Down Expand Up @@ -278,11 +282,12 @@ public slots:
PutProtocolData ( iRecCounter, iRecID, vecbyMesBodyData, RecHostAddr );
}

void OnProtocolCLMessageReceived ( int iRecID, CVector<uint8_t> vecbyMesBodyData, CHostAddress RecHostAddr )
void OnProtocolCLMessageReceived ( int iRecID, CVector<uint8_t> vecbyMesBodyData, CHostAddress RecHostAddr, CTcpConnection* pTcpConnection )
{
emit DetectedCLMessage ( vecbyMesBodyData, iRecID, RecHostAddr );
emit DetectedCLMessage ( vecbyMesBodyData, iRecID, RecHostAddr, pTcpConnection );
}

void OnClientIDReceived ( int iChanID );
void OnNewConnection() { emit NewConnection(); }

signals:
Expand All @@ -306,7 +311,7 @@ public slots:
void RecorderStateReceived ( ERecorderState eRecorderState );
void Disconnected();

void DetectedCLMessage ( CVector<uint8_t> vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr );
void DetectedCLMessage ( CVector<uint8_t> vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr, CTcpConnection* pTcpConnection );

void ParseMessageBody ( CVector<uint8_t> vecbyMesBodyData, int iRecCounter, int iRecID );
};
141 changes: 136 additions & 5 deletions src/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ CClient::CClient ( const quint16 iPortNumber,

QObject::connect ( &ConnLessProtocol, &CProtocol::CLRedServerListReceived, this, &CClient::CLRedServerListReceived );

QObject::connect ( &ConnLessProtocol, &CProtocol::CLConnClientsListMesReceived, this, &CClient::CLConnClientsListMesReceived );
QObject::connect ( &ConnLessProtocol, &CProtocol::CLTcpSupportedReceived, this, &CClient::OnCLTcpSupportedReceived );

QObject::connect ( &ConnLessProtocol, &CProtocol::CLConnClientsListMesReceived, this, &CClient::OnCLConnClientsListMesReceived );

QObject::connect ( &ConnLessProtocol, &CProtocol::CLPingReceived, this, &CClient::OnCLPingReceived );

Expand Down Expand Up @@ -266,11 +268,81 @@ void CClient::OnSendProtMessage ( CVector<uint8_t> vecMessage )
Socket.SendPacket ( vecMessage, Channel.GetAddress() );
}

void CClient::OnSendCLProtMessage ( CHostAddress InetAddr, CVector<uint8_t> vecMessage )
void CClient::OnSendCLProtMessage ( CHostAddress InetAddr, CVector<uint8_t> vecMessage, CTcpConnection* pTcpConnection, enum EProtoMode eProtoMode )
{
if ( pTcpConnection )
{
// already have TCP connection - just send and return
pTcpConnection->write ( (const char*) &( (CVector<uint8_t>) vecMessage )[0], vecMessage.Size() );
return;
}

// the protocol queries me to call the function to send the message
// send it through the network
Socket.SendPacket ( vecMessage, InetAddr );
if ( eProtoMode != PROTO_UDP )
{
// create a TCP client connection and send message
QTcpSocket* pSocket = new QTcpSocket ( this );

// timer for TCP connect timeout because Qt defaults to 30 seconds
// and we want it to be 3 seconds (TCP_CONNECT_TIMEOUT_MS)
QTimer* pTimer = new QTimer ( this );
pTimer->setSingleShot ( true );

connect ( pTimer, &QTimer::timeout, this, [this, pSocket, pTimer, InetAddr]() {
if ( pSocket->state() != QAbstractSocket::ConnectedState )
{
pSocket->abort();
pSocket->deleteLater();
qWarning() << "- Jamulus-TCP: timeout connecting to" << InetAddr.toString();
}
pTimer->deleteLater();
} );

#if QT_VERSION >= QT_VERSION_CHECK( 5, 15, 0 )
# define ERRORSIGNAL &QTcpSocket::errorOccurred
#else
# define ERRORSIGNAL QOverload<QAbstractSocket::SocketError>::of ( &QAbstractSocket::error )
#endif
connect ( pSocket, ERRORSIGNAL, this, [this, pSocket, pTimer] ( QAbstractSocket::SocketError err ) {
Q_UNUSED ( err );

pTimer->stop();
pTimer->deleteLater();

qWarning() << "- TCP connection error:" << pSocket->errorString();
// may want to specifically handle ConnectionRefusedError?

@pljones pljones May 28, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is knowing why the connection was refused. Saying "Maybe check your firewall" isn't much help. Client-side is always tricky because most diagnostics never get looked at.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can always revisit it based on experience.

pSocket->deleteLater();
} );

connect ( pSocket, &QTcpSocket::connected, this, [this, pSocket, pTimer, InetAddr, vecMessage, eProtoMode]() {
pTimer->stop();
pTimer->deleteLater();

// connection succeeded, give it to a CTcpConnection
CTcpConnection* pTcpConnection = new CTcpConnection ( pSocket,
InetAddr,
this,
&Channel,
eProtoMode == PROTO_TCP_LONG ); // client connection, will self-delete on disconnect

if ( eProtoMode == PROTO_TCP_LONG )
{
Channel.SetTcpConnection ( pTcpConnection ); // link session connection with channel
}

pTcpConnection->write ( (const char*) &( (CVector<uint8_t>) vecMessage )[0], vecMessage.Size() );

// the CTcpConnection object will pass the reply back up to CClient::Channel
} );

pSocket->connectToHost ( InetAddr.InetAddr, InetAddr.iPort );
pTimer->start ( TCP_CONNECT_TIMEOUT_MS );
}
else
{
Socket.SendPacket ( vecMessage, InetAddr );
}
}

void CClient::OnInvalidPacketReceived ( CHostAddress RecHostAddr )
Expand All @@ -285,10 +357,10 @@ void CClient::OnInvalidPacketReceived ( CHostAddress RecHostAddr )
}
}

void CClient::OnDetectedCLMessage ( CVector<uint8_t> vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr )
void CClient::OnDetectedCLMessage ( CVector<uint8_t> vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr, CTcpConnection* pTcpConnection )
{
// connection less messages are always processed
ConnLessProtocol.ParseConnectionLessMessageBody ( vecbyMesBodyData, iRecID, RecHostAddr );
ConnLessProtocol.ParseConnectionLessMessageBody ( vecbyMesBodyData, iRecID, RecHostAddr, pTcpConnection );
}

void CClient::OnJittBufSizeChanged ( int iNewJitBufSize )
Expand Down Expand Up @@ -992,6 +1064,16 @@ void CClient::OnClientIDReceived ( int iServerChanID )
ClearClientChannels();
}

// if TCP Supported has already been received, make TCP connection to server
iClientID = iServerChanID; // for sending back to server over TCP

if ( bTcpSupported )
{
// *** Make TCP connection
qDebug() << Q_FUNC_INFO << "need to make TCP connection for" << iClientID;
ConnLessProtocol.CreateCLClientIDMes ( Channel.GetAddress(), iClientID, PROTO_TCP_LONG ); // create persistent TCP connection
}

// allocate and map client-side channel 0
int iChanID = FindClientChannel ( iServerChanID, true ); // should always return channel 0

Expand Down Expand Up @@ -1027,11 +1109,52 @@ void CClient::OnRawAudioSupported()
}
}

void CClient::OnCLTcpSupportedReceived ( CHostAddress InetAddr, int iID )
{
qDebug() << "- TCP supported at server" << InetAddr.toString() << "for ID =" << iID;

if ( iID != PROTMESSID_CLM_CLIENT_ID )
{
emit CLTcpSupportedReceived ( InetAddr, iID ); // pass to connect dialog
return;
}

// if client ID already received, make TCP connection to server
bTcpSupported = true;

if ( iClientID != INVALID_INDEX )
{
// *** Make TCP connection
qDebug() << Q_FUNC_INFO << "need to make TCP connection for" << iClientID;
Q_ASSERT ( InetAddr == Channel.GetAddress() );
ConnLessProtocol.CreateCLClientIDMes ( InetAddr, iClientID, PROTO_TCP_LONG ); // create persistent TCP connection
}
}

void CClient::OnCLConnClientsListMesReceived ( CHostAddress InetAddr, CVector<CChannelInfo> vecChanInfo, CTcpConnection* pTcpConnection )
{
// test if we are receiving for the connect dialog or a connected session
if ( pTcpConnection && pTcpConnection->IsSession() )
{
qDebug() << "- sending client list to client dialog";
OnConClientListMesReceived ( vecChanInfo ); // connected session
}
else
{
qDebug() << "- sending client list to connect dialog";
emit CLConnClientsListMesReceived ( InetAddr, vecChanInfo ); // connect dialog
}
}

void CClient::Start()
{
// init object
Init();

// clear TCP info
iClientID = INVALID_INDEX;
bTcpSupported = false;

// initialise client channels
ClearClientChannels();

Expand All @@ -1052,6 +1175,14 @@ void CClient::Stop()
// stop audio interface
Sound.Stop();

// close any session TCP connection
CTcpConnection* pTcpConnection = Channel.GetTcpConnection();
if ( pTcpConnection )
{
Channel.SetTcpConnection ( nullptr );
pTcpConnection->disconnectFromHost();
}

// disable channel
Channel.SetEnable ( false );

Expand Down
28 changes: 24 additions & 4 deletions src/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,15 @@ class CClient : public QObject

void CreateCLServerListReqVerAndOSMes ( const CHostAddress& InetAddr ) { ConnLessProtocol.CreateCLReqVersionAndOSMes ( InetAddr ); }

void CreateCLServerListReqConnClientsListMes ( const CHostAddress& InetAddr ) { ConnLessProtocol.CreateCLReqConnClientsListMes ( InetAddr ); }
void CreateCLServerListReqConnClientsListMes ( const CHostAddress& InetAddr, enum EProtoMode eProtoMode )
{
ConnLessProtocol.CreateCLReqConnClientsListMes ( InetAddr, eProtoMode );
}

void CreateCLReqServerListMes ( const CHostAddress& InetAddr ) { ConnLessProtocol.CreateCLReqServerListMes ( InetAddr ); }
void CreateCLReqServerListMes ( const CHostAddress& InetAddr, enum EProtoMode eProtoMode )
{
ConnLessProtocol.CreateCLReqServerListMes ( InetAddr, eProtoMode );
}

int EstimatedOverallDelay ( const int iPingTimeMs );

Expand Down Expand Up @@ -448,12 +454,16 @@ class CClient : public QObject
int maxGainOrPanId;
int iCurPingTime;

// for TCP protocol support
bool bTcpSupported;
int iClientID;

protected slots:
void OnHandledSignal ( int sigNum );
void OnSendProtMessage ( CVector<uint8_t> vecMessage );
void OnInvalidPacketReceived ( CHostAddress RecHostAddr );

void OnDetectedCLMessage ( CVector<uint8_t> vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr );
void OnDetectedCLMessage ( CVector<uint8_t> vecbyMesBodyData, int iRecID, CHostAddress RecHostAddr, CTcpConnection* pTcpConnection );

void OnReqJittBufSize() { CreateServerJitterBufferMessage(); }
void OnJittBufSizeChanged ( int iNewJitBufSize );
Expand All @@ -467,8 +477,9 @@ protected slots:
}
}
void OnCLPingReceived ( CHostAddress InetAddr, int iMs );
void OnCLTcpSupportedReceived ( CHostAddress InetAddr, int iID );

void OnSendCLProtMessage ( CHostAddress InetAddr, CVector<uint8_t> vecMessage );
void OnSendCLProtMessage ( CHostAddress InetAddr, CVector<uint8_t> vecMessage, CTcpConnection* pTcpConnection, enum EProtoMode eProtoMode );

void OnCLPingWithNumClientsReceived ( CHostAddress InetAddr, int iMs, int iNumClients );

Expand All @@ -483,6 +494,13 @@ protected slots:
void OnMuteStateHasChangedReceived ( int iServerChanID, bool bIsMuted );
void OnCLChannelLevelListReceived ( CHostAddress InetAddr, CVector<uint16_t> vecLevelList );
void OnConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo );
void OnCLConnClientsListMesReceived ( CHostAddress InetAddr, CVector<CChannelInfo> vecChanInfo, CTcpConnection* pTcpConnection );

public slots:
void OnCLSendEmptyMes ( CHostAddress InetAddr, CTcpConnection* pTcpConnection )
{
ConnLessProtocol.CreateCLEmptyMes ( InetAddr, pTcpConnection );
}

signals:
void ConClientListMesReceived ( CVector<CChannelInfo> vecChanInfo );
Expand All @@ -498,6 +516,8 @@ protected slots:

void CLRedServerListReceived ( CHostAddress InetAddr, CVector<CServerInfo> vecServerInfo );

void CLTcpSupportedReceived ( CHostAddress InetAddr, int iID );

void CLConnClientsListMesReceived ( CHostAddress InetAddr, CVector<CChannelInfo> vecChanInfo );

void CLPingTimeWithNumClientsReceived ( CHostAddress InetAddr, int iPingTime, int iNumClients );
Expand Down
2 changes: 2 additions & 0 deletions src/clientdlg.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,8 @@ CClientDlg::CClientDlg ( CClient* pNCliP,

QObject::connect ( pClient, &CClient::CLRedServerListReceived, this, &CClientDlg::OnCLRedServerListReceived );

QObject::connect ( pClient, &CClient::CLTcpSupportedReceived, this, &CClientDlg::OnCLTcpSupportedReceived );

QObject::connect ( pClient, &CClient::CLConnClientsListMesReceived, this, &CClientDlg::OnCLConnClientsListMesReceived );

QObject::connect ( pClient, &CClient::CLPingTimeWithNumClientsReceived, this, &CClientDlg::OnCLPingTimeWithNumClientsReceived );
Expand Down
Loading
Loading