TCP 接続 (J)

はじめに

まずは、直接 TCP 接続をおこなってネットワーク接続の基本を確認しましょう。 実行させやすいように、サーバとクライアントでプログラムを分けてあります。

今回おこなうのは、サーバからクライアントへのファイル転送ですね。

  • クラス SendServer:
    • サーバプログラムとして、指定 port で待っている。
    • RecvClient との接続を確立すると、サーバからのデータ送信(ファイル転送)をおこなう。
      • このプログラムは 1 回クライアントの相手をしたら終了。
  • クラス RecvClient:
    • ホスト名とポート名を指定して、SendServer に接続を行う。
    • 接続が確立すると、サーバからのデータを受信して、ファイルに格納する

となっています。

実行手順

まずは実行させてみましょう。 二つのプログラムを起動しないといけませんので、要注意。

それから、以下のような状況では例外がおきますので、実行手順を把握して動かして下さい。

  • サーバ用ポートが使用中だと例外
    • 同じポートを使ったプログラムを複数動かしていないか確認すること(デバッグ中プログラムを実行させたままにしている場合とか特に注意)
  • サーバ起動させていないと、クライアント接続時に失敗
    • 先にサーバ側を起動させること

あと、プログラムの通信相手ホストは、default で localhost になっていますが、変えてもらっても大丈夫です。 ただ、firewall が動いている場合、サーバが動いていても外部からの接続はキックされますので、気をつけて。

プログラムの起動に関してですが、

  • eclipse でも IntelliJ でも、対象クラスを選んで run すれば OK です。eclipse では、実行のさせ方の選択がでるでしょうが Java アプリケーション を選んでください。
  • eclipse でも IntelliJ でも、二つのプログラムそれぞれに対してコンソールが現れます。
    • eclipse では、「プルダウン付きモニター画面」のようなアイコンで対象「program run」を選べます。
    • intelliJ では、タブが複数現れます。
  • 環境によって firewall を開けるか問い合わせが来ると思います。localhost だけで実験するなら開けなくて良い(キャンセル)です。複数マシンで実験するなら、対応するネットワークを選んで開放してください。
  • このプログラムでは、ファイル転送をおこなっており、送信ファイル(データ)は project 直下の sampleFilessampleUTF8.txt で、出力結果は同じsampleFiles 以下に recvOutput.txt を作成します。
    • プログラム実行後もファイルが見えないときは、file 閲覧画面で reload してください。

接続確立編

実現したいのは、以下の手順

  1. Server が外部ポートを開いて client を socket で待っている。
  2. Client が上記ホスト&ポートに接続し、server-client 間を socket で接続する。

socket.png

サーバプログラム (SendServer)

try (/* ServerSocket を開く */
     ServerSocket serverSocket = new ServerSocket(portNum);
     /* CLient からのアクセス待ち */
     Socket socket = serverSocket.accept();
     ){
    /* 接続完了 */
    System.out.println("Connection established:  " + socket);
    sendFile(socket, filename);
}

やっている内容は

  • 指定ポートで、接続待ち
    • Java では接続待ち用の socket は、java.net.ServerSocketを用いる
    • ちなみに、ポートが使用中だと例外が起きる
  • やってきた接続を受理し(接続確立)
    • accept() は、相手が来るまで帰ってこない
    • こういうI/O を blocking I/O という
  • sendFile の中で、実際のデータ送信作業(後述)

try ブロックが、見慣れぬ形をしているでしょうけど、例外処理で説明します。

クライアントプログラム(RecvClient)

/* IP address の取得 */
InetAddress host = InetAddress.getByName(hostName);
/* Socket の生成 */
System.out.println("Connecting to " + host + ":" + portNum);
try(Socket socket = new Socket(host, portNum)) {
    receiveFile(socket, filename);
}
  • まず、hostName から IP address (java.net.InetAddress) を取得し、
  • 接続を確立し
    • 通信のためのデータ構造はsocket と呼ばれる。
    • Java では java.net.Socket を用いる
    • ちなみに、相手がいないと例外が起きる
  • recvFile の中で、実際のデータ受信作業(後述)

データ送受信編

で、接続が確立されて、Input/OutputStream が取得されれば、後はこんなかんじ。

  • Server: file からデータをよんで、 socket に送る
  • Client: socket からデータをよんで、 file に書き込む

rcopy.png

送信側(SimpleSend)

try (/* Socket から OutputStream を作成 */
     OutputStream netOut = socket.getOutputStream();
     /* FileInputStream作成  */
     InputStream fileIn = new FileInputStream(filename)) {
    byte[] buf = new byte[BUFSIZE];
    while (true) {
        /* in から read。IOException の可能性 */
        int size = fileIn.read(buf, 0, BUFSIZE);
        if (size < 0) { /* EOF なら、loop を抜けて */
            break;
        } else {
            /* size 分, out に出力。IOException の可能性 */
            netOut.write(buf, 0, size);
        }
    }
    System.out.println("Data send completed.");
}
  • Socket から OutputStream を取得
  • FileInputStream を作成
  • 一定サイズ in から取り込んでは、out に送る

受信側(SimpleRecv)

try (/* Socket から InputStream を作成 */
     InputStream netIn = socket.getInputStream();
     /* FileOutputStream 作成 */
     OutputStream fileOut = new FileOutputStream(filename)) {
    byte[] buf = new byte[BUFSIZE];
    while (true) {
        /* in から read。IOException の可能性 */
        int size = netIn.read(buf, 0, BUFSIZE);
        if (size < 0) { /* EOF なら、loop を抜けて */
            break;
        } else {
            /* size 分, out に出力。IOException の可能性 */
            fileOut.write(buf, 0, size);
        }
    }
    System.out.println("Data receive completed.");
}
  • InputStreamは socket から取得
  • OutputStream は、File へ書き出し
  • 一定サイズ in から取り込んでは、out に送る