2012-03-20 25 views
1

私はタイトルの質問をはっきりと説明する方法がわかりません。私にとっては少し複雑です。私がやっていることは、ローカルポートがリッスンとソケットの開始の両方でなければならないTCPピアツーピアデモを実装しようとすることです。Java TCP SO_RESUEADDR、接続できません

詳細な説明をします。 私は、単一のローカルポートで接続をリッスンして開始するJava実装を提供します。コードが私の考えを説明します。 2つのプロセスが安定して得たときに最後に、彼らは出力の下に与える

ps#1> java TcpPeer localhost 2000 4000 
ps#2> java TcpPeer localhost 4000 2000 

PS#1>

[Server]The server is listening on 2000. 
[Client]socket.isBound():false 
[Client]connect to localhost:4000 successfully. 
[Client][RESP-1]hello world! 

PS

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 TcpPeer { 
    // 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 TcpPeer(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(); 
       peerSock.setReuseAddress(true); 
       peerSock.bind(new InetSocketAddress("localhost", localport)); 
       System.out.println("[Server]The server is listening on " + localport + "."); 

       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) { 
      e.printStackTrace(); 
         System.out.println("[Client]fail to connect " + serverHost + ":" 
           + serverPort + ", try again."); 
         Thread.currentThread().sleep(i * 2 * 1000); 
      if (i == retry - 1) return; 
        } 
       } 

       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); 

       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 { 
    if (args.length != 3) { 
     System.out.println("[Usage] java " + TcpPeer.class.getCanonicalName() 
       + " [serverHost] [serverPort] [localPort]"); 
     System.exit(0); 
    } 

    new TcpPeer(args[0], Integer.parseInt(args[1]), Integer.parseInt(args[2])); 
} 
} 

は、今、私たちは2つのJVMプロセスを起動します#2>

[Server]The server is listening on 4000. 
[Server]New connection accepted/127.0.0.1:2000 
[Server][REQ]hello world! 
[Client]socket.isBound():false 
java.net.BindException: Address already in use: connect 
    at java.net.PlainSocketImpl.socketConnect(Native Method) 
    at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333) 
    at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195) 
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182) 
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366) 
    at java.net.Socket.connect(Socket.java:525) 
    at java.net.Socket.connect(Socket.java:475) 
    at org.clinic4j.net.TcpPeer$2.run(TcpPeer.java:92) 
    at java.lang.Thread.run(Thread.java:619) 
[Client]fail to connect localhost:2000, try again. 
java.net.SocketException: Socket operation on nonsocket: connect 
    at java.net.PlainSocketImpl.socketConnect(Native Method) 
    at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333) 
    at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195) 
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182) 
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366) 
    at java.net.Socket.connect(Socket.java:525) 
    at java.net.Socket.connect(Socket.java:475) 
    at org.clinic4j.net.TcpPeer$2.run(TcpPeer.java:92) 
    at java.lang.Thread.run(Thread.java:619) 
[Client]fail to connect localhost:2000, try again. 
java.net.SocketException: Socket operation on nonsocket: connect 
    at java.net.PlainSocketImpl.socketConnect(Native Method) 
    at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333) 
    at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195) 
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182) 
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366) 
    at java.net.Socket.connect(Socket.java:525) 
    at java.net.Socket.connect(Socket.java:475) 
    at org.clinic4j.net.TcpPeer$2.run(TcpPeer.java:92) 
    at java.lang.Thread.run(Thread.java:619) 
[Client]fail to connect localhost:2000, try again. 
java.net.SocketException: Socket operation on nonsocket: connect 
    at java.net.PlainSocketImpl.socketConnect(Native Method) 
    at java.net.PlainSocketImpl.doConnect(PlainSocketImpl.java:333) 
    at java.net.PlainSocketImpl.connectToAddress(PlainSocketImpl.java:195) 
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:182) 
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:366) 
    at java.net.Socket.connect(Socket.java:525) 
    at java.net.Socket.connect(Socket.java:475) 
    at org.clinic4j.net.TcpPeer$2.run(TcpPeer.java:92) 
    at java.lang.Thread.run(Thread.java:619) 
[Client]fail to connect localhost:2000, try again. 
の出力から

、我々は相互作用は以下のように流れていることを把握することができます:

  1. PS#1は2がPSに接続する4000
  2. PS#1に聞く2000年
  3. のps#に聞きます#2、from localhost:2000 - > localhost:4000。
  4. ps#2は、ステップ#3で確立された接続を閉じます。
  5. ps#2は2000年にps#1に接続しようとしましたが、失敗しました!

なぜ、ps#2がステップ#4でps#1に接続できないのですか?私はまた、OSのネットステータスを監視します。

以下は、手順3の直後のネットステータスです。 enter image description here

また、手順4の直後のネットステータス。 enter image description here

  • 192.168.2.107は

をlocalhostですあなたは私の私の場合にコメントをお願いできますか?ありがとう!

再接続に失敗した場合、元の例外メッセージが表示されていますが、その例外についてはわかりません。

+0

"ローカルポートは、リスンとソケットの開始の両方でなければなりません。"どうして? – EJP

+0

私は[Peer-to-Peer Communication of Network Address Translators](http://www.brynosaurus.com/pub/net/p2pnat/)の論文を読んで、NAT経由でP2P TCP接続を確立しようとします。 – Ramon

+0

こんにちはラモン、同様の状況で立ち往生。あなたはこれのための解決策を見つけましたか?ありがとう! – pkrish

答えて

3

クライアントソケットをバインドしたり、クライアントソケットにSO_REUSEADDRを設定したりしないでください。

クライアントソケットをバインドしないと、システムによって自動的にポート番号が割り当てられます。

また、SO_REUSEADDRは、ソケットがまだ開いている間にアドレス(IP /ポート番号)を再利用できるわけではありません。ソケットが閉じられていて、TIME_WAITの状態になっているときは、それを再びバインドすることができます。

+0

TCP上でSTUNを実証するデモを実装したいのですが、クライアントソケットはサーバソケットと同じポートにバインドされていなければなりません。ネットワークの知識についての良い背景を持っていない。 – Ramon

0

クライアントスレッドはメッセージの最初の交換の直後に終了し、while(true)部分が欠けています。

+0

彼はそれほど遠くにいなくても、その点は疑問です。答えではありません。 – EJP

+0

@EJP OPログクライアントは、[RESP-1] hello world!をコンソールに出力します。コードからはクライアントが 'Thread#run()'と入力したようですが、このメソッドはループされていないので、1つのクライアントインスタンスにつき1つのメッセージの交換だけが可能です。 –

+0

うん。最初のクライアントソケットを正しく閉じていないので、同じポートにバインドされた2番目のクライアントソケットを作成できないようです。 – EJP

0
  1. 正常に接続せずに接続を再試行した場合は、I/Oコードに入り込まずに終了する必要があります。これが実際の例外を取得している理由です。

  2. なぜ接続が失敗しているかを印刷する必要があります。現時点ではこれが最も重要な情報であり、あなたはそれを抑制しています。例外メッセージを出力します。これは一般的な原則です。自分のメッセージを作成せず、与えられたメッセージを使用します。それはほぼ確実により具体的です。

  3. 新しいInetAddress( "localhost")ではなく、ServerSocketをバインドするときにnullを使用します。

  4. ソケットの接続を再試行できるとは確信していません。失敗した場合は、新しいソケットを作成してみてください。

+0

#2。例外をキャッチするたびに元の例外メッセージを出力するためにe.printStackTrace()を呼び出しました...ああ、forループのtry-catchを意味します。 – Ramon

+0

@Ramon私はあらゆるところを意味します。私は、結果の例外をここに投稿して分析できるようにするべきだということも意味します。それとも、あなたが秘密にしておく必要があるのは本当にそのような秘密ですか? – EJP

関連する問題