データ送受信

はじめに

まずは、簡単なデータ送受信プログラムをつかって、ネットワークプログラムを作成する上での基本事項を学びましょう。 実行させやすいように、サーバとクライアントでプログラムを分けてあります。

その内容は

  • クラス SimpleRecv:
    • サーバプログラムとして、指定 port で待っている。
    • SimpleSend との接続を確立すると、クライアントからのデータ受信を行い、ファイルに書き出す。
  • クラス SimpleSend:
    • ホスト名とポート名を指定して、SImpleRecv に接続を行う。
    • 接続が確立すると、ファイルを読み込んで、SimpleRecv に送信する。

となっています。

実行手順

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

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

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

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

接続確立編

サーバプログラム (SimpleRecv)

ServerSocket serverSocket = new ServerSocket(portNum);
/* 接続待ち */
System.out.println("Server waitting on serverSocket " + serverSocket);
Socket socket = serverSocket.accept();
/* 接続完了 */
System.out.println("Connection established:  " + socket);
/* InputStream 取得 */
InputStream in = socket.getInputStream();

やっている内容は

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

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

/* IP address の取得 */
InetAddress host = InetAddress.getByName(hostName);
/* Socket の生成 */
System.out.println("Connecting to " + host + ":" + portNum);
Socket socket = new Socket(host, portNum);
/* OutputStream の取得 */
OutputStream out = socket.getOutputStream();
  • まず、hostName から IP address (java.net.InetAddress) を取得し、
  • 接続を確立し
    • 通信のためのデータ構造はsocket と呼ばれる。
    • Java では java.net.Socket を用いる
    • ちなみに、相手がいないと例外が起きる
  • outputStream を取得する(この後で送信開始)

データ送受信編

で、接続が確立されて、Input/OutputStream が取得されれば、後は、FileIOで紹介したファイルコピーと同じです。

送信側(SimpleSend)

/* OutputStream の取得 */
OutputStream out = socket.getOutputStream();
/* FileInputStream作成: UseSimpleIO と同じ */
InputStream in = new FileInputStream(from);
byte[] buf = new byte[BUFSIZE];
try {
    while (true) {
        /* in から read。IOException の可能性 */
        int size = in.read(buf, 0, BUFSIZE);
        if (size < 0) { /* EOF なら、loop を抜けて */
            break;
        } else {
            /* size 分, out に出力。IOException の可能性 */
            out.write(buf, 0, size);
        }
    }
    System.out.println("Data send completed.");
} finally {
    out.close(); /* out の close(), IOException の可能性 */
    socket.close();
}
  • InputStream は、File から取得
  • OutputStream は socket から取得しておいたもの(ここが違う)
  • 一定サイズ in から取り込んでは、out に送る
  • 最後に終了処理として、使ったリソースの開放

ファイルコピーと違うのは、 OutputStream の作り方と、最後にsocket を閉じているぐらいです。

受信側(SimpleRecv)

/* InputStream 取得 */
InputStream in = socket.getInputStream();
/* FileOutputStream作成 */
OutputStream out = new FileOutputStream(filename);
byte[] buf = new byte[BUFSIZE];
try {
    while (true) {
        /* in から read。IOException の可能性 */
        int size = in.read(buf, 0, BUFSIZE);
        if (size < 0) { /* EOF なら、loop を抜けて */
            break;
        } else {
            /* size 分, out に出力。IOException の可能性 */
            out.write(buf, 0, size);
        }
    }
    System.out.println("Data receive completed.");
} finally {
    out.close(); /* out の close(), IOException の可能性 */
    socket.close();
    serverSocket.close();
}
  • InputStreamは socket から取得
  • OutputStream は、File へ書き出し
  • 一定サイズ in から取り込んでは、out に送る
  • 終了処理
    • serverSocket を閉じておきましょう。放っておいても、そのうち OS などが回収しますが、その間ポートが使えないと面倒です。

こちらも、ほとんどファイルコピーと同じですね。