2014年1月7日火曜日

P2P探訪 STUNでNAT越え その1


UPnPを用いて、NAT越えできました。しかし、ルータがUPnPをサポートしていなかったり。UPnPだけでは越えられないNATがあります。

本文では、その代案として前回解説できなかった。「適当なサーバーに接続してみて、相手から見えているアドレスを返してもらう方法」について解説していきます。

TCPの限界

インターネットで公開されている情報のほとんどは、TCPという通信方法でデータをやり取りされています。ですから、インターネットで情報を公開したい場合は、TCPサーバーを立ち上げる事を考える事でしょう。
 しかし、ルータがUPnPをサポートしていない場合、TCPを用いたサーバーを運用する事は困難になります。※ 基本、無理と考えもらって問題ありません。


接続相手から教えてもらう方法はどうした?

適当なサーバーに接続してみて、相手から見えているアドレスを返してもらう事で実現できないのでしょうか。前回はできそうな事を臭わせていました。しかし、TCPにおいて、これは困難です。

実際にTCPのプログラムを書き確認して見ましょう。接続相手のホストアドレスは推測できます。しかし、ポート番号を知るすべはありません。


import java.io.IOException;
import java.net.Inet4Address;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;


public class TCPTest {

 public static void main(String[] args) {
  TCPTest test = new TCPTest();
  test.startServer();
  try {
   Thread.sleep(3000);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  test.startClient();
 }

 private Server mServer = new Server();
 public void startServer() {
  mServer.start();
 }

 public void startClient() {
  try {
   clientAction();
  } catch (UnknownHostException e) {
   e.printStackTrace();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

 public class Server extends Thread {
  @Override
  public void run() {
   try {
    serverAction();
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }
 public void serverAction() throws IOException {
  int port = 8080;
  ServerSocket server = new ServerSocket(port);
  Socket connection = server.accept();
  System.out.println("hostAddress="+connection.getInetAddress().getHostAddress());
  System.out.println("port="+connection.getPort());
 }

 public void clientAction() throws UnknownHostException, IOException {
  int port = 8080;
  Socket client = new Socket(Inet4Address.getByName("127.0.0.1"), port);
  client.getOutputStream().write("binding".getBytes());
 }
}


以下のような値が表示されます。
hostAddress=127.0.0.1

port=52106


サーバーがaccept()時に生成するSocketのさす「アドレスとポート」は接続先のサーバーのアドレスではありません。
なので、ポート番号は0-65535と限りがありますが、どのポートでサーバーが待ち受けているか調べるには、最悪65536回試す必要があります。



UDPを使おう

そこで、NATを越えて通信する方法として、UDPを使用する事でこの問題を解決しましょう。
UDPは、TCPと異なり、相手に接続するポート番号と、待ち受けるポート番号同じになります。このため、「相手から見えているアドレス」だけでなく、ポート番号も知る事ができるのです。



import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;


public class UDPTest {

 public static void main(String[] args) {
  UDPTest test = new UDPTest();
  test.startServer();
  try {
   Thread.sleep(3000);
  } catch (InterruptedException e) {
   e.printStackTrace();
  }
  test.startClient();
 }

 public void startClient() {
  try {
   clientAction();
  } catch (IOException e) {
   e.printStackTrace();
  }
 }

 public void startServer() {
  Thread th = new Thread() {
   public void run() {
    try {
     serverAction();
    } catch (IOException e) {
     e.printStackTrace();
    }
   }
  };
  th.start();
 }
 public void clientAction() throws IOException {
  DatagramSocket socket = new DatagramSocket(8081);
  byte[] buf = new byte[1024];
  DatagramPacket packet= new DatagramPacket(
    buf,buf.length,
    Inet4Address.getByName("127.0.0.1"),
    8080
    );
  socket.send(packet);
 }

 public void serverAction() throws IOException {
  DatagramSocket socket = new DatagramSocket(8080);
  byte[] buf = new byte[1024];
  DatagramPacket packet= new DatagramPacket(buf,buf.length);
  socket.receive(packet);
  System.out.println("address="+packet.getAddress());
  System.out.println("port="+packet.getPort());
 }

}


以下のような値が表示されます。
address=/127.0.0.1

port=8081


サーバーへ接続したクライアントが待ち受けしている。アドレスとポート番号を取得できました。

まとめ

このような感じで、UDPを使う事で、アプリが外部に公開したいる「アドレス」と「ポート番号」を取得できました。UDP使えば、ご家庭のアプリをサーバーとして公開する事も可能でしょう。


次回予告
しかし、取得した「アドレス」と「ポート」には、制限があった。
素直につかえないぞ!!


-------
kyorohiro work

kyorohiro.strikingly.com





0 件のコメント:

コメントを投稿

mbedtls dart の 開発を始めた web wasm ffi io flutter

C言語 で開発した機能を、Dart をターゲットとして、Web でも サーバーでも、そして Flutter  でも使えるようにしたい。 そこで、mbedtls の 暗号化の部分を Dart 向けのPackageを作りながら、 実現方法を試行錯誤する事にした。 dart...