INT-3984: UDP: OS Selected Server Port

JIRA: https://jira.spring.io/browse/INT-3894

Allow UDP server port (and sender Ack port) to be selected by the operating system.

Also fix multicast tests to run on all platforms.

Change tests to use OS selected port.
This commit is contained in:
Gary Russell
2015-11-20 11:23:09 -05:00
parent 1cc1b2ef79
commit b4218982da
17 changed files with 396 additions and 200 deletions

View File

@@ -401,6 +401,10 @@ project('spring-integration-ip') {
compile project(":spring-integration-core")
testCompile project(":spring-integration-stream")
}
test {
jvmArgs '-Djava.net.preferIPv4Stack=true' // Multicast on OS X
}
}
project('spring-integration-jdbc') {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,17 +26,17 @@ import org.springframework.messaging.MessagingException;
/**
* Channel adapter that joins a multicast group and receives incoming packets and
* sends them to an output channel.
*
*
* @author Gary Russell
* @since 2.0
*/
public class MulticastReceivingChannelAdapter extends UnicastReceivingChannelAdapter {
private String group;
private final String group;
/**
* Constructs a MulticastReceivingChannelAdapter that listens for packets on the
* Constructs a MulticastReceivingChannelAdapter that listens for packets on the
* specified multichannel address (group) and port.
* @param group The multichannel address.
* @param port The port.
@@ -47,7 +47,7 @@ public class MulticastReceivingChannelAdapter extends UnicastReceivingChannelAda
}
/**
* Constructs a MulticastReceivingChannelAdapter that listens for packets on the
* Constructs a MulticastReceivingChannelAdapter that listens for packets on the
* specified multichannel address (group) and port. Enables setting the lengthCheck
* option, which expects a length to precede the incoming packets.
* @param group The multichannel address.
@@ -63,15 +63,16 @@ public class MulticastReceivingChannelAdapter extends UnicastReceivingChannelAda
protected synchronized DatagramSocket getSocket() {
if (this.getTheSocket() == null) {
try {
MulticastSocket socket = new MulticastSocket(this.getPort());
int port = getPort();
MulticastSocket socket = port == 0 ? new MulticastSocket() : new MulticastSocket(port);
String localAddress = this.getLocalAddress();
if (localAddress != null) {
InetAddress whichNic = InetAddress.getByName(localAddress);
socket.setInterface(whichNic);
}
this.setSocketAttributes(socket);
setSocketAttributes(socket);
socket.joinGroup(InetAddress.getByName(this.group));
this.setSocket(socket);
setSocket(socket);
}
catch (IOException e) {
throw new MessagingException("failed to create DatagramSocket", e);

View File

@@ -111,20 +111,26 @@ public class MulticastSendingMessageHandler extends UnicastSendingMessageHandler
if (this.getTheSocket() == null) {
MulticastSocket socket;
if (this.isAcknowledge()) {
if (logger.isDebugEnabled()) {
logger.debug("Listening for acks on port: " + this.getAckPort());
}
int ackPort = this.getAckPort();
if (localAddress == null) {
socket = new MulticastSocket(this.getAckPort());
} else {
InetAddress whichNic = InetAddress.getByName(this.localAddress);
socket = new MulticastSocket(new InetSocketAddress(whichNic, this.getAckPort()));
socket = ackPort == 0 ? new MulticastSocket() : new MulticastSocket(ackPort);
}
if (this.getSoReceiveBufferSize() > 0) {
else {
InetAddress whichNic = InetAddress.getByName(this.localAddress);
socket = new MulticastSocket(new InetSocketAddress(whichNic, ackPort));
}
if (getSoReceiveBufferSize() > 0) {
socket.setReceiveBufferSize(this.getSoReceiveBufferSize());
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Listening for acks on port: " + socket.getLocalPort());
}
setSocket(socket);
updateAckAddress();
}
else {
socket = new MulticastSocket();
setSocket(socket);
}
if (this.timeToLive >= 0) {
socket.setTimeToLive(this.timeToLive);
@@ -135,7 +141,6 @@ public class MulticastSendingMessageHandler extends UnicastSendingMessageHandler
NetworkInterface intfce = NetworkInterface.getByInetAddress(whichNic);
socket.setNetworkInterface(intfce);
}
this.setSocket(socket);
}
}

View File

@@ -79,6 +79,16 @@ public class UnicastReceivingChannelAdapter extends AbstractInternetProtocolRece
return true;
}
@Override
public int getPort() {
if (this.socket == null) {
return super.getPort();
}
else {
return this.socket.getLocalPort();
}
}
@Override
protected void onInit() {
super.onInit();
@@ -87,11 +97,13 @@ public class UnicastReceivingChannelAdapter extends AbstractInternetProtocolRece
@Override
public void run() {
getSocket();
if (logger.isDebugEnabled()) {
logger.debug("UDP Receiver running on port:" + this.getPort());
}
this.setListening(true);
setListening(true);
// Do as little as possible here so we can loop around and catch the next packet.
// Just schedule the packet for processing.
@@ -122,7 +134,8 @@ public class UnicastReceivingChannelAdapter extends AbstractInternetProtocolRece
String ackAddress = ((String) headers.get(IpHeaders.ACK_ADDRESS)).trim();
Matcher mat = addressPattern.matcher(ackAddress);
if (!mat.matches()) {
throw new MessagingException(message, "Ack requested but could not decode acknowledgment address:" + ackAddress);
throw new MessagingException(message,
"Ack requested but could not decode acknowledgment address: " + ackAddress);
}
String host = mat.group(1);
int port = Integer.parseInt(mat.group(2));
@@ -140,7 +153,7 @@ public class UnicastReceivingChannelAdapter extends AbstractInternetProtocolRece
out.close();
}
catch (IOException e) {
throw new MessagingException(message, "Failed to send acknowledgment", e);
throw new MessagingException(message, "Failed to send acknowledgment to: " + ackAddress, e);
}
}
@@ -210,11 +223,13 @@ public class UnicastReceivingChannelAdapter extends AbstractInternetProtocolRece
try {
DatagramSocket socket = null;
String localAddress = this.getLocalAddress();
int port = super.getPort();
if (localAddress == null) {
socket = new DatagramSocket(this.getPort());
} else {
socket = port == 0 ? new DatagramSocket() : new DatagramSocket(port);
}
else {
InetAddress whichNic = InetAddress.getByName(localAddress);
socket = new DatagramSocket(this.getPort(), whichNic);
socket = new DatagramSocket(new InetSocketAddress(whichNic, port));
}
setSocketAttributes(socket);
this.socket = socket;

View File

@@ -20,6 +20,7 @@ import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.util.Collections;
import java.util.HashMap;
@@ -66,6 +67,8 @@ public class UnicastSendingMessageHandler extends
private volatile boolean acknowledge = false;
private volatile String ackHost;
private volatile int ackPort;
private volatile int ackTimeout = 5000;
@@ -158,6 +161,7 @@ public class UnicastSendingMessageHandler extends
this.waitForAck = acknowledge;
this.mapper.setAcknowledge(acknowledge);
this.mapper.setAckAddress(ackHost + ":" + ackPort);
this.ackHost = ackHost;
this.ackPort = ackPort;
if (ackTimeout > 0) {
this.ackTimeout = ackTimeout;
@@ -185,6 +189,7 @@ public class UnicastSendingMessageHandler extends
});
this.taskExecutor = executor;
}
startAckThread();
}
}
@@ -203,19 +208,7 @@ public class UnicastSendingMessageHandler extends
MessageDeliveryException {
if (this.acknowledge) {
Assert.state(this.isRunning(), "When 'acknowlege' is enabled, adapter must be running");
if (!this.ackThreadRunning) {
synchronized(this) {
if (!this.ackThreadRunning) {
ackLatch = new CountDownLatch(1);
this.taskExecutor.execute(this);
try {
ackLatch.await(10000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
startAckThread();
}
CountDownLatch countdownLatch = null;
String messageId = message.getHeaders().getId().toString();
@@ -227,13 +220,16 @@ public class UnicastSendingMessageHandler extends
}
packet = this.mapper.fromMessage(message);
this.send(packet);
logger.debug("Sent packet for message " + message);
if (logger.isDebugEnabled()) {
logger.debug("Sent packet for message " + message);
}
if (this.waitForAck) {
try {
if (!countdownLatch.await(this.ackTimeout, TimeUnit.MILLISECONDS)) {
throw new MessagingException(message, "Failed to receive UDP Ack in " + ackTimeout + " millis");
}
} catch (InterruptedException e) {
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@@ -256,6 +252,23 @@ public class UnicastSendingMessageHandler extends
}
}
public void startAckThread() {
if (!this.ackThreadRunning) {
synchronized(this) {
if (!this.ackThreadRunning) {
ackLatch = new CountDownLatch(1);
this.taskExecutor.execute(this);
try {
ackLatch.await(10000, TimeUnit.MILLISECONDS);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}
protected void send(DatagramPacket packet) throws Exception {
DatagramSocket socket = this.getSocket();
packet.setSocketAddress(this.getDestinationAddress());
@@ -273,19 +286,22 @@ public class UnicastSendingMessageHandler extends
protected synchronized DatagramSocket getSocket() throws IOException {
if (this.socket == null) {
if (acknowledge) {
if (logger.isDebugEnabled()) {
logger.debug("Listening for acks on port: " + ackPort);
}
if (localAddress == null) {
this.socket = new DatagramSocket(this.ackPort);
} else {
this.socket = this.ackPort == 0 ? new DatagramSocket() : new DatagramSocket(this.ackPort);
}
else {
InetAddress whichNic = InetAddress.getByName(this.localAddress);
this.socket = new DatagramSocket(this.ackPort, whichNic);
this.socket = new DatagramSocket(new InetSocketAddress(whichNic, this.ackPort));
}
if (this.soReceiveBufferSize > 0) {
socket.setReceiveBufferSize(this.soReceiveBufferSize);
}
} else {
if (logger.isDebugEnabled()) {
logger.debug("Listening for acks on port: " + getAckPort());
}
updateAckAddress();
}
else {
this.socket = new DatagramSocket();
}
setSocketAttributes(this.socket);
@@ -293,6 +309,10 @@ public class UnicastSendingMessageHandler extends
return this.socket;
}
protected void updateAckAddress() {
this.mapper.setAckAddress(this.ackHost + ":" + getAckPort());
}
/**
* @see java.net.Socket#setReceiveBufferSize(int)
* @see DatagramSocket#setReceiveBufferSize(int)
@@ -336,7 +356,13 @@ public class UnicastSendingMessageHandler extends
* @return the ackPort
*/
public int getAckPort() {
return ackPort;
DatagramSocket socket = this.socket;
if (this.ackPort == 0 && socket != null) {
return socket.getLocalPort();
}
else {
return this.ackPort;
}
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,8 +17,8 @@
package org.springframework.integration.ip.udp;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.net.DatagramPacket;
@@ -26,10 +26,11 @@ import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import org.junit.Test;
import org.springframework.messaging.Message;
import org.springframework.integration.ip.IpHeaders;
import org.springframework.integration.mapping.MessageMappingException;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.messaging.Message;
/**
* @author Gary Russell
@@ -69,7 +70,7 @@ public class DatagramPacketMessageMapperTests {
Message<byte[]> messageOut = mapper.toMessage(packet);
assertEquals(new String(message.getPayload()), new String(messageOut.getPayload()));
if (ack) {
assertEquals(messageOut.getHeaders().get(IpHeaders.ACK_ID).toString(),
assertEquals(messageOut.getHeaders().get(IpHeaders.ACK_ID).toString(),
message.getHeaders().getId().toString());
}
assertTrue(((String)messageOut.getHeaders().get(IpHeaders.HOSTNAME)).contains("localhost"));
@@ -77,10 +78,10 @@ public class DatagramPacketMessageMapperTests {
messageOut = mapper.toMessage(packet);
assertEquals(new String(message.getPayload()), new String(messageOut.getPayload()));
if (ack) {
assertEquals(messageOut.getHeaders().get(IpHeaders.ACK_ID).toString(),
assertEquals(messageOut.getHeaders().get(IpHeaders.ACK_ID).toString(),
message.getHeaders().getId().toString());
}
assertFalse(((String)messageOut.getHeaders().get(IpHeaders.HOSTNAME)).contains("localhost"));
assertFalse(((String)messageOut.getHeaders().get(IpHeaders.HOSTNAME)).contains("localhost"));
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,20 +25,18 @@ import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.LogFactory;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.ip.IpHeaders;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.SocketUtils;
import org.springframework.messaging.Message;
/**
@@ -52,17 +50,20 @@ public class DatagramPacketSendingHandlerTests {
@Test
public void verifySend() throws Exception {
final int testPort = SocketUtils.findAvailableUdpSocket();
byte[] buffer = new byte[8];
final DatagramPacket receivedPacket = new DatagramPacket(buffer, buffer.length);
final CountDownLatch latch = new CountDownLatch(1);
final CountDownLatch received = new CountDownLatch(1);
final AtomicInteger testPort = new AtomicInteger();
final CountDownLatch listening = new CountDownLatch(1);
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
DatagramSocket socket = new DatagramSocket(testPort);
DatagramSocket socket = new DatagramSocket();
testPort.set(socket.getLocalPort());
listening.countDown();
socket.receive(receivedPacket);
latch.countDown();
received.countDown();
socket.close();
}
catch (Exception e) {
@@ -70,12 +71,12 @@ public class DatagramPacketSendingHandlerTests {
}
}
});
Thread.sleep(1000);
assertTrue(listening.await(10, TimeUnit.SECONDS));
UnicastSendingMessageHandler handler =
new UnicastSendingMessageHandler("localhost", testPort);
new UnicastSendingMessageHandler("localhost", testPort.get());
String payload = "foo";
handler.handleMessage(MessageBuilder.withPayload(payload).build());
assertTrue(latch.await(3000, TimeUnit.MILLISECONDS));
assertTrue(received.await(3000, TimeUnit.MILLISECONDS));
byte[] src = receivedPacket.getData();
int length = receivedPacket.getLength();
int offset = receivedPacket.getOffset();
@@ -88,27 +89,22 @@ public class DatagramPacketSendingHandlerTests {
@Test
public void verifySendWithAck() throws Exception {
final List<Integer> openPorts = SocketUtils.findAvailableUdpSockets(SocketUtils.getRandomSeedPort(), 2);
final int testPort = openPorts.get(0);
final int ackPort = openPorts.get(1);
final AtomicInteger testPort = new AtomicInteger();
final AtomicInteger ackPort = new AtomicInteger();
byte[] buffer = new byte[1000];
final DatagramPacket receivedPacket = new DatagramPacket(buffer, buffer.length);
final CountDownLatch latch1 = new CountDownLatch(1);
final CountDownLatch latch2 = new CountDownLatch(1);
UnicastSendingMessageHandler handler =
new UnicastSendingMessageHandler("localhost", testPort, true,
true, "localhost", ackPort, 5000);
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
handler.start();
final CountDownLatch listening = new CountDownLatch(1);
final CountDownLatch ackListening = new CountDownLatch(1);
final CountDownLatch ackSent = new CountDownLatch(1);
Executors.newSingleThreadExecutor().execute(new Runnable() {
@Override
public void run() {
try {
DatagramSocket socket = new DatagramSocket(testPort);
latch1.countDown();
DatagramSocket socket = new DatagramSocket();
testPort.set(socket.getLocalPort());
listening.countDown();
assertTrue(ackListening.await(10, TimeUnit.SECONDS));
socket.receive(receivedPacket);
socket.close();
DatagramPacketMessageMapper mapper = new DatagramPacketMessageMapper();
@@ -118,21 +114,29 @@ public class DatagramPacketSendingHandlerTests {
Object id = message.getHeaders().get(IpHeaders.ACK_ID);
byte[] ack = id.toString().getBytes();
DatagramPacket ackPack = new DatagramPacket(ack, ack.length,
new InetSocketAddress("localHost", ackPort));
new InetSocketAddress("localHost", ackPort.get()));
DatagramSocket out = new DatagramSocket();
out.send(ackPack);
out.close();
latch2.countDown();
ackSent.countDown();
}
catch (Exception e) {
e.printStackTrace();
}
}
});
latch1.await(3000, TimeUnit.MILLISECONDS);
listening.await(10000, TimeUnit.MILLISECONDS);
UnicastSendingMessageHandler handler =
new UnicastSendingMessageHandler("localhost", testPort.get(), true, true, "localhost", 0, 5000);
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
handler.start();
waitAckListening(handler);
ackPort.set(handler.getAckPort());
ackListening.countDown();
String payload = "foobar";
handler.handleMessage(MessageBuilder.withPayload(payload).build());
assertTrue(latch2.await(10000, TimeUnit.MILLISECONDS));
assertTrue(ackSent.await(10000, TimeUnit.MILLISECONDS));
byte[] src = receivedPacket.getData();
int length = receivedPacket.getLength();
int offset = receivedPacket.getOffset();
@@ -142,14 +146,28 @@ public class DatagramPacketSendingHandlerTests {
handler.stop();
}
public void waitAckListening(UnicastSendingMessageHandler handler) throws InterruptedException {
int n = 0;
while (n++ < 100 && handler.getAckPort() == 0) {
Thread.sleep(100);
}
assertTrue(n < 100);
}
@Test
@Ignore
public void verifySendMulticast() throws Exception {
final int testPort = SocketUtils.findAvailableUdpSocket();
MulticastSocket socket;
try {
socket = new MulticastSocket();
}
catch (Exception e) {
return;
}
final int testPort = socket.getLocalPort();
final String multicastAddress = "225.6.7.8";
final String payload = "foo";
final CountDownLatch latch1 = new CountDownLatch(2);
final CountDownLatch latch2 = new CountDownLatch(2);
final CountDownLatch listening = new CountDownLatch(2);
final CountDownLatch received = new CountDownLatch(2);
Runnable catcher = new Runnable() {
@Override
public void run() {
@@ -159,7 +177,7 @@ public class DatagramPacketSendingHandlerTests {
MulticastSocket socket = new MulticastSocket(testPort);
InetAddress group = InetAddress.getByName(multicastAddress);
socket.joinGroup(group);
latch1.countDown();
listening.countDown();
LogFactory.getLog(getClass())
.debug(Thread.currentThread().getName() + " waiting for packet");
socket.receive(receivedPacket);
@@ -172,11 +190,11 @@ public class DatagramPacketSendingHandlerTests {
assertEquals(payload, new String(dest));
LogFactory.getLog(getClass())
.debug(Thread.currentThread().getName() + " received packet");
latch2.countDown();
received.countDown();
}
catch (Exception e) {
noMulticast = true;
latch1.countDown();
listening.countDown();
e.printStackTrace();
}
}
@@ -184,29 +202,36 @@ public class DatagramPacketSendingHandlerTests {
Executor executor = Executors.newFixedThreadPool(2);
executor.execute(catcher);
executor.execute(catcher);
latch1.await(3000, TimeUnit.MILLISECONDS);
listening.await(10000, TimeUnit.MILLISECONDS);
if (noMulticast) {
socket.close();
return;
}
MulticastSendingMessageHandler handler = new MulticastSendingMessageHandler(multicastAddress, testPort);
handler.handleMessage(MessageBuilder.withPayload(payload).build());
assertTrue(latch2.await(3000, TimeUnit.MILLISECONDS));
assertTrue(received.await(10000, TimeUnit.MILLISECONDS));
handler.stop();
socket.close();
}
@Test
@Ignore
public void verifySendMulticastWithAcks() throws Exception {
final List<Integer> openPorts = SocketUtils.findAvailableUdpSockets(SocketUtils.getRandomSeedPort(), 2);
final int testPort = openPorts.get(0);
final int ackPort = openPorts.get(1);
MulticastSocket socket;
try {
socket = new MulticastSocket();
}
catch (Exception e) {
return;
}
final int testPort = socket.getLocalPort();
final AtomicInteger ackPort = new AtomicInteger();
final String multicastAddress = "225.6.7.8";
final String payload = "foobar";
final CountDownLatch latch1 = new CountDownLatch(2);
final CountDownLatch latch2 = new CountDownLatch(2);
final CountDownLatch listening = new CountDownLatch(2);
final CountDownLatch ackListening = new CountDownLatch(1);
final CountDownLatch ackSent = new CountDownLatch(2);
Runnable catcher = new Runnable() {
@Override
public void run() {
@@ -214,9 +239,11 @@ public class DatagramPacketSendingHandlerTests {
byte[] buffer = new byte[1000];
DatagramPacket receivedPacket = new DatagramPacket(buffer, buffer.length);
MulticastSocket socket = new MulticastSocket(testPort);
socket.setSoTimeout(8000);
InetAddress group = InetAddress.getByName(multicastAddress);
socket.joinGroup(group);
latch1.countDown();
listening.countDown();
assertTrue(ackListening.await(10, TimeUnit.SECONDS));
LogFactory.getLog(getClass()).debug(Thread.currentThread().getName() + " waiting for packet");
socket.receive(receivedPacket);
socket.close();
@@ -234,15 +261,16 @@ public class DatagramPacketSendingHandlerTests {
Object id = message.getHeaders().get(IpHeaders.ACK_ID);
byte[] ack = id.toString().getBytes();
DatagramPacket ackPack = new DatagramPacket(ack, ack.length,
new InetSocketAddress("localHost", ackPort));
new InetSocketAddress("localHost", ackPort.get()));
DatagramSocket out = new DatagramSocket();
out.send(ackPack);
out.close();
latch2.countDown();
ackSent.countDown();
socket.close();
}
catch (Exception e) {
noMulticast = true;
latch1.countDown();
listening.countDown();
e.printStackTrace();
}
}
@@ -250,19 +278,23 @@ public class DatagramPacketSendingHandlerTests {
Executor executor = Executors.newFixedThreadPool(2);
executor.execute(catcher);
executor.execute(catcher);
latch1.await(3000, TimeUnit.MILLISECONDS);
if (noMulticast) {
listening.await(10000, TimeUnit.MILLISECONDS);
if (this.noMulticast) {
socket.close();
return;
}
MulticastSendingMessageHandler handler =
new MulticastSendingMessageHandler(multicastAddress, testPort, true,
true, "localhost", ackPort, 500000);
new MulticastSendingMessageHandler(multicastAddress, testPort, true, true, "localhost", 0, 10000);
handler.setMinAcksForSuccess(2);
handler.afterPropertiesSet();
handler.start();
waitAckListening(handler);
ackPort.set(handler.getAckPort());
ackListening.countDown();
handler.handleMessage(MessageBuilder.withPayload(payload).build());
assertTrue(latch2.await(10000, TimeUnit.MILLISECONDS));
assertTrue(ackSent.await(10000, TimeUnit.MILLISECONDS));
handler.stop();
socket.close();
}
}

View File

@@ -26,7 +26,9 @@ import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
@@ -36,7 +38,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.LogFactory;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
@@ -61,22 +62,26 @@ public class UdpChannelAdapterTests {
@Test
public void testUnicastReceiver() throws Exception {
testUnicastReceiver(false);
testUnicastReceiver(false, false);
}
@Test
public void testUnicastReceiverLocalNicOnly() throws Exception {
testUnicastReceiver(false, true);
}
@Test
public void testUnicastReceiverDeadExecutor() throws Exception {
testUnicastReceiver(true);
testUnicastReceiver(true, false);
}
private void testUnicastReceiver(final boolean killExecutor) throws Exception {
private void testUnicastReceiver(final boolean killExecutor, boolean useLocalAddress) throws Exception {
QueueChannel channel = new QueueChannel(2);
int port = SocketUtils.findAvailableUdpSocket();
final CountDownLatch stopLatch = new CountDownLatch(1);
final CountDownLatch exitLatch = new CountDownLatch(1);
final AtomicBoolean stopping = new AtomicBoolean();
final AtomicReference<Exception> exceptionHolder = new AtomicReference<Exception>();
UnicastReceivingChannelAdapter adapter = new UnicastReceivingChannelAdapter(port) {
UnicastReceivingChannelAdapter adapter = new UnicastReceivingChannelAdapter(0) {
@Override
public boolean isActive() {
@@ -130,9 +135,12 @@ public class UdpChannelAdapterTests {
};
adapter.setOutputChannel(channel);
// SocketUtils.setLocalNicIfPossible(adapter);
if (useLocalAddress) {
adapter.setLocalAddress("127.0.0.1");
}
adapter.start();
SocketTestUtils.waitListening(adapter);
int port = adapter.getPort();
Message<byte[]> message = MessageBuilder.withPayload("ABCD".getBytes()).build();
DatagramPacketMessageMapper mapper = new DatagramPacketMessageMapper();
@@ -157,11 +165,11 @@ public class UdpChannelAdapterTests {
@Test
public void testUnicastReceiverWithReply() throws Exception {
QueueChannel channel = new QueueChannel(2);
int port = SocketUtils.findAvailableUdpSocket();
UnicastReceivingChannelAdapter adapter = new UnicastReceivingChannelAdapter(port);
UnicastReceivingChannelAdapter adapter = new UnicastReceivingChannelAdapter(0);
adapter.setOutputChannel(channel);
adapter.start();
SocketTestUtils.waitListening(adapter);
int port = adapter.getPort();
Message<byte[]> message = MessageBuilder.withPayload("ABCD".getBytes()).build();
DatagramPacketMessageMapper mapper = new DatagramPacketMessageMapper();
@@ -211,13 +219,13 @@ public class UdpChannelAdapterTests {
@Test
public void testUnicastSender() throws Exception {
QueueChannel channel = new QueueChannel(2);
int port = SocketUtils.findAvailableUdpSocket();
UnicastReceivingChannelAdapter adapter = new UnicastReceivingChannelAdapter(port);
UnicastReceivingChannelAdapter adapter = new UnicastReceivingChannelAdapter(0);
adapter.setBeanName("test");
adapter.setOutputChannel(channel);
// SocketUtils.setLocalNicIfPossible(adapter);
adapter.start();
SocketTestUtils.waitListening(adapter);
int port = adapter.getPort();
// String whichNic = SocketUtils.chooseANic(false);
UnicastSendingMessageHandler handler = new UnicastSendingMessageHandler(
@@ -238,20 +246,20 @@ public class UdpChannelAdapterTests {
}
@SuppressWarnings("unchecked")
@Test @Ignore
@Test
public void testMulticastReceiver() throws Exception {
System.setProperty("java.net.preferIPv4Stack", "true");
QueueChannel channel = new QueueChannel(2);
int port = SocketUtils.findAvailableUdpSocket();
MulticastReceivingChannelAdapter adapter = new MulticastReceivingChannelAdapter("225.6.7.8", port);
MulticastReceivingChannelAdapter adapter = new MulticastReceivingChannelAdapter("225.6.7.8", 0);
adapter.setOutputChannel(channel);
String nic = SocketTestUtils.chooseANic(true);
if (nic == null) { // no multicast support
LogFactory.getLog(this.getClass()).error("No Multicast support");
String nic = checkMulticast();
if (nic == null) {
return;
}
adapter.setLocalAddress(nic);
adapter.start();
SocketTestUtils.waitListening(adapter);
int port = adapter.getPort();
Message<byte[]> message = MessageBuilder.withPayload("ABCD".getBytes()).build();
DatagramPacketMessageMapper mapper = new DatagramPacketMessageMapper();
@@ -267,16 +275,33 @@ public class UdpChannelAdapterTests {
adapter.stop();
}
private String checkMulticast() throws Exception {
String nic = SocketTestUtils.chooseANic(true);
if (nic == null) { // no multicast support
LogFactory.getLog(this.getClass()).info("No Multicast support; test skipped");
return null;
}
try {
MulticastSocket socket = new MulticastSocket();
socket.joinGroup(InetAddress.getByName("225.6.7.9"));
socket.close();
}
catch (Exception e) {
LogFactory.getLog(this.getClass()).info("No Multicast support; test skipped");
}
return nic;
}
@SuppressWarnings("unchecked")
@Test @Ignore
@Test
public void testMulticastSender() throws Exception {
System.setProperty("java.net.preferIPv4Stack", "true");
QueueChannel channel = new QueueChannel(2);
int port = SocketUtils.findAvailableUdpSocket();
UnicastReceivingChannelAdapter adapter = new MulticastReceivingChannelAdapter("225.6.7.9", port);
adapter.setOutputChannel(channel);
String nic = SocketTestUtils.chooseANic(true);
if (nic == null) { // no multicast support
LogFactory.getLog(this.getClass()).error("No Multicast support");
String nic = checkMulticast();
if (nic == null) {
return;
}
adapter.setLocalAddress(nic);
@@ -297,8 +322,7 @@ public class UdpChannelAdapterTests {
@Test
public void testUnicastReceiverException() throws Exception {
SubscribableChannel channel = new DirectChannel();
int port = SocketUtils.findAvailableUdpSocket();
UnicastReceivingChannelAdapter adapter = new UnicastReceivingChannelAdapter(port);
UnicastReceivingChannelAdapter adapter = new UnicastReceivingChannelAdapter(0);
adapter.setOutputChannel(channel);
// SocketUtils.setLocalNicIfPossible(adapter);
adapter.setOutputChannel(channel);
@@ -308,6 +332,7 @@ public class UdpChannelAdapterTests {
adapter.setErrorChannel(errorChannel);
adapter.start();
SocketTestUtils.waitListening(adapter);
int port = adapter.getPort();
Message<byte[]> message = MessageBuilder.withPayload("ABCD".getBytes()).build();
DatagramPacketMessageMapper mapper = new DatagramPacketMessageMapper();

View File

@@ -21,15 +21,17 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Date;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
import org.springframework.messaging.Message;
@@ -66,22 +68,44 @@ public class UdpMulticastEndToEndTests implements Runnable {
private final CountDownLatch readyToReceive = new CountDownLatch(1);
private volatile int receiverPort;
private static long hangAroundFor = 0;
@Test
@Ignore
public void runIt() throws Exception {
String location = "org/springframework/integration/ip/udp/testIp-out-multicast-context.xml";
test(location);
}
private void test(String location) throws InterruptedException, Exception {
UdpMulticastEndToEndTests launcher = new UdpMulticastEndToEndTests();
Thread t = new Thread(launcher);
t.start(); // launch the receiver
AbstractApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"testIp-out-multicast-context.xml",
UdpMulticastEndToEndTests.class);
int n = 0;
while (n++ < 100 && launcher.getReceiverPort() == 0) {
Thread.sleep(100);
}
assertTrue("Receiver failed to listen", n < 100);
ClassPathXmlApplicationContext applicationContext = createContext(launcher, location);
launcher.launchSender(applicationContext);
applicationContext.stop();
applicationContext.close();
}
private ClassPathXmlApplicationContext createContext(UdpMulticastEndToEndTests launcher, String location) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
applicationContext.setConfigLocation(location);
StandardEnvironment env = new StandardEnvironment();
Properties props = new Properties();
props.setProperty("port", Integer.toString(launcher.getReceiverPort()));
PropertiesPropertySource pps = new PropertiesPropertySource("ftpprops", props);
env.getPropertySources().addLast(pps);
applicationContext.setEnvironment(env);
applicationContext.refresh();
return applicationContext;
}
public void launchSender(ApplicationContext applicationContext) throws Exception {
DestinationResolver<MessageChannel> channelResolver = new BeanFactoryChannelResolver(applicationContext);
@@ -118,6 +142,9 @@ public class UdpMulticastEndToEndTests implements Runnable {
assertEquals(testingIpText, new String(finalMessage.getPayload()));
}
public int getReceiverPort() {
return receiverPort;
}
/**
* Instantiate the receiving context
@@ -128,6 +155,24 @@ public class UdpMulticastEndToEndTests implements Runnable {
AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(
"testIp-in-multicast-context.xml",
UdpMulticastEndToEndTests.class);
MulticastReceivingChannelAdapter inbound = ctx.getBean(MulticastReceivingChannelAdapter.class);
int n = 0;
try {
while (!inbound.isListening()) {
Thread.sleep(100);
if (n++ > 100) {
throw new RuntimeException("Failed to start listening");
}
}
}
catch (RuntimeException e) {
throw e;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("interrupted");
}
this.receiverPort = inbound.getPort();
while (okToRun) {
try {
readyToReceive.countDown();

View File

@@ -32,6 +32,8 @@ import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.support.channel.BeanFactoryChannelResolver;
@@ -71,6 +73,8 @@ public class UdpUnicastEndToEndTests implements Runnable {
private CountDownLatch readyToReceive = new CountDownLatch(1);
private volatile int receiverPort;
private static long hangAroundFor = 10;
@Before
@@ -86,24 +90,42 @@ public class UdpUnicastEndToEndTests implements Runnable {
@Test
public void runIt() throws Exception {
UdpUnicastEndToEndTests launcher = new UdpUnicastEndToEndTests();
Thread t = new Thread(launcher);
t.start(); // launch the receiver
AbstractApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"testIp-out-context.xml", UdpUnicastEndToEndTests.class);
launcher.launchSender(applicationContext);
applicationContext.stop();
String location = "org/springframework/integration/ip/udp/testIp-out-context.xml";
test(location);
}
@Test
public void tesUdpOutboundChannelAdapterWithinChain() throws Exception {
String location = "org/springframework/integration/ip/udp/testIp-out-within-chain-context.xml";
test(location);
}
private void test(String location) throws InterruptedException, Exception {
UdpUnicastEndToEndTests launcher = new UdpUnicastEndToEndTests();
Thread t = new Thread(launcher);
t.start(); // launch the receiver
AbstractApplicationContext applicationContext = new ClassPathXmlApplicationContext(
"testIp-out-within-chain-context.xml", UdpUnicastEndToEndTests.class);
int n = 0;
while (n++ < 100 && launcher.getReceiverPort() == 0) {
Thread.sleep(100);
}
assertTrue("Receiver failed to listen", n < 100);
ClassPathXmlApplicationContext applicationContext = createContext(launcher, location);
launcher.launchSender(applicationContext);
applicationContext.stop();
applicationContext.close();
}
private ClassPathXmlApplicationContext createContext(UdpUnicastEndToEndTests launcher, String location) {
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext();
applicationContext.setConfigLocation(location);
StandardEnvironment env = new StandardEnvironment();
Properties props = new Properties();
props.setProperty("port", Integer.toString(launcher.getReceiverPort()));
PropertiesPropertySource pps = new PropertiesPropertySource("ftpprops", props);
env.getPropertySources().addLast(pps);
applicationContext.setEnvironment(env);
applicationContext.refresh();
return applicationContext;
}
@@ -138,6 +160,9 @@ public class UdpUnicastEndToEndTests implements Runnable {
assertEquals(testingIpText, new String(finalMessage.getPayload()));
}
public int getReceiverPort() {
return receiverPort;
}
/**
* Instantiate the receiving context
@@ -158,7 +183,14 @@ public class UdpUnicastEndToEndTests implements Runnable {
}
}
}
catch (Exception e) { }
catch (RuntimeException e) {
throw e;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("interrupted");
}
this.receiverPort = inbound.getPort();
while (okToRun) {
try {
readyToReceive.countDown();

View File

@@ -12,19 +12,19 @@
http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/integration/ip
http://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd">
<message-history/>
<!--
Play with the buffer size to force errors. If the checkLength property is
<!--
Play with the buffer size to force errors. If the checkLength property is
set and this buffer is too small, we'll throw an exception.
-->
<ip:udp-inbound-channel-adapter id="udpReceiver"
channel="udpOutChannel"
port="11111"
port="0"
receive-buffer-size="500"
multicast="false"
check-length="true" />
<beans:import resource="testIp-common-context.xml" />
</beans:beans>

View File

@@ -13,18 +13,18 @@
http://www.springframework.org/schema/integration/ip
http://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd">
<!--
Play with the buffer size to force errors. If the checkLength property is
<!--
Play with the buffer size to force errors. If the checkLength property is
set and this buffer is too small, we'll throw an exception.
-->
<ip:udp-inbound-channel-adapter id="mcUdpReceiver"
channel="udpOutChannel"
port="11112"
port="0"
receive-buffer-size="500"
multicast="true"
multicast-address="225.6.7.8"
check-length="true" />
<beans:import resource="testIp-common-context.xml" />
</beans:beans>

View File

@@ -4,14 +4,14 @@
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:stream="http://www.springframework.org/schema/integration/stream"
xmlns:ip="http://www.springframework.org/schema/integration/ip"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/stream
http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/integration/ip
http://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd">
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<context:property-placeholder />
<stream:stdin-channel-adapter id="stdin" channel="outputChannel" >
<poller fixed-delay="100"/>
@@ -26,13 +26,13 @@
ref="testIp"
method="testIp"/>
<ip:udp-outbound-channel-adapter id="udpSender"
<ip:udp-outbound-channel-adapter id="udpSender"
host="localhost"
port="11111"
port="${port}"
check-length="true"
acknowledge="true"
ack-host="localhost"
ack-port="22222"
ack-port="0"
ack-timeout="10000"
channel="outputChannel"/>

View File

@@ -4,14 +4,14 @@
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:stream="http://www.springframework.org/schema/integration/stream"
xmlns:ip="http://www.springframework.org/schema/integration/ip"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/stream
http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/integration/ip
http://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd">
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<context:property-placeholder />
<stream:stdin-channel-adapter id="stdin" channel="mcOutputChannel" >
<poller fixed-delay="100" />
@@ -26,18 +26,18 @@
ref="testIp"
method="testIp"/>
<ip:udp-outbound-channel-adapter id="mcUdpSender"
<ip:udp-outbound-channel-adapter id="mcUdpSender"
multicast="true"
time-to-live="2"
host="225.6.7.8"
port="11112"
port="${port}"
check-length="true"
acknowledge="true"
ack-host="localhost"
ack-port="22223"
ack-port="0"
ack-timeout="10000"
channel="mcOutputChannel"/>
<beans:import resource="testIp-common-context.xml" />
</beans:beans>

View File

@@ -4,14 +4,14 @@
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:stream="http://www.springframework.org/schema/integration/stream"
xmlns:ip="http://www.springframework.org/schema/integration/ip"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/stream
http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/integration/ip
http://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd">
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/integration/stream http://www.springframework.org/schema/integration/stream/spring-integration-stream.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd">
<context:property-placeholder />
<stream:stdin-channel-adapter id="stdin" channel="outputChannel" >
<poller fixed-delay="100"/>
@@ -28,11 +28,11 @@
<chain input-channel="outputChannel">
<ip:udp-outbound-channel-adapter host="localhost"
port="11111"
port="${port}"
check-length="true"
acknowledge="true"
ack-host="localhost"
ack-port="22223"
ack-port="0"
ack-timeout="10000"/>
</chain>

View File

@@ -19,10 +19,13 @@ package org.springframework.integration.ip.util;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Enumeration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -431,17 +434,19 @@ public class SocketTestUtils {
}
public static String chooseANic(boolean multicast) throws Exception {
// Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
// while (interfaces.hasMoreElements()) {
// NetworkInterface intface = interfaces.nextElement();
// if (intface.isLoopback() || (multicast && !intface.supportsMulticast()))
// continue;
// Enumeration<InetAddress> inet = intface.getInetAddresses();
// if (!inet.hasMoreElements())
// continue;
// String address = inet.nextElement().getHostAddress();
// return address;
// }
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface intface = interfaces.nextElement();
if (intface.isLoopback() || (multicast && !intface.supportsMulticast())) {
continue;
}
for (Enumeration<InetAddress> inetAddr = intface.getInetAddresses(); inetAddr.hasMoreElements(); ) {
InetAddress nextElement = inetAddr.nextElement();
if (nextElement instanceof Inet4Address) {
return nextElement.getHostAddress();
}
}
}
return null;
}

View File

@@ -53,6 +53,9 @@ This enables the receiving side to verify the length of the packet received.
If a receiving system uses a buffer that is too short the contain the packet, the packet can be truncated.
The length header provides a mechanism to detect this.
Starting with _version 4.3_, the `port` can be set to `0`, in which case the Operating System chooses the port; the
chosen port can be discovered by invoking `getPort()` after the adapter is started and `isListening()` returns `true`.
[source,xml]
----
<int-ip:udp-outbound-channel-adapter id="udpOut"
@@ -94,6 +97,8 @@ TIP: When multicast is true, an additional attribute min-acks-for-success specif
For even more reliable networking, TCP can be used.
Starting with _version 4.3_, the `ackPort` can be set to `0`, in which case the Operating System chooses the port.
[source,xml]
----
<int-ip:udp-inbound-channel-adapter id="udpReceiver"