【问题标题】:TCP Hole Punching and setReuseAddress() on LinuxLinux 上的 TCP 打孔和 setReuseAddress()
【发布时间】:2013-09-12 19:05:28
【问题描述】:

我正在开发一个涉及基于 TCP 的 P2P 通信的 Java 客户端/服务器应用程序。我正在尝试按照此处所述实现 TCP 打孔: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 选项?

【问题讨论】:

标签: java linux sockets networking tcp


【解决方案1】:

在 Linux 中,两个套接字都需要设置 SO_REUSEADDR 套接字选项。因此,如果我们希望将两个套接字 sock1 和 sock2 绑定到同一个端口,那么只有在 sock1 和 sock2 都设置 SO_REUSEADDR 时,s2 才能重用端口/地址。

【讨论】:

    【解决方案2】:

    除非出现异常,否则您永远不会关闭客户端套接字,从而将 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 现在可以正确使用。

    【讨论】:

      猜你喜欢
      • 2011-09-19
      • 2012-02-07
      • 2016-12-05
      • 2014-03-08
      • 2017-01-25
      • 2014-06-08
      • 2015-02-10
      • 2014-12-28
      • 2013-01-25
      相关资源
      最近更新 更多