概要

FTP(File Transfer Protocol)はファイル転送の古典的なプロトコルで、レガシーシステムとのデータ連携や、定期的なファイル配信の場面で今でも使われています。HTTP と異なり、FTP ではコマンド用とデータ転送用の 2 つの TCP 接続を使うことや、PASV モードによるファイアウォール越しの接続方式など、独自の構造があります。実務では Apache Commons Net の FTPClient を使いますが、プロトコルの仕組みを理解しておくとトラブルシューティングで「どこで止まっているのか」を切り分けやすくなります。この記事では、FTP コマンドの基本(USER/PASS/PASV/STOR/RETR)を TCP ソケットレベルで確認し、応答コードの意味と PASV モードのポート計算を実装します。

使いどころ

FTP サーバーとの接続でタイムアウトやエラーが発生した際に、コマンドレベルで原因を切り分ける

社内システムのファイル連携で FTP を使っている案件で、プロトコルの動作を理解して保守する

FTP 経由のファイル配信バッチで、PASV モードの接続失敗を調査する

コード例

FTP コマンドの基本フローとPASVポート計算
import java.io.*;

public class FtpClientSample {

    record FtpCommand(String name, String arg) {
        FtpCommand(String name) { this(name, ""); }

        String toCommandLine() {
            if (arg.isEmpty()) return name + "\r\n";
            return name + " " + arg + "\r\n";
        }
    }

    static void showFtpProtocol() {
        System.out.println("=== FTP プロトコルの基本 ===");
        System.out.println("1. コマンドチャネル接続: Socket(ftpserver, 21)");
        System.out.println("   S: 220 FTP Service Ready");
        System.out.println("2. 認証:");
        System.out.println("   C: USER anonymous  /  S: 331 Password required");
        System.out.println("   C: PASS guest@...  /  S: 230 Login successful");
        System.out.println("3. PASV モード:");
        System.out.println("   C: PASV");
        System.out.println("   S: 227 Entering Passive Mode (127,0,0,1,196,10)");
        System.out.println("   -> データポート = 196*256+10 = 50186");
    }

    static int parsePasvPort(String pasvResponse) {
        var start = pasvResponse.indexOf('(');
        var end = pasvResponse.indexOf(')');
        if (start < 0 || end < 0) {
            throw new IllegalArgumentException(
                "PASV レスポンスの形式が不正: " + pasvResponse);
        }
        var parts = pasvResponse.substring(start + 1, end).split(",");
        int p1 = Integer.parseInt(parts[4].trim());
        int p2 = Integer.parseInt(parts[5].trim());
        return p1 * 256 + p2;
    }

    static String classifyResponseCode(int code) {
        return switch (code / 100) {
            case 1 -> "処理継続中(" + code + ")";
            case 2 -> "成功(" + code + ")";
            case 3 -> "追加情報要求(" + code + ")";
            case 4 -> "一時エラー(" + code + ")";
            case 5 -> "永続エラー(" + code + ")";
            default -> "不明(" + code + ")";
        };
    }

    public static void main(String[] args) {
        showFtpProtocol();

        var commands = new FtpCommand[]{
            new FtpCommand("USER", "anonymous"),
            new FtpCommand("PASS", "[email protected]"),
            new FtpCommand("PASV"),
            new FtpCommand("TYPE", "I"),
            new FtpCommand("STOR", "test.txt"),
            new FtpCommand("RETR", "test.txt"),
            new FtpCommand("QUIT"),
        };
        System.out.println("\n=== FTP コマンド ===");
        for (var cmd : commands) {
            System.out.println("  " + cmd.toCommandLine().trim());
        }

        var pasvResp = "227 Entering Passive Mode (192,168,1,1,196,10)";
        System.out.println("\nPASV ポート: " + parsePasvPort(pasvResp));

        System.out.println("\n=== 応答コード分類 ===");
        for (int code : new int[]{220, 230, 331, 530, 550}) {
            System.out.println(classifyResponseCode(code));
        }

        System.out.println("\n実務では Apache Commons Net の FTPClient を使用してください");
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

record で FTP コマンドを構造化し、switch 式で応答コードを分類できる。コードの見通しが改善される。

Java 17
// Java 17: record + switch 式で構造化
record FtpCommand(String name, String arg) {
    String toCommandLine() {
        return arg.isEmpty()
            ? name + "\r\n"
            : name + " " + arg + "\r\n";
    }
}
String category = switch (code / 100) {
    case 1 -> "処理継続中";
    case 2 -> "成功";
    case 3 -> "追加情報要求";
    case 4 -> "一時エラー";
    default -> "永続エラー";
};

Library Comparison

標準 API(Socket + 手動コマンド送信)FTP プロトコルの学習やトラブルシューティングの切り分けツール。PASV ポート計算、データチャネル接続、バイナリ転送の全てを自前で実装する必要がある。
Apache Commons Net(FTPClient)実務での FTP ファイル転送。接続管理、PASV モード、バイナリ/テキスト転送を一括で扱える。外部依存が追加される。プロトコルの詳細は隠蔽される。
JSch / Apache Mina SSHDSFTP(SSH ベースのファイル転送)が必要な場合。FTP とは別プロトコル。SSH の設定と鍵管理が必要になる。

注意点

この記事のコードは FTP プロトコルの仕組み理解が目的。実務では Apache Commons Net の FTPClient を使うこと

FTP の認証情報は平文で送信される。セキュリティが必要な場合は SFTP(SSH)または FTPS(FTP over TLS)を使うこと

PASV モードのポート計算は「上位バイト * 256 + 下位バイト」。この計算を間違えるとデータチャネルの接続に失敗する

アクティブモード(PORT コマンド)はクライアント側にサーバーからの接続を受け付けるポートが必要。NAT 環境では PASV モードを使う

FAQ

FTP と SFTP の違いは何ですか。

FTP は専用プロトコルで認証情報が平文で流れます。SFTP は SSH 上のファイル転送プロトコルで、通信が暗号化されます。セキュリティが必要な場合は SFTP を選んでください。

PASV モードとアクティブモードの違いは何ですか。

アクティブモードはサーバーからクライアントに接続しますが、NAT やファイアウォール越しでは使えません。PASV モードはクライアントからサーバーに接続するため、NAT 環境でも動作します。

PASV レスポンスのポート番号はどう計算しますか。

レスポンスの最後の 2 つの数値を使い、上位バイト * 256 + 下位バイト で計算します。例: (192,168,1,1,196,10) なら 196*256+10=50186 がデータポートです。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます