Java获取MulticastSocket.receive以抛出ClosedByInterruptException

时间:2017-03-16 01:22:13

标签: java sockets multicast multicastsocket

我有一些代码从组播套接字读取数据,直到用户定义的结束时间。如果通过调用Thread.interrupt(或者您可以提出的任何其他用户启动的操作)中断线程,我还想停止读取数据。我无法弄清楚如何在线程中断时获取通知。现有代码如下:

// These are the constants I am given
final int         mcastPort = ...;
final InetAddress mcastIP   = ...;
final InetAddress ifaceIP   = ...; // Null indicates all interfaces should be used
final Instant     endTime   = ...; // Time at which to stop processing

// Initialize a datagram
final byte[] buffer = new byte[1000];
final DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

// Process incoming datagram packets
try (final MulticastSocket socket = new MulticastSocket(port)) {
    socket.joinGroup(mcastIP);
    if (ifaceIP != null)
        socket.setInterface(ifaceIP);

    do {
        final Duration soTimeout = Duration.between(Instant.now(), endTime);
        socket.setSoTimeout(soTimeout);
        socket.receive(packet);

        // Process packet
        ...
    } while (true);
} catch (final SocketTimeoutException e) {
    // Normal condition... the recording time has ended
} catch (final ClosedByInterruptException e) {
    // Uh-oh... this never happens
} ...

我看到有一个DatagramSocket.getChannel方法返回DatagramChannel,所以我自然地认为该类型用于读取/写入底层套接字。该假设不正确,这意味着MulticastSocket未实现InterruptibleChannel。因此,MulticastSocket.receive永远不会抛出ClosedByInterruptException

我在网上搜索了一些示例,但无法弄清楚如何修改上述代码以使用DatagramChannel代替MulticastSocket。我需要帮助的问题是:

  1. 如何在DatagramChannel上设置SO_TIMEOUT参数?
  2. 如何将InetAddress转换为NetworkInterface对象?
  3. 以下是关于如何将我的实施从MulticastSocket转换为DatagramChannel以满足我的要求的最佳猜测:

    // Initialize a buffer
    final ByteBuffer buffer = ByteBuffer.allocate(1000);
    
    try (final DatagramChannel mcastChannel = DatagramChannel.open()) {
        mcastChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
        mcastChannel.connect(new InetSocketAddress(port));
        mcastChannel.join(mcastIP);
        if (ifaceIP != null)
            // HELP: this option requires an InterfaceAddress object,
            //       but I only have access to an InetAddress object
            mcastChannel.setOption(StandardSocketOptions.IP_MULTICAST_IF, ifaceIP);
    
        do {
            final Duration soTimeout = Duration.between(Instant.now(), endTime);
            // HELP: SO_TIMEOUT is not a member of StandardSocketOptions
            mcastChannel.setOption(SO_TIMEOUT, ???);
            mcastChannel.receive(buffer);
    
            // Process packet
            ...
        } while (true);
    } ...
    

    这种方法是否有效? DatagramChannel.receive未列出SocketTimeoutException作为其能够抛出的异常之一。如果这样可行,那么请告诉我如何将第二个实现更改为与第一个相同,但是当客户端调用ClosedByInterruptException时能够抛出Thread.interrupt。如果没有,那么有没有人对如何满足在预定义时间停止数据报接收的要求有任何其他想法,同时还提供了一种通过用户交互来停止执行的方法?

2 个答案:

答案 0 :(得分:1)

  

如何在DatagramChannel上设置SO_TIMEOUT参数?

致电channel.socket().setSoTimeout()

  

如何将InetAddress转换为NetworkInterface对象?

您可以枚举网络接口,直到找到具有所需地址的网络接口。

  

DatagramChannel.receive()没有列出SocketTimeoutException

它没有必要。它列出了IOExceptionSocketTimeoutException扩展了IOException

你的第二段代码应该调用bind(),而不是connect(),它应该在调用join()之前设置界面,而不是之后。除此之外,一旦修复网络接口问题,它应该按预期工作,如果被中断,它将抛出ClosedByInterruptException

答案 1 :(得分:0)

最终的工作代码是:

int **twod = new int *[rows];

注意事项:

  1. 致电// Create a datagram packet used to read multicast data from the socket final byte[] buffer = new byte[1000]; final DatagramPacket packet = new DatagramPacket(buffer, buffer.length); // Process incoming datagram packets try (final DatagramChannel mcastChannel = DatagramChannel.open(StandardProtocolFamily.INET)) { // Set the appropriate parameters on the socket mcastChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true); mcastChannel.bind(new InetSocketAddress(port)); if (ifaceIP == null) { // Call join on each NetworkInterface that supports IPv4 multicast } else { mcastChannel.join(mcastIP, NetworkInterface.getByInetAddress(ifaceIP)); } final DatagramSocket socket = mcastChannel.socket(); do { final Duration timeToEnd = Duration.between(Instant.now(), endTime); if (timeToEnd.compareTo(Duration.ZERO) < 0) break; socket.setSoTimeout((int)timeToEnd.toMillis()); socket.receive(packet); // Process packet ... } while (true); } catch (final SocketTimeoutException e) { // The end time has passed } catch (final ClosedByInterruptException e) { // The user initiated the closure } ... 。如果将其更改为socket.receive,则永远不会获得mcastChannel.receive
  2. SocketTimeoutException的呼叫发生在致bind的呼叫之前。您可以根据需要在多个不同的网络接口上多次呼叫join