Skip to content

Commit b4bc662

Browse files
authored
Update V1.9.1
1 parent 40fac5f commit b4bc662

7 files changed

Lines changed: 2966 additions & 80 deletions

File tree

DbServerClient.h

Lines changed: 76 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@
2828
#include <utility>
2929
#include <cstring>
3030
#include <thread>
31+
#ifdef _WIN32
32+
#include <winsock2.h>
33+
#include <ws2tcpip.h>
34+
#else
35+
#include <netinet/tcp.h>
36+
#endif
3137
#include "NfsAnlzFetcher.h"
3238
#include "WaveformCache.h"
3339
#include "AppSettings.h"
@@ -500,6 +506,7 @@ class DbServerClient : private juce::Thread
500506
static constexpr int kMaxArtCacheEntries = 64;
501507
static constexpr int kMaxConnections = 6;
502508
static constexpr int kReconnectCooldownMs = 5000;
509+
static constexpr double kIdleTimeoutMs = 30000.0; // close connections idle for >30s
503510

504511
// NXS2 extension constants for color waveform
505512
static constexpr uint32_t kNxs2ExtRequest = 0x2c04;
@@ -558,6 +565,7 @@ class DbServerClient : private juce::Thread
558565
uint8_t contextPlayer = 0; // player number used in setupQueryContext
559566
uint32_t txId = 0;
560567
double lastFailTime = 0.0;
568+
double lastActivityTime = 0.0; // hiRes ms -- for idle timeout
561569

562570
bool isConnected() const { return socket && socket->isConnected(); }
563571

@@ -572,6 +580,7 @@ class DbServerClient : private juce::Thread
572580
contextPlayer = 0;
573581
txId = 0;
574582
dbPort = 0;
583+
lastActivityTime = 0.0;
575584
playerIP.clear();
576585
}
577586
};
@@ -908,6 +917,7 @@ class DbServerClient : private juce::Thread
908917
conn.lastFailTime = 0.0; // intentional close, not a failure
909918
break; // fall through to new connection below
910919
}
920+
conn.lastActivityTime = juce::Time::getMillisecondCounterHiRes();
911921
return &conn;
912922
}
913923
}
@@ -962,6 +972,19 @@ class DbServerClient : private juce::Thread
962972
return nullptr;
963973
}
964974

975+
// Disable Nagle's algorithm -- ensure each dbserver message is sent as
976+
// a single TCP segment. Some players fail to parse messages that arrive
977+
// split across multiple packets.
978+
{
979+
auto fd = sock->getRawSocketHandle();
980+
if (fd >= 0)
981+
{
982+
int flag = 1;
983+
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
984+
reinterpret_cast<const char*>(&flag), sizeof(flag));
985+
}
986+
}
987+
965988
// Step 3: Send initial handshake -- [0x11, 0x00000001]
966989
{
967990
uint8_t hello[] = { 0x11, 0x00, 0x00, 0x00, 0x01 };
@@ -986,6 +1009,7 @@ class DbServerClient : private juce::Thread
9861009
slot->playerIP = playerIP;
9871010
slot->dbPort = dbPort;
9881011
slot->contextSetUp = false;
1012+
slot->lastActivityTime = now;
9891013
slot->txId = 0;
9901014
slot->lastFailTime = 0.0;
9911015

@@ -1001,8 +1025,37 @@ class DbServerClient : private juce::Thread
10011025
return slot;
10021026
}
10031027

1004-
/// Discover the dbserver port by querying TCP 12523
1028+
/// Discover the dbserver port by querying TCP 12523.
1029+
/// Retries a few times because the player may not be ready to respond
1030+
/// immediately after booting or loading media.
10051031
int discoverDbPort(const juce::String& playerIP)
1032+
{
1033+
static constexpr int kMaxRetries = 3;
1034+
static constexpr int kRetryDelayMs = 1000;
1035+
1036+
for (int attempt = 0; attempt < kMaxRetries; ++attempt)
1037+
{
1038+
if (attempt > 0)
1039+
{
1040+
DBG("DbServerClient: port discovery retry " + juce::String(attempt)
1041+
+ "/" + juce::String(kMaxRetries - 1) + " for " + playerIP);
1042+
juce::Thread::sleep(kRetryDelayMs);
1043+
if (!isRunningFlag.load(std::memory_order_relaxed))
1044+
return 0;
1045+
}
1046+
1047+
int port = discoverDbPortOnce(playerIP);
1048+
if (port > 0)
1049+
return port;
1050+
}
1051+
1052+
DBG("DbServerClient: port discovery failed after " + juce::String(kMaxRetries)
1053+
+ " attempts for " + playerIP);
1054+
return 0;
1055+
}
1056+
1057+
/// Single attempt at port discovery via TCP 12523
1058+
int discoverDbPortOnce(const juce::String& playerIP)
10061059
{
10071060
DBG("DbServerClient: discovering db port on " + playerIP + ":12523");
10081061

@@ -1040,9 +1093,10 @@ class DbServerClient : private juce::Thread
10401093
int port = (int(response[0]) << 8) | response[1];
10411094
DBG("DbServerClient: discovered db port " + juce::String(port) + " on " + playerIP);
10421095

1043-
if (port <= 0 || port > 65535)
1096+
if (port <= 0 || port >= 65535)
10441097
{
1045-
DBG("DbServerClient: invalid port " + juce::String(port));
1098+
DBG("DbServerClient: invalid port " + juce::String(port)
1099+
+ " (0xFFFF = no service available)");
10461100
return 0;
10471101
}
10481102

@@ -2182,6 +2236,25 @@ class DbServerClient : private juce::Thread
21822236

21832237
if (threadShouldExit()) break;
21842238

2239+
// Close idle connections — prevents zombie TCP connections from
2240+
// holding CDJ NFS slots (CDJ-2000NXS2 has limited slots).
2241+
{
2242+
double now = juce::Time::getMillisecondCounterHiRes();
2243+
for (auto& conn : connections)
2244+
{
2245+
if (conn.isConnected() && conn.lastActivityTime > 0.0
2246+
&& (now - conn.lastActivityTime) > kIdleTimeoutMs)
2247+
{
2248+
DBG("DbServerClient: closing idle connection to "
2249+
+ conn.playerIP + ":" + juce::String(conn.dbPort)
2250+
+ " (idle " + juce::String((int)((now - conn.lastActivityTime) / 1000.0)) + "s)");
2251+
conn.close();
2252+
}
2253+
}
2254+
}
2255+
2256+
if (threadShouldExit()) break;
2257+
21852258
// Process all queued requests
21862259
while (true)
21872260
{

0 commit comments

Comments
 (0)