我正在开发一个涉及TCP上P2P通信的Java客户端/服务器应用程序。我正在尝试实现TCP Hole Punching,如下所述:http://www.brynosaurus.com/pub/net/p2pnat/#sec-tcp。这需要同时侦听并尝试使用相同的本地TCP端口建立传出连接。显然,如果使用SO_REUSEADDR
套接字选项,这应该可以使用,我通过Java中的setReuseAddress()
方法设置。但是,这并不像我预期的那样有效。这是一些测试代码:
import java.io.IOException;
import java.net.*;
public class Test {
public static void main(String args[]) {
new Thread() {
public void run() {
try {
ServerSocket ss = new ServerSocket();
ss.setReuseAddress(true);
ss.bind(new InetSocketAddress(7077));
ss.accept();
} catch (Exception e) {
System.out.println("ServerSocket exception: " + e.getMessage());
}
}
}.start();
Socket s;
while (true) {
s = new Socket();
try {
s.setReuseAddress(true);
s.bind(new InetSocketAddress(7077));
s.connect(new InetSocketAddress("192.168.0.103", 7077));
break;
} catch (Exception e) {
System.out.println("Socket exception: " + e.getMessage());
try { s.close(); } catch (IOException e1) { }
try { Thread.sleep(1000); } catch (InterruptedException e1) { }
}
}
}
}
这在Windows 7中按预期工作:ServerSocket
在其自己的线程中侦听端口7077,并且Socket反复尝试连接到192.168.0.103:7077。但是,在Linux(Ubuntu)下,只有第一个Socket连接尝试有效,后续尝试获得“已在使用的地址”BindException
。我是否应该能够从我同时监听的TCP源端口建立传出连接,并在关闭套接字后立即重用本地端口号,因为我启用了SO_REUSEADDR
选项?
答案 0 :(得分:1)
在Linux中,两个套接字都需要设置SO_REUSEADDR套接字选项。因此,如果我们想要两个套接字,sock1和sock2被绑定到同一个端口,那么仅当sock1和sock2都设置了SO_REUSEADDR时,s2才能重用端口/地址。
答案 1 :(得分:0)
除非有异常,否则永远不会关闭客户端套接字,使SO_REUSEADDR
成为无操作。
....
s = new Socket();
try {
// ...
} catch (Exception e) {
System.out.println("Socket exception: " + e.getMessage());
// remove try block from here
try { Thread.sleep(1000); } catch (InterruptedException e1) { }
} finally {
try { s.close(); } catch (IOException e1) { }
}
....
在上面,我将套接字的关闭移动到新创建的finally块,因此它总是被执行,即使你打破了全局while循环。
由于套接字现在在所有条件下都已关闭,SO_REUSEADDR
现在将正确使用。