简体   繁体   English

在Linux上进行TCP打孔和setReuseAddress()

[英]TCP Hole Punching and setReuseAddress() on Linux

I am working on a Java client/server application that involves P2P communication over TCP. 我正在研究一个涉及通过TCP进行P2P通信的Java客户端/服务器应用程序。 I'm trying to implement TCP Hole Punching as described here: http://www.brynosaurus.com/pub/net/p2pnat/#sec-tcp . 我正在尝试按此处所述实现TCP打孔: http : //www.brynosaurus.com/pub/net/p2pnat/#sec-tcp This requires simultaneously listening on and attempting to establish an outgoing connection using the same local TCP port. 这要求同时侦听并尝试使用同一本地TCP端口建立传出连接。 Apparently, this should work if the SO_REUSEADDR socket option is used, which I am setting via the setReuseAddress() method in Java. 显然,如果使用了SO_REUSEADDR套接字选项(我正在通过Java中的setReuseAddress()方法进行设置setReuseAddress() ,这应该可以工作。 However, this is not working as I expected. 但是,这不符合我的预期。 Here is some test code: 这是一些测试代码:

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) { }
            }

        }

    }

}

This works as expected in Windows 7: the ServerSocket listens on port 7077 in its own thread and the Socket repeatedly attempts to connect to 192.168.0.103:7077. 这可以在Windows 7中按预期工作: ServerSocket在其自己的线程中侦听端口7077,并且Socket反复尝试连接至192.168.0.103:7077。 However, under Linux (Ubuntu) only the first Socket connection attempt works, and subsequent attempts get the "Address already in use" BindException . 但是,在Linux(Ubuntu)下,只有第一个Socket连接尝试有效,随后的尝试将获得“地址已在使用中” BindException Shouldn't I be able to establish an outgoing connection from a TCP source port that I'm also listening on simultaneously, and to reuse the local port number immediately after closing the socket, since I have the SO_REUSEADDR option enabled? 因为启用了SO_REUSEADDR选项,我不应该能够从同时监听的TCP源端口建立传出连接,并在关闭套接字后立即重用本地端口号吗?

In Linux, both sockets need to set SO_REUSEADDR socket option. 在Linux中,两个套接字都需要设置SO_REUSEADDR套接字选项。 Thus, if we want two sockets, sock1 and sock2 to be bound ot the same port, then s2 would be able to reuse the port/address only if both sock1 and sock2 set SO_REUSEADDR. 因此,如果我们希望将两个套接字sock1和sock2绑定到同一端口,则只有在sock1和sock2都设置了SO_REUSEADDR的情况下,s2才能够重用该端口/地址。

You are never closing your client socket, unless there is an exception, making the point of SO_REUSEADDR a no-op. 除非有例外,否则您永远都不会关闭客户端套接字,从而使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) { }
}
....

In the above, I moved the closing of the socket to a newly created finally block so it is always executed, even if you break out the global while loop. 在上面的代码中,我将套接字的关闭位置移到了新创建的finally块中,因此即使您中断了全局while循环,它也始终会执行。

Since the socket is now closed under all conditions, the SO_REUSEADDR will use correctly now. 由于套接字现在在所有情况下都是关闭的,因此SO_REUSEADDR现在将正确使用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM