【问题标题】:Trouble with UDP ports and DatagramSocketsUDP 端口和 DatagramSocket 的问题
【发布时间】:2015-04-10 20:01:06
【问题描述】:

我正在开发一个项目,该项目假设使用 DatagramPackets 和 DatagramSockets 将文件从一台机器发送到另一台机器。该实现被假设为模仿 TCP 协议。因此,一旦接收方收到一个数据包,它就会向发送方发回一个 ACK​​,确认数据包已送达。到目前为止,我的程序没有对 ACK 进行任何检查。我无法实现 ACK 消息。在我的接收程序上,它显示正在发送 ACK,但发送方应用程序没有收到它们。

我在创建套接字时不断收到错误消息。 “java.net.BindException:地址已在使用中:无法绑定”。我很困惑,因为发件人应用程序中的其他任何地方都没有指定端口。我只是使用DatagramSocket socket = new DatagramSocket(); 但我确实使用 DatagramPacket packet = new DatagramPacket(packetData, packetData.length, internetAddress, 49000); socket.send(packet); 发送数据包时。

我尝试在我的 waitForAck() 方法中删除数据报声明,并使用与发送数据包相同的 datagramSocket。但是socket.receive(packet); 会挂起并且永远不会收到任何东西,因为它没有被分配一个端口来监听。

这是我监听 ACK 的方法:

public void waitForACK(){
    //listen for ack for a period of time
    //if ACK received, then break send next packet
    //if ACK not received or time out, send last packet
    //TODO: implement a timeout
    System.out.println("### Sender waiting for ACK");
    try {
        DatagramSocket receivingSocket = new DatagramSocket(49000);
        while (!ACKreceived) {
            byte[] buf = new byte[1500]; // Actual Ethernet packet size is 1500 bytes
            // receive request
            DatagramPacket packet = new DatagramPacket(buf, buf.length);
            receivingSocket.receive(packet); //socket.receive(packet); <--
            byte[] packetData = Arrays.copyOf(packet.getData(), packet.getLength());
            ACKreceived = checkACK(packetData);//check the recieved packet contains an ACK message
        }
        System.out.println("### Sender recieved ACK");
    } catch (Exception e) {
        System.out.println("### never got ACK");
        System.out.println(e);
    }
}

我也尝试过这个,但是 scoket 会挂起,实际上从来没有收到任何东西。即使接收文件的应用程序成功报告发送 ACK。我猜是因为它不知道在端口 49000 上接收 ACK。

public void waitForACK(){
    //listen for ack for a period of time
    //if ACK received, then break send next packet
    //if ACK not received or time out, send last packet
    //TODO: implement a timeout
    System.out.println("### Sender waiting for ACK");
    try {
        while (!ACKreceived) {
            byte[] buf = new byte[1500]; // Actual Ethernet packet size is 1500 bytes
            // receive request
            DatagramPacket packet = new DatagramPacket(buf, buf.length);
            socket.receive(packet); //<--- HANGS RIGHT HERE
            byte[] packetData = Arrays.copyOf(packet.getData(), packet.getLength());
            ACKreceived = checkACK(packetData);//check the recieved packet contains an ACK message
        }
        System.out.println("### Sender recieved ACK");
    } catch (Exception e) {
        System.out.println("### never got ACK");
        System.out.println(e);
    }
}

【问题讨论】:

  • 在端口空闲时使用 netstat 等工具进行绑定错误检查。 “已在使用”意味着肯定即使您的代码未绑定某些进程。您编辑的代码看起来如何? (你说你在哪里删除了Datagram 声明?
  • 我编辑了我的帖子以包含您要求的代码。这只是我的 waitForACK() 方法的修改版本。与此同时,我将对 netstat 进行一些研究。
  • 对方是否明确发送到49000端口?本地端口和对等端口通常不相同,除非您明确将它们设为相同,因此您发送到 49000 的事实并不意味着您应该期望在 49000 上接收。

标签: java sockets tcp udp datagram


【解决方案1】:

你正在泄漏套接字。

不要仅仅为了等待 ACK 而创建新的套接字。在应用程序的整个生命周期内,您应该只打开一个 DatagramSocket

【讨论】:

    【解决方案2】:

    尝试使用 netstat 命令检查端口上是否有其他程序(甚至您的程序)处于活动状态。在 unix netstat -lp 上,su 会告诉你,在 windows 上 netstat 也存在不同的命令行选项

    【讨论】:

      【解决方案3】:

      在我们解决您的代码问题之前:为什么客户端尝试侦听端口 49000?

      如果您还没有意识到这一点:本地端口和对等端口不必相同,而且通常不是。当您调用DatagramSocket() 时,您将获得操作系统分配的任意本地端口。您发送到 49000 的事实不会改变您的本地端口。如果连接的另一端只是将数据包发送回它接收到的元组,则不会到达 49000,它将到达您的本地端口。

      如果这是您的问题,解决方法是使用第二个版本(只需使用您现有的套接字来监听和发送),然后修复另一端(您没有向我们展示代码)发送对数据包发送者的完整地址元组的 ACK,而不是发送者主机上的端口 49000。


      如果您意识到这一点,但认为双方出于某种原因需要拥有本地端口 49000……好吧,他们可能没有。通常,协议需要一侧(“服务器”)有一个众所周知的端口来连接,但另一侧(“客户端”)不需要。这就是为什么您可以在客户端上使用 DatagramSocket() 而不是 DatagramSocket(49000) 并且一切正常的原因。

      再次,同样的修复。


      在极少数情况下,双方确实确实需要有一个众所周知的端口(例如,您可以在公司的内部防火墙中显式打开它),您几乎肯定希望发送到发生在那个端口上。

      因此,与其创建用于发送的DatagramSocket() 和用于监听的DatagramSocket(48000),不如先创建一个DatagramSocket(48000) 并将其用于两者。

      但是,请注意,与使用固定端口的任何解决方案一样,此解决方案还有两个额外的问题:

      首先,如果客户端和服务器都想绑定端口 48000,它们不能同时运行在同一台机器上。您可以将其中一个重新编号为 48001,或者直接接受。

      其次,如果您希望频繁启动和停止客户端,它通常会尝试绑定端口 49000,而操作系统仍然有一个处于TIME_WAIT 状态的端口的套接字,因此您将获得一个绑定错误。这就是SO_REUSEADDR 的用途;使用它。


      如果您确实想在客户端上使用任意端口发送器,但使用固定端口侦听器怎么办?在某些情况下这是有道理的,但除非你能解释为什么你真的需要这个,否则你没有。

      如果你这样做了,那么,只有这样,你才能使用类似于你的第一个版本的东西。但是您仍然可能希望创建一次侦听器套接字,而不是每次侦听 ACK;它应该是与发送套接字不同的属性。 (当然你仍然需要处理与上一节相同的事情。)

      如果您确实想为每个 ACK​​ 创建一个新的侦听器套接字,那么您必须确保立即关闭它,而不是等待 Java GC 和操作系统共同为您关闭它,或者下次等待 ACK 时,可能会出现绑定错误,因为旧的侦听器套接字仍然绑定到它。

      【讨论】:

      • 为什么ACK套接字要和发送套接字不同?
      • @EJP:不应该,除非在极少数情况下。这就是答案的重点:除非您有(并且需要)其中一种罕见的情况,否则只需使用客户端的第二个版本,然后 修复服务器 以响应客户端发送的相同端口来自。
      • @EJP:请注意,仅在最后一个版本中,您需要两个套接字——您明确需要双方都有一个固定端口侦听器,并且您还明确需要发送来自不同的端口。在这种情况下,很明显为什么 ACK 套接字应该与发送套接字不同——如果您确实需要不同的端口,则需要单独的套接字。
      • 那么“它应该是与发送套接字不同的属性”是什么意思?你到底怎么可能需要两个不同的端口?
      • @EJP:看上下文。 “如果你确实 [需要这种非常罕见的情况,因为上面的“几乎总是”部分对你来说都不是真的],那么,只有到那时,你才能使用类似于你的第一个版本的东西。”你没有到达那里? (至于“它应该只是与发送套接字不同的属性”的含义:这意味着,即使在需要两个套接字的极少数情况下,您仍然不想在本地创建第二个并泄漏它; 你想让它成为一个属性,就像第一个一样。)
      猜你喜欢
      • 1970-01-01
      • 2017-12-10
      • 1970-01-01
      • 2017-07-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-11
      • 2021-12-01
      相关资源
      最近更新 更多