前へ << FTP クライアントを作ってみよう (1) | FTP クライアントを作ってみよう (3) >> 次へ |
流れとしては
複雑と言えば複雑なのですが、コマンド用コネクションの流れは HTTP クライアントや POP3 クライアントでやったことと同じですし、 データ用コネクションは echo サーバのところでやったように ポートを見張ればいいのです。
1: #!/usr/local/bin/perl -w 2: 3: # $Id: ftp-client.pl,v 1.2 2002/02/05 17:53:09 68user Exp $ 4: 5: use Socket; # Socketモジュールを使う 6: 7: $hostname = 'localhost'; 8: $username = 'zxr400'; 9: $password = ''; 10: 11: #---------- コマンドコネクションを作成 ----------------- 12: 13: # FTP プロトコルを使う 14: $port = getservbyname('ftp', 'tcp'); 15: 16: # ホスト名を、IPアドレスの構造体に変換 17: $iaddr = inet_aton($hostname) 18: or die "$hostnameは存在しないホストです。\n"; 19: 20: # ポート番号と IP アドレスをまとめて構造体に変換 21: $sock_addr = pack_sockaddr_in($port, $iaddr); 22: 23: # ソケット生成 24: socket(COMMAND, PF_INET, SOCK_STREAM, 0) 25: or die "ソケットを生成できません。\n"; 26: 27: # 指定のホストの指定のポートに接続 28: connect(COMMAND, $sock_addr) 29: or die "$hostname のポート $port に接続できません。\n"; 30: 31: # ファイルハンドル COMMAND をバッファリングしない 32: select(COMMAND); $|=1; select(STDOUT); 33: 34: 35: #---------- ユーザ認証 --------------------------------- 36: 37: print COMMAND "USER $username\r\n"; 38: print COMMAND "PASS $password\r\n"; 39: 40: 41: #---------- データ用コネクションを作成 ----------------- 42: 43: # データコネクション用のソケット生成・アドレス割り付け 44: for ( $data_port=5000 ; $data_port<65536 ; $data_port++ ){ 45: 46: # ソケット生成 47: socket(DATA_WAITING, PF_INET, SOCK_STREAM, 0) 48: or die "ソケットを生成できません。\n"; 49: 50: # ソケットオプション設定 51: setsockopt(DATA_WAITING, SOL_SOCKET, SO_REUSEADDR, 1) 52: or die "setsockoptでエラーが発生しました。\n"; 53: 54: # ソケットにアドレス(=名前)を割り付ける 55: if ( bind(DATA_WAITING, pack_sockaddr_in($data_port, INADDR_ANY)) ){ 56: # 成功したら forループを抜ける 57: last; 58: } else { 59: # 失敗したら次のポートのbindを試みる 60: print "ポート$data_portのbindに失敗しました。\n"; 61: 62: # ポート65535まで試してもダメなら終了 63: if ( $data_port == 65535 ){ 64: die "終了します。\n"; 65: } 66: } 67: } 68: # OSに、クライアントからの接続を受け入れるよう指示 69: listen(DATA_WAITING, SOMAXCONN) 70: or die "listen: $!"; 71: 72: 73: #---------- ローカルホストの IP アドレスを取得 --------------- 74: 75: $local_sock_addr = getsockname(COMMAND); 76: ($local_port, $local_addr) = unpack_sockaddr_in($local_sock_addr); 77: $local_ip = inet_ntoa($local_addr); 78: # IPアドレス aaa.bbb.ccc.ddd を aaa,bbb,ccc,dddという形式に 79: $local_ip =~ s/\./,/g; 80: 81: 82: #---------- PORT・LIST コマンドを送信 ------------------------- 83: 84: # FTP サーバに、データコネクションの IP アドレスとポートの情報を渡す 85: printf COMMAND "PORT $local_ip,%d,%d\r\n" 86: ,$data_port/256,$data_port%256; 87: 88: # ファイル一覧を送るよう要求 89: print COMMAND "LIST\r\n"; 90: 91: 92: #---------- データコネクションを使って、データ受信 ----------- 93: 94: # FTP サーバ側からの接続を待つ 95: accept(DATA, DATA_WAITING); 96: 97: # 送られてくるデータの内容を表示 98: while (<DATA>){ 99: print $_; 100: } 101: 102: 103: #---------- 終了処理 ---------------------------------------- 104: 105: # データ用コネクションclose 106: close(DATA); 107: close(DATA_WAITING); 108: 109: # QUITを送ってセッション終了 110: print COMMAND "QUIT\r\n"; 111: close(COMMAND);とりあえず大前提として、FTP サーバにログインするためには、 相手先のホスト名、ユーザ名、パスワードが必要になります。 誰かのアカウント情報を例とすることはできませんので、 ここは anonymous FTP サーバにログインすることにします。
7: $hostname = 'localhost'; 8: $username = 'zxr400'; 9: $password = '';ユーザ名を anonymous (あるいは ftp)、パスワードにメールアドレスを 指定することで、自動的に anonymous FTP にログインできることは ご存知だと思います。
13: # FTP プロトコルを使う 14: $port = getservbyname('ftp', 'tcp'); 15: 16: # ホスト名を、IPアドレスの構造体に変換 17: $iaddr = inet_aton($hostname) 18: or die "$hostnameは存在しないホストです。\n"; 19: 20: # ポート番号と IP アドレスをまとめて構造体に変換 21: $sock_addr = pack_sockaddr_in($port, $iaddr); 22: 23: # ソケット生成 24: socket(COMMAND, PF_INET, SOCK_STREAM, 0) 25: or die "ソケットを生成できません。\n"; 26: 27: # 指定のホストの指定のポートに接続 28: connect(COMMAND, $sock_addr) 29: or die "$hostname のポート $port に接続できません。\n"; 30: 31: # ファイルハンドル COMMAND をバッファリングしない 32: select(COMMAND); $|=1; select(STDOUT);HTTP クライアントとほどんど一緒です。唯一違うのは
14: $port = getservbyname('ftp', 'tcp');で、http が ftp に変わっただけですね。
念のため、もう一度軽く説明しておくと、
37: print COMMAND "USER $username\r\n"; 38: print COMMAND "PASS $password\r\n";USER・PASS を送っているだけですね。サンプルということで、 ユーザ認証が成功したかどうかはチェックしていません。 パスワードを間違わないように。
43: # データコネクション用のソケット生成・アドレス割り付け 44: for ( $data_port=5000 ; $data_port<65536 ; $data_port++ ){ 45: 46: # ソケット生成 47: socket(DATA_WAITING, PF_INET, SOCK_STREAM, 0) 48: or die "ソケットを生成できません。\n"; 49: 50: # ソケットオプション設定 51: setsockopt(DATA_WAITING, SOL_SOCKET, SO_REUSEADDR, 1) 52: or die "setsockoptでエラーが発生しました。\n"; 53: 54: # ソケットにアドレス(=名前)を割り付ける 55: if ( bind(DATA_WAITING, pack_sockaddr_in($data_port, INADDR_ANY)) ){ 56: # 成功したら forループを抜ける 57: last; 58: } else { 59: # 失敗したら次のポートのbindを試みる 60: print "ポート$data_portのbindに失敗しました。\n"; 61: 62: # ポート65535まで試してもダメなら終了 63: if ( $data_port == 65535 ){ 64: die "終了します。\n"; 65: } 66: } 67: }まずポート番号 5000 を bind して、もし失敗したらポート 5001 番を試すことに します。それでもダメなら 5002、5003 …と順に試していき、ポート 65535 番まで 全て失敗したら残念ながら終了します。
69: listen(DATA_WAITING, SOMAXCONN) 70: or die "listen: $!";データコネクションを listen します。 ここに処理が来たということは、データコネクションの bind に成功したということです。 listen を実行した時点で、クライアントからの接続は受け入れることができますが、 実際に FTP サーバ側からの接続が来るのは、PORT・LIST コマンドを送った後です。
→ 関数説明: listen
PORT 10,0,0,1,19,136というような PORT コマンドを送るわけですが、 IP アドレス(この場合は10.0.0.1)と データコネクションの PORT 番号 (この場合は19×256+136=5000) の情報が必要になります。 既に bind のループで $data_port にポート番号が入っていますので、 あとは IP アドレスを取得しなければいけません。
75: $local_sock_addr = getsockname(COMMAND); 76: ($local_port, $local_addr) = unpack_sockaddr_in($local_sock_addr); 77: $local_ip = inet_ntoa($local_addr);順に見ていくと、
→ 関数説明: getsockname
→ 関数説明: unpack_sockaddr_in
79: $local_ip =~ s/\./,/g;$local_ip は 10.0.0.1 などのような IP アドレス表記になっていますが、 PORT コマンドに渡す際は「.」(ドット)ではなく「,」(カンマ)で区切らなくてはいけないので、 s/\./,/gで変換します。最終的には、$local_ip は 10,0,0,1 という文字列になるわけです。
85: printf COMMAND "PORT $local_ip,%d,%d\r\n" 86: ,$data_port/256,$data_port%256;
89: print COMMAND "LIST\r\n";PORT コマンド、LIST コマンドを FTP サーバに送信します。 先ほど作った $local_ip (IPアドレス) と、 $data_port (ポート番号÷256,ポート番号÷256の余り) を 組み合わせて、ポート番号の情報を伝えます。例えば FTP クライアントが 192.168.1.1 で 動いており、データ用コネクションを張るためにポート 12345 番を listen しているなら、
PORT 192,168,1,1,48,57という文字列を送ります (12345=48×256+57)。
95: accept(DATA, DATA_WAITING);accept します。既に PORT・LIST コマンドは送ったので、既に FTP サーバ側から接続要求が来ているかもしれません (まだ来ていないかもしれませんが、そのうち接続しにくるはずです)。
新しくソケット DATA が作られ、今後データコネクションはソケット DATA を 通してデータのやりとりをします。ソケット DATA_WAITING は、 さらに次の接続が来た場合のためのものですが、FTP プロトコルの 仕様としては新たな接続があることはあり得ません。
→ 関数説明: accept
98: while (<DATA>){ 99: print $_; 100: }データコネクション用のソケットからデータを読み込み、内容をそのまま表示します。 先ほどコマンドコネクションで FTP サーバに LIST コマンドを送りましたので、 送られてくるのはファイルの一覧です。
106: close(DATA); 107: close(DATA_WAITING); 108: 109: # QUITを送ってセッション終了 110: print COMMAND "QUIT\r\n"; 111: close(COMMAND);while ループが終了したら、データコネクションは FTP サーバ側から切断されますが、 念のためこちらでも close(DATA) しておきましょう。さらにソケット DATA_WAITING も クローズします。
最後に残ったコマンド用コネクションは、FTP サーバに QUIT コマンドを送り、 ソケットをクローズします。
前へ << FTP クライアントを作ってみよう (1) | FTP クライアントを作ってみよう (3) >> 次へ |
ご意見・ご指摘は Twitter: @68user までお願いします。