概要
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 モードの接続失敗を調査する
コード例
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 を使用してください");
}
}Version Coverage
record で FTP コマンドを構造化し、switch 式で応答コードを分類できる。コードの見通しが改善される。
// 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
注意点
この記事のコードは FTP プロトコルの仕組み理解が目的。実務では Apache Commons Net の FTPClient を使うこと
FTP の認証情報は平文で送信される。セキュリティが必要な場合は SFTP(SSH)または FTPS(FTP over TLS)を使うこと
PASV モードのポート計算は「上位バイト * 256 + 下位バイト」。この計算を間違えるとデータチャネルの接続に失敗する
アクティブモード(PORT コマンド)はクライアント側にサーバーからの接続を受け付けるポートが必要。NAT 環境では PASV モードを使う
FAQ
FTP は専用プロトコルで認証情報が平文で流れます。SFTP は SSH 上のファイル転送プロトコルで、通信が暗号化されます。セキュリティが必要な場合は SFTP を選んでください。
アクティブモードはサーバーからクライアントに接続しますが、NAT やファイアウォール越しでは使えません。PASV モードはクライアントからサーバーに接続するため、NAT 環境でも動作します。
レスポンスの最後の 2 つの数値を使い、上位バイト * 256 + 下位バイト で計算します。例: (192,168,1,1,196,10) なら 196*256+10=50186 がデータポートです。