From d260b28d5f68c97f4be72c42bf582ac0a260db7f Mon Sep 17 00:00:00 2001 From: Nils Sowen Date: Sun, 10 Jul 2022 12:49:00 +0200 Subject: [PATCH] Allow sending and receiving messages over the same UDP socket --- .../com/illposed/osc/transport/OSCPort.java | 11 ++++++ .../com/illposed/osc/transport/OSCPortIn.java | 22 +++++++++++ .../osc/transport/OSCPortInBuilder.java | 37 ++++++++++++++++-- .../illposed/osc/transport/OSCPortOut.java | 13 +++++++ .../osc/transport/OSCPortOutBuilder.java | 29 ++++++++++++++ .../osc/transport/udp/UDPTransport.java | 12 +++--- .../illposed/osc/transport/OSCPortTest.java | 38 +++++++++++++++++++ 7 files changed, 153 insertions(+), 9 deletions(-) diff --git a/modules/core/src/main/java/com/illposed/osc/transport/OSCPort.java b/modules/core/src/main/java/com/illposed/osc/transport/OSCPort.java index 7a980d97..0552fa52 100644 --- a/modules/core/src/main/java/com/illposed/osc/transport/OSCPort.java +++ b/modules/core/src/main/java/com/illposed/osc/transport/OSCPort.java @@ -70,6 +70,17 @@ protected OSCPort( this(local, remote, serializerAndParserBuilder, NetworkProtocol.UDP); } + protected OSCPort( + final Transport transport) + { + if (transport == null) { + throw new IllegalStateException( + "Can not use NULL as transport" + ); + } + this.transport = transport; + } + public Transport getTransport() { return transport; } diff --git a/modules/core/src/main/java/com/illposed/osc/transport/OSCPortIn.java b/modules/core/src/main/java/com/illposed/osc/transport/OSCPortIn.java index b18a90da..c247f0c6 100644 --- a/modules/core/src/main/java/com/illposed/osc/transport/OSCPortIn.java +++ b/modules/core/src/main/java/com/illposed/osc/transport/OSCPortIn.java @@ -86,6 +86,28 @@ public static List defaultPacketListeners() { return listeners; } + /** + * Create an OSC-Port that uses a concrete {@code transport} given + * as parameter and with {@link #isResilient() resilient} set to true. + * One must make sure that the appropriate connection + * has already been set up before using this instance, meaning + * that local and remote address are set correctly. + * @param transport the transport used for receiving OSC packets + * @param packetListeners to handle received and serialized OSC packets + * @throws IOException if we fail to bind a channel to the local address + */ + public OSCPortIn( + final Transport transport, + final List packetListeners) + { + super(transport); + + this.listening = false; + this.daemonListener = true; + this.resilient = true; + this.packetListeners = packetListeners; + } + /** * Create an OSC-Port that listens on the given local socket for packets from {@code remote}, * using a parser created with the given factory, diff --git a/modules/core/src/main/java/com/illposed/osc/transport/OSCPortInBuilder.java b/modules/core/src/main/java/com/illposed/osc/transport/OSCPortInBuilder.java index 3486419f..94b27150 100644 --- a/modules/core/src/main/java/com/illposed/osc/transport/OSCPortInBuilder.java +++ b/modules/core/src/main/java/com/illposed/osc/transport/OSCPortInBuilder.java @@ -24,6 +24,7 @@ public class OSCPortInBuilder { private SocketAddress local; private SocketAddress remote; private NetworkProtocol networkProtocol = NetworkProtocol.UDP; + private Transport transport; private OSCPacketListener addDefaultPacketListener() { if (packetListeners == null) { @@ -37,6 +38,33 @@ private OSCPacketListener addDefaultPacketListener() { } public OSCPortIn build() throws IOException { + + if (packetListeners == null) { + addDefaultPacketListener(); + } + + // If transport is set, other settings cannot be used + if (transport != null) { + if (remote != null) { + throw new IllegalArgumentException( + "Cannot use remote socket address / port in conjunction with transport object."); + } + + if (local != null) { + throw new IllegalArgumentException( + "Cannot use local socket address / port in conjunction with transport object."); + } + + if (parserBuilder != null) { + throw new IllegalArgumentException( + "Cannot use parserBuilder in conjunction with transport object."); + } + + return new OSCPortIn( + transport, packetListeners + ); + } + if (local == null) { throw new IllegalArgumentException( "Missing local socket address / port."); @@ -50,10 +78,6 @@ public OSCPortIn build() throws IOException { parserBuilder = new OSCSerializerAndParserBuilder(); } - if (packetListeners == null) { - addDefaultPacketListener(); - } - return new OSCPortIn( parserBuilder, packetListeners, local, remote, networkProtocol ); @@ -97,6 +121,11 @@ public OSCPortInBuilder setNetworkProtocol(final NetworkProtocol protocol) { return this; } + public OSCPortInBuilder setTransport(final Transport networkTransport) { + transport = networkTransport; + return this; + } + public OSCPortInBuilder setPacketListeners( final List listeners) { diff --git a/modules/core/src/main/java/com/illposed/osc/transport/OSCPortOut.java b/modules/core/src/main/java/com/illposed/osc/transport/OSCPortOut.java index 5e6436b8..51dda36f 100644 --- a/modules/core/src/main/java/com/illposed/osc/transport/OSCPortOut.java +++ b/modules/core/src/main/java/com/illposed/osc/transport/OSCPortOut.java @@ -108,6 +108,19 @@ public OSCPortOut() throws IOException { this(new InetSocketAddress(InetAddress.getLocalHost(), DEFAULT_SC_OSC_PORT)); } + /** + * Creates an OSC-Port that sends to a remote host from the specified given + * {@code transport} that was previously created using appropriate local + * and remote addresses, network protocol, and serializers. + * @param transport the transport used for sending OSC packets + * @throws IOException if we fail to bind a channel to the local address + */ + public OSCPortOut( + final Transport transport) + { + super(transport); + } + /** * Converts and sends an OSC packet (message or bundle) to the remote address. * @param packet the bundle or message to be converted and sent diff --git a/modules/core/src/main/java/com/illposed/osc/transport/OSCPortOutBuilder.java b/modules/core/src/main/java/com/illposed/osc/transport/OSCPortOutBuilder.java index 6792b3e2..e90eed8f 100644 --- a/modules/core/src/main/java/com/illposed/osc/transport/OSCPortOutBuilder.java +++ b/modules/core/src/main/java/com/illposed/osc/transport/OSCPortOutBuilder.java @@ -17,8 +17,32 @@ public class OSCPortOutBuilder { private SocketAddress remote; private SocketAddress local; private NetworkProtocol networkProtocol = NetworkProtocol.UDP; + private Transport transport; public OSCPortOut build() throws IOException { + + // If transport is set, other settings cannot be used + if (transport != null) { + if (remote != null) { + throw new IllegalArgumentException( + "Cannot use remote socket address / port in conjunction with transport."); + } + + if (local != null) { + throw new IllegalArgumentException( + "Cannot use local socket address / port in conjunction with transport."); + } + + if (serializerBuilder != null) { + throw new IllegalArgumentException( + "Cannot use serializerBuilder in conjunction with transport."); + } + + return new OSCPortOut( + transport + ); + } + if (remote == null) { throw new IllegalArgumentException( "Missing remote socket address / port."); @@ -74,4 +98,9 @@ public OSCPortOutBuilder setNetworkProtocol(final NetworkProtocol protocol) { networkProtocol = protocol; return this; } + + public OSCPortOutBuilder setTransport(final Transport networkTransport) { + transport = networkTransport; + return this; + } } diff --git a/modules/core/src/main/java/com/illposed/osc/transport/udp/UDPTransport.java b/modules/core/src/main/java/com/illposed/osc/transport/udp/UDPTransport.java index b3f741a8..0db0d841 100644 --- a/modules/core/src/main/java/com/illposed/osc/transport/udp/UDPTransport.java +++ b/modules/core/src/main/java/com/illposed/osc/transport/udp/UDPTransport.java @@ -24,7 +24,9 @@ /** * A {@link Transport} implementation for sending and receiving OSC packets over - * a network via UDP. + * a network via UDP. This implementation uses separate ByteBuffers for sending + * and receiving, making it possible to use the same UDP socket for sending packets + * and listening simultaneously. */ public class UDPTransport implements Transport { @@ -34,8 +36,8 @@ public class UDPTransport implements Transport { * incoming datagram data size. */ public static final int BUFFER_SIZE = 65507; - private final ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE); - + private final ByteBuffer sendBuffer = ByteBuffer.allocate(BUFFER_SIZE); + private final ByteBuffer receiveBuffer = ByteBuffer.allocate(BUFFER_SIZE); private final SocketAddress local; private final SocketAddress remote; private final DatagramChannel channel; @@ -131,12 +133,12 @@ public void close() throws IOException { @Override public void send(final OSCPacket packet) throws IOException, OSCSerializeException { - oscChannel.send(buffer, packet, remote); + oscChannel.send(sendBuffer, packet, remote); } @Override public OSCPacket receive() throws IOException, OSCParseException { - return oscChannel.read(buffer); + return oscChannel.read(receiveBuffer); } @Override diff --git a/modules/core/src/test/java/com/illposed/osc/transport/OSCPortTest.java b/modules/core/src/test/java/com/illposed/osc/transport/OSCPortTest.java index f38096da..78a1311c 100644 --- a/modules/core/src/test/java/com/illposed/osc/transport/OSCPortTest.java +++ b/modules/core/src/test/java/com/illposed/osc/transport/OSCPortTest.java @@ -331,6 +331,44 @@ public void testSocketAutoClose() throws Exception { .build(); } + @Test + public void testUseSameTransportForOutAndIn() throws Exception { + + if (sender != null) { + sender.close(); + } + + sender = new OSCPortOutBuilder() + .setRemoteSocketAddress(new InetSocketAddress(4711)) + .setLocalSocketAddress(new InetSocketAddress( 4711)) + .setNetworkProtocol(NetworkProtocol.UDP) + .build(); + + // create new receiver based on sender's transport implementation + receiver = new OSCPortInBuilder() + .setTransport(sender.getTransport()) + .build(); + + Assert.assertEquals("expected same transport for both sender and receiver", + sender.getTransport(), receiver.getTransport()); + + // exchange simple message + OSCMessage message = new OSCMessage("/message/receiving"); + SimpleOSCMessageListener listener = new SimpleOSCMessageListener(); + receiver.getDispatcher().addListener(new OSCPatternAddressMessageSelector("/message/receiving"), + listener); + + receiver.startListening(); + sender.send(message); + + // as the socket for sending is the same as for receiving, we will just produce an echo here + assertMessageReceived(listener, WAIT_FOR_RECEIVE_MS); + + // manually close here before tearDown() + receiver.close(); + sender.close(); + } + private void assertMessageReceived( SimpleOSCMessageListener listener, int timeout) { assertEventuallyTrue(