首页 文章

UDP端口和DatagramSockets问题

提问于
浏览 950
0

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

我一直在创建套接字时遇到错误 . "java.net.BindException: Address already in use: Cannot bind" . 我很困惑,因为发件人应用程序中没有其他地方有指定的端口 . 我只是使用 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);
    }
}

3 回答

  • 1

    你正在泄漏套接字 .

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

  • 0

    尝试使用netstat命令检查端口上是否有另一个程序(甚至程序)处于活动状态 . 在su将显示的unix netstat -lp上,在windows netstat上也存在不同的命令行选项

  • -1

    在我们解决您的代码问题之前:为什么客户端尝试侦听端口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的时候,你可能会遇到绑定错误,因为旧的侦听器套接字仍然绑定到它 .

相关问题