TCP 打孔 Java 示例

Posted

技术标签:

【中文标题】TCP 打孔 Java 示例【英文标题】:TCP Hole Punching Java Example 【发布时间】:2015-02-10 04:36:20 【问题描述】:

我在本地机器上使用以下代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * Just for testing socket SO_RESUEADDR. If set SO_RESUEADDR to true, we can use
 * a single local port to listen for incoming TCP connections, and to initiate
 * multiple outgoing TCP connections concurrently. By this way we can implement
 * TCP hole punching(establish P2P connection traversal through NAT over TCP).
 */
public class TcpPeer2 
// TCP port is a different source from UDP port, it means you can listen on
// same port for both TCP and UDP at the same time.
private int localport = 7890;
private ServerSocket peerSock;
private Socket serverSocket;

public TcpPeer2(final String serverHost, final int serverPort, final int localPort)
       throws Exception 
 this.localport = localPort;

 Thread server = new Thread(new Runnable() 

   @Override
   public void run() 
     try 
       peerSock = new ServerSocket();
       System.out.println(peerSock.isBound());
       peerSock.setReuseAddress(true);
       System.out.println(peerSock.isBound());
       peerSock.bind(new InetSocketAddress("localhost", localport));
       System.out.println("[Server]The server is listening on " + localport + ".");
       System.out.println(peerSock.isBound());
       System.out.println(peerSock.isClosed());
       System.out.println(peerSock.getLocalSocketAddress().toString());
       //peerSock.
       while (true) 
         try 
           serverSocket = peerSock.accept();
           // just means finishing handshaking, and connection
           // established.
           System.out.println("[Server]New connection accepted"
                   + serverSocket.getInetAddress() + ":" + serverSocket.getPort());

           BufferedReader br = getReader(serverSocket);
           PrintWriter pw = getWriter(serverSocket);
           String req = br.readLine();
           System.out.println("[Server][REQ]" + req);
           pw.println(req);

 //              pw.close();
 //              br.close();
          catch (IOException e) 
           e.printStackTrace();
          finally 
 //              try 
 //                if (serverSocket != null)
 //                  serverSocket.close();
 //               catch (IOException e) 
 //                e.printStackTrace();
 //              
         
       
      catch (Exception e) 
       e.printStackTrace();
     
   

 );
 // server.setDaemon(true);
 server.start();

 Thread.currentThread();
 // sleep several seconds before launch of client
 Thread.sleep(5 * 1000);

 final int retry = 5;
 Thread client = new Thread(new Runnable() 

   @Override
   public void run() 
     Socket socket = new Socket();
     try 
       socket.setReuseAddress(true);
       System.out.println("[Client]socket.isBound():" + socket.isBound());
       socket.bind(new InetSocketAddress("localhost", localport));
       for (int i = 1; i < retry; i++) 
         try 
           socket.connect(new InetSocketAddress(serverHost, serverPort));
           System.out.println("[Client]connect to " + serverHost + ":"
                   + serverPort + " successfully.");
           break;
          catch (Exception e) 
           System.out.println("[Client]fail to connect " + serverHost + ":"
                   + serverPort + ", try again.");
           Thread.currentThread().sleep(i * 2 * 1000);
           /**
            * PeerA and PeerB
            * <p>
            * Alternatively, A's TCP implementation might
            * instead notice that A has an active listen socket
            * on that port waiting for incoming connection
            * attempts. Since B's SYN looks like an incoming
            * connection attempt, A's TCP creates a new stream
            * socket with which to associate the new TCP
            * session, and hands this new socket to the
            * application via the application's next accept()
            * call on its listen socket. A's TCP then responds
            * to B with a SYN-ACK as above, and TCP connection
            * setup proceeds as usual for client/server-style
            * connections.
            * <p>
            * Since A's prior outbound connect() attempt to B
            * used a combination of source and destination
            * endpoints that is now in use by another socket,
            * namely the one just returned to the application
            * via accept(), A's asynchronous connect() attempt
            * must fail at some point, typically with an
            * “address in use” error. The application
            * nevertheless has the working peer-to- peer stream
            * socket it needs to communicate with B, so it
            * ignores this failure.
            */
           if (i == retry - 1) 
             System.out
                     .println("[Client]Use the socket returned by ServerSocket.");

             socket = serverSocket;
           
         
       

       PrintWriter pw = getWriter(socket);
       String msg = "hello world!";
       pw.println(msg);

       /**
        * Got response from the server socket.
        */
       BufferedReader br = getReader(socket);
       String resp = br.readLine();
       System.out.println("[Client][RESP-1]" + resp);

       /**
        * The client thread of other process will send request. If
        * fail to establish connection with other peer, the Socket
        * return by the ServerSocket will be used.
        */
       resp = br.readLine();
       System.out.println("[Client][RESP-2]" + resp);
//          pw.close();
//          br.close();
      catch (Exception e) 
       e.printStackTrace();
      finally 
//          try 
//            socket.close();
//           catch (Exception e) 
//            e.printStackTrace();
//          
     
   

 );
 client.start();
  

  private PrintWriter getWriter(Socket socket) throws IOException 
    OutputStream socketOut = socket.getOutputStream();
    return new PrintWriter(socketOut, true);
  

  private BufferedReader getReader(Socket socket) throws IOException 
    InputStream socketIn = socket.getInputStream();
    return new BufferedReader(new InputStreamReader(socketIn));
  

  public static void main(String[] args) throws Exception 
   new TcpPeer2("127.0.0.1", 8000, 7000);
  

但它给了我一个 JVM 绑定异常。

我已从以下链接下载此代码: http://ramonli.blogspot.in/2012/03/tcp-hole-punching-how-to-establish-tcp.html

理论上它应该可以正常工作并且不会抛出任何异常。 因此,它应该是 Java 中 TCP 打孔的模板。

我做错了什么?

【问题讨论】:

不是您问题的答案,但您可以将Thread.currentThread().sleep(i * 2 * 1000) 替换为TimeUnit.SECONDS.sleep(i * 2) 你能添加你得到的异常吗?也许您使用的端口已经在使用中? false false [Server]服务器正在监听 7000。true false localhost/127.0.0.1:7000 [Client]socket.isBound():false java.net.BindException: 地址已在使用中: JVM_Bind at java.net.DualStackPlainSocketImpl.bind0(Native Method) at java.net.DualStackPlainSocketImpl.socketBind(DualStackPlainSocketImpl.java:106) at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:376) at java.net.PlainSocketImpl。 bind(PlainSocketImpl.java:190) at java.net.Socket.bind(Socket.java:631) at TcpPeer2$2.run(TcpPeer2.java:92) at java.lang.Thread.run(Thread.java:745) @flob 这是我收到的跟踪信息 是的 正在使用相同的端口 这就是为什么我设置了 setReuseAddress(true) 这必须为 TCP 打孔完成 如果您的客户端仍在侦听,您无法将其绑定到与服务器相同的侦听端口。通过绑定,您将拥有完全相同的一对 local(addr:port)/remote(addr:port),即 localhost:7000/*:*。您可以使用socket.bind(new InetSocketAddress("localhost", 0)); 将您的客户端绑定到一个随机的免费本地端口,但这似乎打败了您的目标。 【参考方案1】:

当服务器仍在侦听时,您无法将客户端绑定到同一个本地主机:端口,即使使用 SO_REUSEADDR 也是如此。

请参阅man socket 了解此内容:

SO_REUSEADDR

表示用于验证提供的地址的规则 在 bind(2) 调用中应该允许重用本地地址。为了 AF_INET sockets 这意味着一个socket可以绑定,除非当 有一个活动的监听套接字绑定到该地址。 当监听套接字绑定到 INADDR_ANY 时 特定端口,则无法绑定到该端口 任何本地地址。参数是一个整数布尔标志。

您可以使用socket.bind(new InetSocketAddress("localhost", 0)); 将您的客户端绑定到一个随机的免费本地端口,但这会破坏您的目的。

【讨论】:

那有什么方法可以实现TCP打孔呢? 对不起,我手头没有好的代码示例,也没有自己做过。但它至少应该回答您的问题中仅明确包含问题我做错了什么?。所以从技术上讲,这是一个有效的答案,即使它不能解决根本问题:-/ 对此感到抱歉。

以上是关于TCP 打孔 Java 示例的主要内容,如果未能解决你的问题,请参考以下文章

用于 UDP NAT 打孔的 PHP 和 Java...?

Delphi:TCP 打孔

TCP打孔(NAT穿越)库还是啥?

为啥这种方式的 TCP 打孔不起作用?

iPhone上的TCP打孔

打孔后未建立 TCP 连接