2017-03-16 63 views
-1

我有一些代碼從多播套接字讀取數據直到用戶定義的結束時間。如果線程通過調用Thread.interrupt(或通過您可以提出的任何其他用戶啓動的操作)中斷,我還想停止讀取數據。當線程中斷時,我無法弄清楚如何獲得通知。現有的代碼如下:Java獲取MulticastSocket.receive來拋出ClosedByInterruptException

// 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對象?

以下是關於如何從MulticastSocketDatagramChannel我的實現將我最好的猜測,以滿足我的要求:

// 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作爲它能夠拋出的例外之一。如果這能起作用,那麼請讓我知道我需要如何將第二個實現更改爲與第一個實現相同,但是當客戶端調用Thread.interrupt時能夠拋出ClosedByInterruptException。如果沒有,那麼是否有人有任何其他想法來滿足在預定義時間停止數據報接收的要求,同時還提供了一種通過用戶交互來停止執行的方法?

+0

不要對沒有引用的文本使用引號格式。 – EJP

回答

1

如何在DatagramChannel上設置SO_TIMEOUT參數?

致電channel.socket().setSoTimeout()

如何將InetAddress轉換爲NetworkInterface對象?

您列舉了網絡接口,直到找到具有所需地址的網絡接口。

DatagramChannel.receive()沒有列出SocketTimeoutException

它不就得了。它列出了IOException,並且SocketTimeoutException延伸了IOException

你的第二段代碼應該調用bind()而不是connect(),它應該在調用join()之前設置內部,而不是之後。除此之外,一旦你解決了網絡接口問題,它應該按預期工作,如果中斷,它將拋出ClosedByInterruptException

0

最後,工作代碼爲:

// 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 
} ... 

注意事項:

  1. socket.receive呼叫。如果更改爲mcastChannel.receive,則永遠不會獲得SocketTimeoutException
  2. 致電bind發生在致電join之前。您可以根據需要多次撥打join,在多個不同的網絡接口上進行。
相關問題