我有一个小的概念证明,我试图通过另一个端口路由UDP流量,而任何一方都不知道端口的转换。如果我禁用connect()调用,只需执行“触发和忘记”数据报,我就可以让它正常工作。此外,即使使用connect()调用,我的代码也能工作,但它似乎会选择特定的流量并抛出一个PortUnreachableException。
实际的服务器/客户端(我正在路由的流量)可能向两个方向发送流量。其中一些流量通过的很好,但有些没有,并导致无法到达的端口错误。我已经能够重现这种有问题的服务器/客户端行为,并且我已经使用一个名为run_buggySend()的简单方法将该实现放入我下面的run_buggySend代码中,您将在下面看到这个方法。
我向后和横向地查看了代码,不知道为什么会抛出异常。我知道大多数人都使用bind()或connect(),但两者都不使用,但我的用例同时要求两者,因为我正在路由的一些协议需要非常具体的端口,用于和两个方向的通信。此外,虽然我可以摆脱connect()调用以使其工作,但我喜欢缓存路由信息以提高性能的想法,也不明白为什么它不应该兼容。然后,如果没有bind(),来自硬编码端口的流量永远不会到达我的DatagramChannel。
我还想知道这是否会碰到一些错误的Java代码,因为端口无法访问的异常甚至不会从更相关/更正确的DatagramChannel抛出。如果我将触发数据报发送到3000,那么13001通道将抛出错误。同样,如果我将其发送到13001,则3000通道将抛出一个错误。
也就是说,我将通过下面的最小复制代码。感谢您的任何提示或帮助!
import java.io.IOException;
import java.math.BigInteger;
import java.net.*;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ByteChannel;
import java.nio.charset.StandardCharsets;
public class ByteChannelUDPBridge implements Runnable
{
private final ByteBuffer buff = ByteBuffer.allocateDirect(4096);
private final ByteChannelUDP channelA;
private final ByteChannelUDP channelB;
private final int portBase = 3000;
private final int portOffset = 10000;
public ByteChannelUDPBridge() throws IOException
{
InetAddress loopback = InetAddress.getLoopbackAddress();
channelA = new ByteChannelUDP(
new InetSocketAddress(loopback, portBase), // bind=3000
new InetSocketAddress(loopback, portBase + 1)); // connect=3001
channelB = new ByteChannelUDP(
new InetSocketAddress(loopback, portOffset + portBase + 1), // bind=13001
new InetSocketAddress(loopback, portOffset + portBase)); // connect=13000
}
public void transfer(ByteChannel chanIn, ByteChannel chanOut) throws IOException
{
try
{
chanIn.read(buff);
if (buff.position() > 0)
{
System.out.print(chanIn + " recieved " + buff.position() + " bytes: " + buff.position() + " bytes: ");
buff.flip();
logToHex(StandardCharsets.UTF_8.decode(buff));
buff.rewind();
chanOut.write(buff);
buff.clear();
}
}
catch (PortUnreachableException ex)
{
System.err.println(chanIn + ": " + ex);
}
}
public void run()
{
try
{
while (true)
{
transfer(channelA, channelB); // 3000 >> B:13001 -> C:13000
transfer(channelB, channelA); // 13001 >> B:3000 -> C:3001
Thread.sleep(1);
}
}
catch (IOException | InterruptedException ex) { ex.printStackTrace(); }
finally
{
try
{
channelA.close();
channelB.close();
}
catch (IOException dontCare) { dontCare.printStackTrace(); }
}
}
public void run_buggySend()
{
try
{
DatagramSocket sock = new DatagramSocket(3001); // 13000
InetSocketAddress recip = new InetSocketAddress(InetAddress.getLoopbackAddress(), 3000); // 13001
byte[] buffer = new byte[]{1, 2, 3};
while (true)
{
sock.send(new DatagramPacket(buffer, buffer.length, recip));
Thread.sleep(15000);
}
}
catch (IOException | InterruptedException ex)
{
ex.printStackTrace();
}
}
public void logToHex(CharBuffer arg) { System.out.format("%x\n", new BigInteger(1, arg.toString().getBytes())); }
public static void main(String[] args) throws Exception
{
ByteChannelUDPBridge listener = new ByteChannelUDPBridge();
Thread t1 = new Thread(listener);
t1.start();
Thread t2 = new Thread(listener::run_buggySend);
t2.start();
}
}和
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ByteChannel;
import java.nio.channels.DatagramChannel;
public class ByteChannelUDP implements ByteChannel
{
private final DatagramChannel channel;
private final SocketAddress bind, connect;
public ByteChannelUDP(InetSocketAddress addrBind, InetSocketAddress addrConnect) throws IOException
{
bind = addrBind;
connect = addrConnect;
channel = DatagramChannel.open();
channel.configureBlocking(false);
channel.bind(bind);
channel.connect(connect);
}
@Override
public int read(ByteBuffer dst) throws IOException
{
//return channel.read(dst); // this also causes the unreachable exception
int before = dst.remaining();
SocketAddress rec = channel.receive(dst);
return dst.remaining() - before;
}
@Override
public int write(ByteBuffer src) throws IOException
{
//return channel.write(src); // not used unless i can get read() to work
return channel.send(src, connect);
}
@Override
public boolean isOpen() { return channel.isOpen(); }
@Override
public void close() throws IOException { channel.close(); }
@Override
public String toString()
{
return bind.toString() + " <> " + connect.toString();
}
}发布于 2021-06-18 20:00:34
偶然发现了问题和解决方案。当recieve() / read()方法抛出PortUnreachableException时,触发错误的实际上是send() / write()。
其中一方的接收器显然配置不正确,也不容易修复,因此可以理解的是,中间写将出现错误。
然而,由于一些难以理解的原因,Java决定提醒调用者注意read()方法中无法交付的数据报更有意义,尽管失败的是write()。此外,这种行为似乎也没有被记录下来。Java应该将异常抛出到下一次写入或单独的“错误轮询”方法中,而不是从与写入操作的失败几乎完全无关的读取操作中抛出异常。
https://stackoverflow.com/questions/68037907
复制相似问题