2

I have a need to open N multicast sockets (where N comes from the size of an argument list). I will then send the same data to each of the N sockets within a loop, and finally, close each socket. My question is, how do I do this using the try-with-resources block? The following is how I would do this with a single resource:

final int port = ...;
try (final MulticastSocket socket = new MulticastSocket(port)) {
    // Do a bunch of sends of small packet data over a long period of time
    ...
}

The only way I can think of to do this with multiple ports is the following:

final List<Integer> ports = ...;
final List<MulticastSocket> sockets = new ArrayList<>(ports.size());
try {
    for (final Integer port : ports) {
        sockets.add(new MulticastSocket(port));
    }

    // Do a bunch of sends of small packet data over a long period of time
    ...
} finally {
    for (final MulticastSocket socket : sockets) {
        try {
            socket.close();
        } catch (final Throwable t) {
            // Eat the exception
        }
    }
}

Is there a more concise way to accomplish this, or is my proposed solution as good as it gets?

4 Answers 4

2

Do it recursively to keep the guarantees of try-with-resources:

void foo(List<Integer> ports, List<Socket> sockets) {
  if (sockets.size() == ports.size()) {
    // Do something with your sockets.
  } else {
    try (Socket s = new MulticastSocket(ports.get(sockets.size())) {
      sockets.add(s);
      foo(ports, sockets);
      // You could call sockets.remove(sockets.size()-1) here.
      // Not convinced whether it's worth it.
    }
  }
}
Sign up to request clarification or add additional context in comments.

3 Comments

so you can't loop but you can create a recursive invocation? this is really nice
I had to read this twice to understand what you were doing. +1 for creativity and brevity, but I think I still prefer Mike's suggestion of creating a utility AutoCloseable class instead. It seems to be more readable, and abusing recursion for this just feels... dirty.
@JeffG you should do what you feel comfortable with, because ultimately you have to maintain it; but I think you'll find it quite messy and difficult to implement exactly equivalent semantics of multiple TWR blocks without something like this.
1

What you are doing is practically as good as it gets.

You could create an AutoCloseable general-purpose multi-closer which contains a List<AutoCloseable> and accepts as a constructor parameter a count of closeables and a factory to invoke to create each closeable, and then close them all when its close() is invoked, so that you can use it like this:

try( MultiCloser<MulticastSocket> multiCloser = 
         new MultiCloser<>( ports.size(), i -> new MulticastSocket( ports.get( i ) ) )
{
    for( MulticastSocket socket : multiCloser.getItems() )
    {
        do something with the socket
    }
}

...but it would probably be an overkill.

2 Comments

What is port in the lambda expression?
@saka1029 argh, there, fixed it. But that's not the point. The point is the construct. The details are left as an exercise to the student.
0

What is the point to use an ArrayList to store the MulticastSocket instances ?

You said that :

I will then send the same data to each of the N sockets within a loop, and finally, close each socket.

So you can create them in a loop and send for each iteration the same processing.
To do it, you should a little change your design.
The processing task of the MulticastSocket could be performed by a functional interface that allows also to specify the port to use.

For example :

@FunctionalInterface
public interface SocketProcessor {
    void process(MulticastSocket multicastSocket) ;
}

You could have a method that takes as parameter this functional interface to apply the processing :

public static void processSocket(SocketProcessor socketProcessor, Integer port) throws IOException {
  try (final MulticastSocket socket = new MulticastSocket(port)) {
    socketProcessor.process(socket);
  }
}

At last from the client code, you could create a socketProcessor instance with a lambda :

SocketProcessor socketProcessor = (MulticastSocket socket) -> {
    socket.send(...);
    socket.send(...);
};

And then you could loop on the ports in order to invoke processSocket with the suitable port and the SocketProcessor instance just created :

for (final Integer port : ports) {
    try {
      processSocket(socketProcessor, port);
    } catch (IOException e) {
      // do processing
    }
}

This solution is not necessary shorter (without being really longer) but it is really clearer.
The two main concerns are separated :

  • processSocket(SocketProcessor) that performs the boiler plate code

  • SocketProcessor that defines the concrete task.

2 Comments

Your answer made me realize that I forgot to specify that the packets need to be sent interleaved (e.g., send packet 1 on all sockets, then send packet 2 on all sockets, ...). Given that is one of my requirements (which is admittedly absent in my question), this answer would require creating a new socket for each packet that needs to be sent.
@Jeff G In this case, indeed the proposed answer doesn't suit to your need.
0

Inspired by the idea proposed by Mike Nakis, I came up with the following class...

package myNamespace;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import myNamespace.ThrowingFunction;
import myNamespace.ThrowingSupplier;

/** Collection of AutoCloseable objects */
public class ResourceCollection<T extends AutoCloseable>
        implements Iterable<T>, AutoCloseable {

    /** Resources owned by this instance */
    private final List<T> myResources;

    /**
     * Constructor
     * @param allocator Function used to allocate each resource
     * @param count     Number of times to call the allocator
     * @throws E Thrown if any of the allocators throw
     */
    public <E extends Throwable> ResourceCollection(
            final ThrowingSupplier<T, E> allocator, final int count)
            throws E {
        myResources = new ArrayList<>(count);
        try {
            while (myResources.size() < count) {
                final T resource = allocator.getThrows();
                myResources.add(resource);
            }
        } catch (final Throwable e) {
            close();
            throw e;
        }
    }

    /**
     * Constructor
     * @param allocator Function used to allocate each resource
     * @param input     List of input parameters passed to the allocator
     * @throws E Thrown if any of the allocators throw
     */
    public <U, E extends Throwable> ResourceCollection(
            final ThrowingFunction<U, T, E> allocator, final Collection<U> input)
            throws E {
        myResources = new ArrayList<>(input.size());
        try {
            for (final U value : input) {
                final T resource = allocator.applyThrows(value);
                myResources.add(resource);
            }
        } catch (final Throwable e) {
            close();
            throw e;
        }
    }

    /**
     * Gets the number of resources in the collection
     * @return The number of resources in the collection
     */
    public int size() {
        return myResources.size();
    }

    /**
     * Gets whether the collection contains no resources
     * @return Whether the collection contains no resources
     */
    public boolean isEmpty() {
        return myResources.isEmpty();
    }

    /**
     * Gets the resource at index i
     * @param i The index of a resource, in the range [0, size())
     * @return The resource at index i
     */
    public T get(final int i) {
        return myResources.get(i);
    }

    @Override
    public Iterator<T> iterator() {
        return myResources.iterator();
    }

    @Override
    public void close() {
        final ListIterator<T> resourceIter =
                myResources.listIterator(myResources.size());
        while (resourceIter.hasPrevious()) {
            final T resource = resourceIter.previous();
            if (resource != null) {
                try {
                    resource    .close ();
                    resourceIter.remove();
                } catch (final Throwable t) {
                    // Eat the exception
                }
            }
        }
    }

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.