HOME


PHPのちょっとしたTIPS

Last Modified : 2003/03/18 (Tue)
No.4 FTPクライアント - fsockopen() fputs() fgets() fread() fclose()

PHPでFTPクライアントを作ったので簡単な(PHPではなくFTPの)解説。
PHPはコンパイルに--enable-ftpというオプションを付けると、
FTP関数が使用可能になりますが、ここでは自力でやってみます。

FTPはHTTP、SMTP、POP3なんかと違って少々面倒です。
というのも、FTPでは2種類の接続を使用するからです。
一つはコントロール接続といって通常21番のポートを使用して
認証を始めサーバとのコマンドのやり取りに使用します。
もう一つはデータ接続で、ファイルリストやファイルなどの
データ転送に使われます。データ転送に関しては、
クライアント側がポートListenするActiveモードとサーバ側
がポートをListenするPassiveモードがあります。
クライアント側がポートをListenするのは面倒そうなので、
ここではサーバにListenしてもらうPassiveモードを使います。

では、いつものようにTelnetを使用してFTPをしてみましょう。
前述のように複数のポートを使用するので、ターミナルも複数
使用します。(改行は<CRLF>です)
$ telnet localhost 21
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 ProFTPD 1.2.1 Server (ProFTPD) [localhost]
USER tomo
331 Password required for tomo.
PASS ******
230 User tomo logged in.
PWD
257 "/" is current directory.
QUIT
221 Goodbye.
Connection closed by foreign host.
PWDコマンドでリモートのカレントディレクトリが表示されます。
ここまではコントロール接続のみで確認できます。
次はデータ接続を使いPassiveモードでリモートのファイルリストを取得します。
$ telnet localhost 21
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 ProFTPD 1.2.1 Server (ProFTPD) [localhost]
USER tomo
331 Password required for tomo.
PASS ******
230 User tomo logged in.
PASV
227 Entering Passive Mode (127,0,0,1,199,32).
LIST
-----------------------------------------------------------------------------
ここでもう一つターミナルを開きます(データポートは199*256 + 32 = 50976です)
$ telnet 127.0.0.1 50976
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
-rw-------   1 tomo  tomo      2508 Jul  5 08:52 .bash_history
-rw-------   1 tomo  tomo       138 Jun  5 09:34 .bashrc
-rw-------   1 tomo  tomo       372 May 23 08:21 .cshrc
-rw-------   1 tomo  tomo       607 May 22 14:21 .login
-rw-------   1 tomo  tomo       812 May 22 14:21 .profile
drwx-----x  29 tomo  tomo      1024 Jul  2 04:00 public_html
Connection closed by foreign host.
(向こうから切断される)
-----------------------------------------------------------------------------
150 Opening ASCII mode data connection for file list
226 Transfer complete.
QUIT
221 Goodbye.
Connection closed by foreign host.
意外と簡単ですね(^-^)
PASVコマンドの後のカンマ区切りの6つ数字は、最初の4つはサーバのIPアドレスですが、
後の2つはデータ接続用のポート番号を意味していて、次の計算で算出できます。
2つの内最初のを A 、後のを B とすると

データポート = A*256 + B

となります。
ファイルのダウンロードはLISTコマンドの代わりに、
RETR ファイル名
を使用することで同様にできます。
(Telnetでのテストではテキストファイルにしておかないと中身が見れません)
ではもう一つだけ例を出します。
今度はSTORコマンドを使ってテキストファイルのアップロードを行ないます。
$ telnet localhost 21
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 ProFTPD 1.2.1 Server (ProFTPD) [localhost]
USER tomo
331 Password required for tomo.
PASS ******
230 User tomo logged in.
CWD test
250 CWD command successful.
PASV
227 Entering Passive Mode (127,0,0,1,199,90).
STOR ftp.txt
-----------------------------------------------------------------------------
ここでもう一つターミナルを開きます(データポートは199*256 + 90 = 51034です)
$ telnet 127.0.0.1 51034
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
FTP
test
TEXT
(こちらから切断)
-----------------------------------------------------------------------------
150 Opening ASCII mode data connection for ftp.txt
226 Transfer complete.
QUIT
221 Goodbye.
Connection closed by foreign host.
CWDコマンドで test というディレクトリに移動しました。
STOR ftp.txtでリモート側にftp.txtという名前のファイルを指定します。
データ接続で適当なテキストをキーボードから入力した後、(強制)切断します。
これでリモート側に、入力した内容がtest/ftp.txtという名前でアップロード
されたことになります。

その他のコマンドはRFC959とかを参照してください。

というわけで作ったのがこれです。
サーバとのやり取りに関してはHTTPクライアントの時と基本は同じです。
fsockopen() で接続
fputs() でコマンド送信
fgets() or fread() で応答受信
fclose() で切断
改行は<CRLF>だからPHPでは \r\n
という感じです。
一番最初の例を簡単にPHPで書いてみます。
<?php
$sock = fsockopen("localhost", 21);
fgets($sock, 512);
fputs($fp, "USER tomo\r\n");
fgets($sock, 512);
fputs($fp, "PASS ******\r\n");
fgets($sock, 512);
fputs($fp, "PWD\r\n");
fgets($sock, 512);
fputs($fp, "QUIT\r\n");
fgets($sock, 512);
fclose($sock);
?>
ちょっと手抜きでしょうか、、、(^^;
エラーの場合の処理やFTPサーバからの応答が複数行の時の処理などを入れれば
良くなりますね。

サーバからの応答の最初の3文字は数字で応答コードと言われるものです。
それぞれの桁が意味を持っていますが、最初の数字が1, 2, 3なら正常で、
4, 5ならエラーであると覚えておけば良いかな(いい加減?)と思います。

応答が複数行になる場合は応答コードの直後に"-"(ハイフン)が来ます。
最後の行の場合だけ" "(スペース)です。