| 前へ << 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 までお願いします。