When using the artnet4j library in a Processing 4 application, the library creates a UDP port binding even when acting as an ArtNet Sender. This behavior leads to binding conflicts with other applications that may need to communicate locally, such as DigiShow LINK and QLC+, which do not bind to a specific port when sending ArtNet data.
Expected Behavior
ArtNet Sender applications should use an ephemeral port for sending data and should not bind to a specific port (e.g. 6454). Only ArtNet Receiver applications require a static bind to listen for incoming ArtNet packets.
Observed Behavior
The artnet4j library binds a port regardless of whether it is used as a Sender or Receiver. This causes conflicts when other applications attempt to bind to the same port.
Steps to Reproduce
-
Condition 1: Sender = Processing 4, Receiver = DigiShow LINK
- Launch DigiShow LINK before Processing 4: No port conflict.
- Launch Processing 4 before DigiShow LINK: Port conflict occurs.
-
Condition 2: Receiver = Processing 4, Sender = DigiShow LINK
- Launch DigiShow LINK before Processing 4: No port conflict.
- Launch Processing 4 before DigiShow LINK: No port conflict.
-
Extra Tests:
- DigiShow LINK Sender launched in isolation: No binding on port 6454.
- Processing 4 Sender launched in isolation: Binding occurs on port 6454.
Diagnostic Results
Using lsof
on macOS reveals that DigiShow LINK does not create a binding when sending ArtNet, but the Processing 4 application using artnet4j does:
Validation of Ephemeral Port Usage in DigiShow LINK
After examining the DigiShow LINK source code, it was confirmed that DigiShow LINK uses ephemeral ports for sending ArtNet data. This approach prevents port conflicts and allows the application to coexist with other applications that need to bind as a Reciever.
Key Points from dgs_artnet_interface.cpp
:
-
UDP Port Definition:
#define ARTNET_UDP_PORT 6454
-
UDP Socket Initialization:
m_udp = new QUdpSocket();
-
Binding the UDP Socket for Input:
if (m_interfaceInfo.mode == INTERFACE_ARTNET_INPUT) {
// for artnet receiver
m_dataAll.clear();
m_udp->bind(m_udpPort);
connect(m_udp, SIGNAL(readyRead()), this, SLOT(onUdpDataReceived()));
}
-
Setting Up the UDP Socket for Output:
if (m_interfaceInfo.mode == INTERFACE_ARTNET_OUTPUT) {
// for artnet sender
m_sequence = 0;
const unsigned char bytes[] = {
0x41, 0x72, 0x74, 0x2d, 0x4e, 0x65, 0x74, 0x00, // Art-Net
0x00, 0x20, // opcode ARTNET_POLL
0x00, 0x0e, // version
0x00, 0x00 // take-to-me
};
m_udp->writeDatagram((const char*)bytes, sizeof(bytes),
m_udpHost,
(quint16)m_udpPort);
m_timer = new QTimer();
connect(m_timer, SIGNAL(timeout()), this, SLOT(onTimerFired()));
m_timer->setTimerType(Qt::PreciseTimer);
m_timer->setSingleShot(false);
m_timer->setInterval(1000 / frequency);
m_timer->start();
}
Suggested Fix
Modify the artnet4j library to avoid static binding its port when acting solely as a Sender. Instead, it should use an ephemeral port for sending data.
Proposed Code Changes
Here is an example modification to the ArtNetServer
class:
public void start(InetAddress networkAddress, boolean isReceiver) throws SocketException, ArtNetException {
if (broadCastAddress == null) {
setBroadcastAddress(DEFAULT_BROADCAST_IP);
}
if (socket == null) {
socket = new DatagramSocket(null);
socket.setReuseAddress(true);
socket.setBroadcast(true);
if (isReceiver) {
if (networkAddress == null)
networkAddress = socket.getLocalAddress();
socket.bind(new InetSocketAddress(networkAddress, port));
logger.info("Art-Net server started at: " + networkAddress.getHostAddress() + ":" + port);
} else {
logger.info("Art-Net server started as sender using ephemeral port.");
}
for (ArtNetServerListener l : listeners) {
l.artNetServerStarted(this);
}
isRunning = true;
serverThread = new Thread(this);
serverThread.start();
} else {
throw new ArtNetException(
"Couldn't create server socket, server already running?");
}
}
In the application code, specify whether the instance is acting as a Sender or Receiver:
ArtNetServer server = new ArtNetServer();
server.start(null, false); // For sender
// or
server.start(null, true); // For receiver
Conclusion
By implementing these changes, the artnet4j library will avoid unnecessary port bindings when acting as a Sender, preventing conflicts with other applications and allowing for more flexible execution order.