我正在开发一个项目,该项目假设使用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);
}
}
答案 0 :(得分:1)
你正在泄漏套接字。
不要只是为了等待ACK而创建一个新的套接字。在应用程序的生命周期中,您应该只打开一个DatagramSocket
。
答案 1 :(得分:0)
尝试使用netstat命令检查端口上是否有其他程序(甚至程序)处于活动状态。在su将告诉你的unix netstat -lp上,在windows netstat上也存在不同的命令行选项
答案 2 :(得分:-1)
在我们解决您的代码问题之前:为什么客户端试图侦听端口49000?
如果您还没有意识到这一点:本地端口和对等端口不必相同,通常不一样。当您调用DatagramSocket()
时,您将获得操作系统分配的任意本地端口。您发送到49000的事实不会更改您的本地端口。如果连接的另一端只是发送回元组,它收到一个数据包,它不会到达49000,它将到达你的本地端口。
如果这是你的问题,修复方法是使用第二个版本(只需使用你现有的套接字来收听和发送),然后修复另一个版本(你还没有向我们展示)代码))将ACK发送到数据包发送方的完整地址元组,而不是发送方主机上的端口49000。
如果你意识到这一点,但认为双方都需要出于某种原因拥有本地端口49000 ......好吧,他们可能不会这样做。通常,协议需要一方(&#34;服务器&#34;)有一个众所周知的连接端口,但另一方(&#34;客户端&#34;)并不需要。这就是为什么您可以在客户端上使用DatagramSocket()
而不是DatagramSocket(49000)
的原因。
同样,同样的修复。
在极少数情况下,双方真的做需要有一个众所周知的端口(例如,所以你可以在公司的内部防火墙中明确打开它),你几乎可以肯定希望发送到也在该端口上发生。
因此,不是创建要发送的DatagramSocket()
和要监听的DatagramSocket(48000)
,而是先创建一个DatagramSocket(48000)
并将其用于两者。
但请注意,此解决方案与使用固定端口的任何解决方案一样,还有两个问题:
首先,如果客户端和服务器都想绑定端口48000,则它们不能在同一台机器上运行。您可以将其中一个重新编号为48001,或者只接受它。
其次,如果您希望经常启动和停止客户端,它通常会尝试绑定端口49000,而操作系统仍然有TIME_WAIT
状态的端口的套接字,所以你&# 39;重新获得绑定错误。这是SO_REUSEADDR
的用途;用它。
如果您确实想在客户端上使用任意端口发件人,而是使用固定端口侦听器,该怎么办?在某些情况下,这是有道理的,但除非你能解释为什么你真的需要这个,否则你就没有。
如果你这样做,那么,只有这样,你能否使用类似你的第一个版本的东西。但是仍然可能想要创建一次侦听器套接字,而不是每次都要监听ACK;它应该是与发送套接字不同的属性。 (当然,你仍然需要处理与上一节相同的事情。)
如果您确实想为每个ACK创建一个新的侦听器套接字,那么您必须确保立即关闭它,而不是等待Java GC和操作系统集体解决它为您关闭它,或者下次等待ACK时,您可能会收到绑定错误,因为旧的侦听器套接字仍然绑定到它。