| 前へ << FTP クライアントを作ってみよう (2) | FTP クライアントを作ってみよう (4) >> 次へ |
前項のプログラムのその部分を再掲します。
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: $!";
実はこんなことをしなくても、OS 側に勝手にポートを選ばせることができるのです。 前項のプログラムは、
bind(DATA_WAITING,sockaddr_in(ポート番号,INADDR_ANY))としていましたが、
bind(DATA_WAITING,sockaddr_in(0,INADDR_ANY))と、ポート番号を 0 に指定すると、 OS が勝手に空いているポートにソケットを割り当ててくれます。
すると、上記の部分は
43: # ソケット生成 44: socket(DATA_WAITING, PF_INET, SOCK_STREAM, 0) 45: || die "ソケットを生成できません。$!"; 46: 47: # ソケットオプション設定 48: setsockopt(DATA_WAITING, SOL_SOCKET, SO_REUSEADDR, 1) 49: || die "setsockoptでエラーが発生しました。$!"; 50: 51: # ソケットにアドレス(=名前)を割り付ける 52: bind(DATA_WAITING, pack_sockaddr_in(0, INADDR_ANY)); 53: || die "bind に失敗しました。$!"; 54: 55: # OSに、クライアントからの接続を受け入れるよう指示 56: listen(DATA_WAITING, SOMAXCONN) 57: || die "listen できません。$!";と短く書くことができます。 なお、PORT コマンドを送信するときにデータコネクションのポート番号を知らないといけないのですが、 OS にポート番号を選択させたので、何番のポートが割り当てられたのかわかりません。そのため、
71: $local_sock_addr = getsockname(DATA_WAITING); 72: ($data_port, $tmp) = unpack_sockaddr_in($local_sock_addr);という処理が必要になります。 これは、データコネクション用ソケットに対して getsockbyname・unpack_sockaddr_in を使い、 ソケットに割り当てられたポート番号を取得しているのです。
この部分を書き換えたサンプルプログラムを以下に示します。
1: #!/usr/local/bin/perl -w
2:
3: # $Id: ftp-client-2.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: || die "$hostname は存在しないホストです。$!";
19:
20: # ポート番号と IP アドレスをまとめて構造体に変換
21: $sock_addr = pack_sockaddr_in($port, $iaddr);
22:
23: # ソケット生成
24: socket(COMMAND, PF_INET, SOCK_STREAM, 0)
25: || die "ソケットを生成できません。$!";
26:
27: # 指定のホストの指定のポートに接続
28: connect(COMMAND, $sock_addr)
29: || die "$hostname のポート $port に接続できません。$!";
30:
31: # ファイルハンドル COMMAND をバッファリングしない
32: select(COMMAND); $|=1; select(STDOUT);
33:
34:
35: #---------- ユーザ認証 ---------------------------------
36:
37: print COMMAND "USER $username\n";
38: print COMMAND "PASS $password\n";
39:
40:
41: #---------- データ用コネクションを作成 -----------------
42:
43: # ソケット生成
44: socket(DATA_WAITING, PF_INET, SOCK_STREAM, 0)
45: || die "ソケットを生成できません。$!";
46:
47: # ソケットオプション設定
48: setsockopt(DATA_WAITING, SOL_SOCKET, SO_REUSEADDR, 1)
49: || die "setsockoptでエラーが発生しました。$!";
50:
51: # ソケットにアドレス(=名前)を割り付ける
52: bind(DATA_WAITING, pack_sockaddr_in(0, INADDR_ANY));
53: || die "bind に失敗しました。$!";
54:
55: # OSに、クライアントからの接続を受け入れるよう指示
56: listen(DATA_WAITING, SOMAXCONN)
57: || die "listen できません。$!";
58:
59:
60: #---------- ローカルホストの IP アドレスを取得 ---------------
61:
62: $local_sock_addr = getsockname(COMMAND);
63: ($tmp, $local_addr) = unpack_sockaddr_in($local_sock_addr);
64: $local_ip = inet_ntoa($local_addr);
65: # IPアドレス aaa.bbb.ccc.ddd を aaa,bbb,ccc,dddという形式に
66: $local_ip =~ s/\./,/g;
67:
68:
69: #---------- データコネクションのポート番号を取得 -------------
70:
71: $local_sock_addr = getsockname(DATA_WAITING);
72: ($data_port, $tmp) = unpack_sockaddr_in($local_sock_addr);
73:
74:
75: #---------- PORT・LIST コマンドを送信 -------------------------
76:
77: # FTP サーバに、データコネクションの IP アドレスとポートの情報を渡す
78: printf COMMAND "PORT $local_ip,%d,%d\n"
79: ,$data_port/256,$data_port%256;
80:
81: # ファイル一覧を送るよう要求
82: print COMMAND "LIST\n";
83:
84:
85: #---------- データコネクションを使って、データ受信 -----------
86:
87: # FTP サーバ側からの接続を待つ
88: accept(DATA, DATA_WAITING);
89:
90: # 送られてくるデータの内容を表示
91: while (<DATA>){
92: print $_;
93: }
94:
95:
96: #---------- 終了処理 ----------------------------------------
97:
98: # データ用コネクションclose
99: close(DATA);
100: close(DATA_WAITING);
101:
102: # QUITを送ってセッション終了
103: print COMMAND "QUIT\n";
104: close(COMMAND);
FTP クライアント FTP サーバ
USER ------ コマンド用コネクション ------>
PASS ------ コマンド用コネクション ------>
PORT ------ コマンド用コネクション ------>
LIST ------ コマンド用コネクション ------>
<----- データ用コネクション --------- ファイル一覧送信
QUIT ------ コマンド用コネクション ------>
となります。これは「Active モード」というもので、
データコネクションの確立の際、
「FTP サーバ側が能動的に FTP クライアント側に接続する」
という動作になります。
一方、「Passive モード」というものがあります。このモードでは、 「FTP サーバ側が受け手となり、FTP クライアントからの接続を待つ」 という動作になります。つまり、FTP クライアントは、 bind・listen・accept などのサーバ的な動作をする必要がなくなるのです。
FTP クライアントがファイアーウォールの内側にいて、 外部から接続できない場合、Active モードではうまくいかない場合があります。 そういうときには Passive モードを使います。ftp コマンドでは、
ftp> passive Passive mode on.とすると、それ以降 Passive モードになります。もう一度
ftp> passive Passive mode off.とすると、Active モードに戻ります。
FTP クライアント FTP サーバ
USER ------ コマンド用コネクション ------>
PASS ------ コマンド用コネクション ------>
PASV ------ コマンド用コネクション ------>
LIST ------ コマンド用コネクション ------>
connect ------ データ用コネクション -------->
<----- データ用コネクション --------- ファイル一覧送信
QUIT ------ コマンド用コネクション ------>
となります。Active モードとの違いは、データコネクションの確立方式だけです。
再度、ftp に -d オプションを付けて、プロトコルの流れを見てみましょう。
ftp> passive Passive mode on.passive で Passive モードに移行しても、FTP プロトコルとしては 何も送受信しません。では、ls でファイル一覧を表示しましょう。
ftp> ls ---> PASV 227 Entering Passive Mode (10,0,0,1,156,67) ---> LISTコマンドコネクションで FTP クライアントが PASV を送信します。 すると、サーバからは
Entering Passive Mode (10,0,0,1,156,67)というレスポンスが返ってきます。これは、FTP サーバが
10.0.0.1 というホストのポート 40003 (156×256+67) で データ用コネクションの接続を受け入れ中であるということです。 それを受けて、FTP クライアントは 10.0.0.1 のポート 40003 に接続します。 すると、ファイル一覧が FTP サーバ側から送信されてきます。
では、続けて ファイルを get しましょう。
ftp> get README.TXT ---> PASV 227 Entering Passive Mode (10,0,0,1,156,75) ---> RETR README.TXTget をタイプすると、再度 PASV を送信し、ポート番号を取得します。 つまり ls・get・put などをタイプするたびに、 FTP クライアントは毎回 PASV を送信し、そのレスポンスとして、 IP アドレスとポート番号を取得します。
指定された IP アドレス + ポート番号に接続すると、データの送受信が始まります。 LIST (ls) ならファイル一覧が FTP サーバから送られてきます。 RETR (get) ならファイルの内容です。 STOR (put) なら、こちらからファイルの内容を送信しないといけません。
本来は、ここで Passive モードを利用した FTP クライアントを 例として上げたいところですが、いろいろと事情があり、 それはできません。次節 で FTP プロトコルを 解説した後、Passive モードに対応した高機能版 FTP プロトコルを作成します。
| 前へ << FTP クライアントを作ってみよう (2) | FTP クライアントを作ってみよう (4) >> 次へ |
ご意見・ご指摘は Twitter: @68user までお願いします。