FTPサーバに接続 アクティブモードでアップロード


VB Tips And Sample(HOME)(VB.NET Sample インデックス)


VB2010で、FTPサーバにバイナリファイルをアクティブで送信するサンプル。
パッシブ(passive)のサンプルは、「Visual Basic .NET または Visual Basic 2005 を使用して FTP サイトにアクセスする方法
にあるが、意外にActiveのサンプルは完成品が見つかりませんでした。
というわけで、自PCにIIS+FTPを立てて、ファイヤーウォールを切って、ゴリゴリと作ってみました。

なぜ、FTP+アクティブのサンプルが少ないか
FTPの基本は、コマンド送信ソケットと、データ送信ソケットの二つのコネクションが必要。
メール送信は一本でOKなのにね。2005 ソケットでSMTPメール送信。基本を参照してください。
アクティブとパッシブの違いは、そのデータ送信の箇所。それは、
  • FTP+パッシブ=FTPツールを使うユーザは、クライアントで、サーバには接続しに行くだけ。
  • FTP+アクティブ=FTPツールを使うユーザは、クライアントなのだが、サーバからの接続を待つ必要があり、擬似サーバになる必要がある。
の赤字の箇所。
要はクライアントは当然ファイヤウォール内にいることが多いわけで、外部からのアクセスは大体厳しく制限されている。
なのに、FTPの穴を開けてしまうとせっかくのファイヤーウォールが・・・・ということで、アクティブが使えないユーザが多い事が予想される。
なので、「ま、パッシブでいいんじゃない?」というのが理由らしい。
実際、FTPサーバ側は両方平行運用大丈夫なところがほとんどなので。Microsoft FTP Serviceも両方平行運用可能。
FTP送信ログ
AcceptSocketで止まる(固まる・フリーズ)事があるようだが(実際管理人も?で止まったが)、
クライアント側でリッスン待ちにしたエンドポイント(IPアドレス+ポート)と、「PORT」するIPアドレスを、同じIPアドレスにしない為ではないかと思う。
少なくとも、管理人環境ではそうでした。

Imports System
Imports System.Net
Imports System.IO
Imports System.Text
Imports System.Net.Sockets


Public Class Form1
    Dim sock As Socket 'コマンド用ソケット
    Private ASCII As Encoding = Encoding.ASCII



    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click


        Dim ftps As IPEndPoint '送り先のエンドポイント(IPアドレス+ポート)
        Dim Myip As IPAddress '送信元のIPアドレス
        Dim Tcpl As TcpListener '送信元でFTPサーバからのアクセスを待つオブジェクト

        Dim Myport As Integer '送信元のポート

        sock = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        ftps = New IPEndPoint(IPAddress.Parse("192.168.11.27"), 21)
        sock.Connect(ftps) 'リモート ホストへの接続を確立します。

        SendCommand("USER anonymous")'ユーザ送信
        SendCommand("PASS info")'パスワード送信

        SendCommand("TYPE I")'バイナリモードに変換

        Myip = IPAddress.Parse("192.168.11.27")
        Tcpl = New TcpListener(Myip, 0) '指定したローカル IP アドレスとポート番号で受信接続の試行を待機する。0を指定して置けば任意の空いているポートを使ってくれる。
        Tcpl.Start()

        Dim separator As String() = Tcpl.LocalEndpoint.ToString().Split(":")

        Debug.Print(separator(1).ToString)'待っている事になったポート番号を取得

        Myport = CInt(separator(1))

       'このIPアドレス、このポートに接続してきてねとFTPサーバにコマンド送信
        SendCommand("PORT 192,168,11,27," & (Myport \ 256) & "," & Myport Mod 256)

       'これから/test3.jpg送るで、とコマンド送信
        SendCommand("STOR /test3.jpg")

        Dim Dsocket As Socket = Tcpl.AcceptSocket

         'アップロードするファイルを開く
        Dim fs As New System.IO.FileStream("C:\Users\admin\Documents\Visual Studio 2010\Projects\FTP\FTP\stest.jpg", System.IO.FileMode.Open, System.IO.FileAccess.Read)
        Dim buffer(fs.Length) As Byte
        Dim x As Integer = CInt(fs.Length)

        Dim readSize As Integer = fs.Read(buffer, 0, x)

        Dsocket.SendTo(buffer, Tcpl.LocalEndpoint)

        Dsocket.Close()

       '//終わりましたよとコマンド送信
        SendCommand("QUIT")

    End Sub
    
    'http://support.microsoft.com/kb/832679/jaから引用
    ' This is a function that is used to send a command to the FTP server that you are connected to.
    Private Sub SendCommand(ByVal sCommand As String)

        Dim bytes(255) As Byte
        Dim i As Integer

        sCommand = sCommand & ControlChars.CrLf
        Dim cmdbytes As Byte() = ASCII.GetBytes(sCommand)
        sock.Send(cmdbytes, cmdbytes.Length, 0)

        ' Get reply from the server.
        i = sock.Receive(bytes)
        Debug.WriteLine(Encoding.UTF8.GetString(bytes))


    End Sub

   

    
End Class


最近はPHPの仕事が多いですね。
FTPでソケットを使用し、しかもアクティブで送信というサンプルを追記。

----------------------------------------------------------------------
Hello
220 Microsoft FTP Service
331 Anonymous access allowed, send identity (e-mail name) as password.
230 User logged in.
192.168.11.200:35582
200 Type set to I.
200 PORT command successful.
125 Data connection already open; Transfer starting.
42611
226 Transfer complete. 
----------------------------------------------------------------------

print "Hello<br>";

//FTPアクティブでのファイル送信サンプル
//相手サーバ
$ftp_server='192.168.11.27';
$ftp_user_name='anonymous';
$ftp_user_pass='info';
//我がアパッチサーバIP
$MyserverIP="192.168.11.200";

//相手サーバ
$sock = fsockopen($ftp_server, 21);//ソケットの作成
$st=fgets($sock, 512);
print $st."<br>";

//ユーザ名
fputs($sock, "USER ".$ftp_user_name."\r\n");
$st=fgets($sock, 512);
print $st."<br>";

//パスワード
fputs($sock, "PASS ".$ftp_user_pass."\r\n");
$st=fgets($sock, 512);
print $st."<br>";


FTPBinarySend("stest.jpg","rtest.jpg");

//ソケットでFTP 送信
function FTPBinarySend($remotefile,$localfile){

global $sock;
global $MyserverIP;

// 新しいソケットを作成する  データ送受信用
//FTPは、コマンド用のソケットと、データ送受信用のソケット二つを区別して使う
$dcsock= socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($dcsock,SOL_SOCKET, SO_REUSEADDR, 1);
//ローカルサーバのIPに、待ちうけ用ソケットをバインド [0]を指定すると空いているポートになる
socket_bind($dcsock,$MyserverIP,0);
//FTPサーバが接続してくるのを待つ
socket_listen($dcsock);
//ポート番号を取得する
socket_getsockname($dcsock, $IP, $PORT);

print $IP.":".$PORT."\n<br>";

//print intval($PORT/256);
//exit;

fputs($sock, "TYPE I\r\n");//バイナリモードに変換
$st=fgets($sock, 512);
    print $st."<br>";

//このIPアドレス、このポートに接続してきてねとFTPサーバにコマンド送信
fputs($sock, "PORT 192,168,11,200,".intval($PORT/256).",".($PORT%256)."\r\n");
$st=fgets($sock, 512);
    print $st."<br>";
    
//これから$remotefile送るでとコマンド送信
    fputs($sock, "STOR /".$remotefile."\r\n");
$st=fgets($sock, 512);
    print $st."<br>"; 
    
    //FTPサーバがアクセスしてきたソケットを受け付ける
    $newsock = socket_accept($dcsock);
    
//ファイルをバイナリモードで開けてストリームに読み込む
    $fp = fopen($localfile, "rb");
$contents = '';
$contents = stream_get_contents($fp);
/*
while (!feof($fp)) {
  $contents .= fread($fp, 8192);
  
}*/
fclose($fp);
//ソケットに書き込む
$st = socket_write($newsock, $contents);
    $contents = '';
    print $st."<br>";
    
    //データ送受信用は切断
    socket_close($dcsock);
    socket_close($newsock);
}

//終わりましたよとコマンド送信
    fputs($sock, "QUIT\r\n");
$st=fgets($sock, 512);
    print $st."<br>";
    socket_close($sock);
    exit;



VB Tips And Sample(HOME)(VB.NET Sample インデックス)