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