vb@rchiv
VB Classic
VB.NET
ADO.NET
VBA
C#

https://www.vbarchiv.net
Rubrik: Internet/Netzwerk   |   VB-Versionen: VB5, VB601.11.01
FTP - File Transfer Protokoll, Teil 1

Das Wort "FTP" ist die Abkürzung für "File Transfer Protokoll" und bedeutet auf Deutsch so viel wie "Datei-Übertragsungs-Protokoll". Dieses Protokoll wird verwendet, um Dateien und Verzeichnisstrukturen von einem Rechner auf den anderen zu übertragen. Dabei ist festgelegt, dass ein Rechner der Empfänger (Client) und der andere Rechner der Sender (Server) ist...

Autor:  Dieter Otter / Matthias VolkBewertung:  Views:  48.141 

Das Wort "FTP" ist die Abkürzung für "File Transfer Protokoll" und bedeutet auf Deutsch so viel wie "Datei-Übertragsungs-Protokoll". Dieses Protokoll wird verwendet, um Dateien und Verzeichnisstrukturen von einem Rechner auf den anderen zu übertragen. Dabei ist festgelegt, dass ein Rechner der Empfänger (Client) und der andere Rechner der Sender (Server) ist. Eines der bekanntesten FTP-Programme ist ohne Zweifel "WS FTP" der Firma Ipswitch, Inc. Hierbei handelt es sich um einen FTP-Client. In unserem Workshop möchten wir Ihnen zeigen, wie ein solcher FTP-Client aufgebaut ist und vor allen Dingen, wie man einen eigenen FTP-Client programmieren kann.

Arbeitsweise eines FTP-Clients

Die Vorgehensweise eines FTP-Clients ist eigentlich ziemlich einfach. Zunächst wird versucht eine Verbindung zum Server herzustellen, wobei man eine bestimmte Reaktion (Antwort) zurückerhält. Diese erste Reaktion ist die Willkommensnachricht des Servers. Nach der Willkommensnachricht wird dem Server der Username mitgeteilt, sofern dieser es verlangt. Als Antwort erhält man vom Server, dass die Anmeldung (und somit der Benutzername) erfolgreich war, der Benutzername ungültig ist oder zusätzlich für die Anmeldung ein Passwort benötigt wird. Und genau nach diesem Schema erfolgt der gesamte Programmablauf. Es wird eine Anfrage gestartet und auf eine Antwort vom Server gewartet. Auf diese Antwort muss natürlich immer entsprechend reagiert werden. Das ganze wiederholt sich solange, bis beide "Parteien" (Server und Client) alle gewünschten Informationen erhalten haben.

Für die Übertragung von Dateien und Verzeichnisstrukturen muss der Client einen neuen Port öffnen und diesen dem Server mitteilen. Der Server startet daraufhin eine Verbindung zu dem angegebenen Port und kann die gewünschte Datei senden (Download-Aktion) bzw. empfangen (Upload-Aktion). Der Port selbst wird auf Grundlage einer "Ort"-Berechnung erstellt, worauf wir aber später in diesem Workshop noch näher eingehen werden.

Die Problematik

Das einzige Problem, das sich bei diesem Konzept ergibt, ist, dass der Client nicht schneller arbeiten darf, als der Server die entsprechenden Antworten und Bestätigungen zurückschickt. Wird z.B. der Username an den Server übermittelt, muss unbedingt die Antwort des Server abgewartet werden - auch wenn bereits im Vorfeld bekannt ist, ob und welches Passwort vom Server verlangt wird. Hält man dieses Prozedere nicht ein, wird man sehr oft feststellen, dass an den Server übermittelte Kommandos von diesem nicht akzeptiert werden.

Eine weitere Problematik, welche sich im Laufe des Workshop-Projekts herausgestellt ist, dass beim Abrufen oder Übertragen von Daten der Server bereits als Antwort "Übertragung beendet" gemeldet hat, obwohl das letzte Datenpaket noch gar nicht vollständig über den Datenport eingetroffen ist. Aus diesem Grund darf ein Datenport nicht sofort nach Erhalt der obigen Meldung geschlossen werden.

Ohne dem WinSock-Control geht gar nichts!

Für die Realisierung der FTP-Schnittstelle muss auf das WinSock-Control zurückgegriffen werden, da Visual Basic hierfür keine "hauseigenen" Befehle zur Verfügung stellt. Der Projekt selbst beruht auf einen komplexen Source-Code, so dass es nicht möglich ist, auf alle Funktionen und Prozeduren ausführlich einzugehen. In den nachfolgenden Kapiteln erfahren Sie, wie eine Form als Träger des Sourcecodes benutzt wird, um ein Objekt zu erstellen, das fast alle Funktionen eines modernen FTP-Clients unterstützt.

Das WinSock-Control und seine (drei) Aufgaben:
Die Form selbst beinhaltet drei WinSock-Steuerelemente:

CmdSock: Zuständig für das Senden und Empfangen der Server-Kommandos
DataSock: Zuständig für das Empfangen von Verzeichnisstrukturen
DownSock: Zuständig für das Senden und Empfangen von Dateien

Die benötigten Deklarationen

Fügen Sie nachfolgenden Code-Abschnitt in den Allgemein-Teil der Form ein. Es handelt sich hierbei um Konstanten-, Typen- und Variablen-Deklarationen. Die beiden API-Funktionen dienen lediglich dazu, einen Timeout festzulegen und das Clientfenster (die Form also) immer im Vordergrund zu halten. Anhand der Deklarationen ist bereits ersichtlich, über welche Funktionen der FTP-Client alles verfügen wird.

Option Explicit
 
' API-Deklarationen
Private Declare Function GetTickCount Lib "kernel32" () As Long
 
Private Declare Function SetWindowPos Lib "user32.dll" ( _
  ByVal hwnd As Long, _
  ByVal hWndInsertAfter As Long, _
  ByVal X As Long, _
  ByVal Y As Long, _
  ByVal cx As Long, _
  ByVal cy As Long, _
  ByVal wFlags As Long) As Long
 
 
' Server-Antworten
' ================
 
Private Enum ServerMessages
  ' Aufforderung für Benutzernamen
  ConnectMsg = 220
 
  ' Benutzername ist OK
  UserNameOK = 331
 
  ' Benutzername wurde nicht akzeptiert
  UserNameFail = 421
 
  ' Passwort ist OK
  PasswordOK = 230
 
  ' Passwort wurde nicht akzeptiert
  PasswordFail = 421
 
  ' Passwort für den anonymen Zugang wurde nicht akzeptiert
  UserCantLogin = 530
 
  ' Nachricht enthält das aktuelle Verzeichnis
  CurrentDirectory = 257
 
  ' Kommando ist OK
  CommandOk = 200
 
  ' Kommando wurde nicht ausgeführt
  CommandFail = 500
 
  ' Datenübertragung wird gestartet
  TransferStart = 150
 
  ' Datenübertragung beendet
  TransferComplete = 226
 
  ' Verbindung zur Datenleitung ist fehlerhaft
  DataConnectionError = 425
 
  ' Verbindung zur Datenleitung wurde geschlossen
  DataConnectionClosed = 426
 
  ' Wechseln des Verzeichnisses war erfolgreich
  ChangeDirOK = 250
 
  ' Server-Timeout, Verbindung wird geschlossen
  ConnectionTimeOut = 421
 
  ' Dateigröße konnte ermittelt werden
  FileSizeOK = 213
 
  ' Kommando ist nicht verfügbar
  CommandNotImplemented = 504
 
  ' Zugriff verweigert
  PermissionDenied = 501
 
  ' Fortsetzen möglich
  ResumingSupportet = 350
End Enum
 
' Client-Kommandos
' ================
 
' Sendet den Benutzernamen
Private Const User = "USER "
 
' Sendet das Passowort
Private Const Password = "PASS "
 
' Beendet eine Sitzung
Private Const QuitSession = "QUIT"
 
' Aufforderung für Benachrichtigung über das aktuelle
' Verzeichnis
Private Const CurrentDir = "PWD"
 
' Aufforderung für alle Objekte im Verzeichnis über
' die Datenleitung
Private Const ListFolder = "LIST"
 
' Daten sollen im Ascii-Format übermittelt werden
Private Const OvermitAscii = "TYPE A"
 
' Setzt einen neuen Port für die Datenleitung
Private Const SetPort = "PORT "
 
' Wechselt ein Verzeichnis
Private Const ChangeFolder = "CWD "
 
' Wechselt in den übergeordneten Ordner
Private Const ChangeFolderUP = "CDUP"
 
' Aufforderung zum Ermitteln einer Dateigröße
Private Const FileSize = "SIZE "
 
' Fortsetzten eines UP-/Downloads
Private Const ResumeTransfere = "REST "
 
' Daten sollen im Binär-Format übermittelt werden
Private Const OvermitBinary = "TYPE I"
 
' Datei downloaden
Private Const Download = "RETR "
 
' Up-/Download abbrechen
Private Const AbortTransfere = "ABOR"
 
' Datei uploaden
Private Const Upload = "STOR "
 
 
' Port Berechnungs-Konstanten
' ===========================
Private Const PortStartValue = 8
Private Const PortValueStartValue = 80
 
 
' TimeOut Konstanten
' ==================
 
' Maximale Zeit für einen Verbindungsversuch
Private Const ConnectTimeOut = 30
 
' Maximale Zeit für einen Kommando-Aufruf
Private Const ReplyTimeOut = 30
 
 
' Größe der Datenpakete für das Versenden
Private Const SendBufferLenght = 1024
 
 
' Globale Variablen zum Zwischenspeichern von Informationen
' =========================================================
 
' Setzt ob das Fenster versteckt oder entladen werden soll
' (True = entladen)
Dim DoClose As Boolean
 
' letzte übermittelte Wert des Servers
Dim LastServerValue As String
 
' letzte übermittelte Nachricht des Servers
Dim LastServerCmd As ServerMessages
 
' Puffer für das Listen eines Verzeichnisses
Dim Data As String
 
' Größe der Download-Datei in Bytes
Dim TotalBytes As Long
 
' bisher übertragene Anzahl Bytes
Dim OvermittedBytes As Long
 
' Variable für eine freie Dateinummer
Dim FFile As Long
 
' Wenn TRUE, wird die Dateiübertragung abgebrochen
Dim TAbort As Boolean
 
' Datenpaket an den Server gesendet? (Upload)
Dim PacketSend As Boolean
 

Verbindung zum Server herstellen

Bevor irgendeine Funktion, wie das Uploaden oder Downloaden einer Datei ausgeführt werden kann, muss natürlich erst einmal eine Verbindung zum FTP-Server hergestellt werden. Benötigt wird der Servername (RemoteHost) und der Port (Anschluss). Über die Connect-Methode wird der Verbindungsaufbau gestartet. Nun muss - ganz wichtig - zunächst geprüft werden, ob eine Verbindung zustande gekommen ist. Hierzu wird innerhalb einer "Warteschleife" die State-Eigenschaft abgefragt. Erhält man innerhalb der festgesetzten TimeOut-Zeit keine Antwort, wird die Schleife aufgrund einer Zeit-Überschreitung beendet.

' Verbindung zum Server herstellen
Friend Function Connect(ByVal RemoteHost As String, _
  ByVal Port As Long, ByVal UserName As String, _
  ByVal Pass As String) As Boolean
 
  Dim TimeOut As Long
 
  ' Zeit-Limit (TimeOut)
  TimeOut = (GetTickCount / 1000) + ConnectTimeOut
 
  ' Verbinden
  With CmdSock
    .Close
    .RemoteHost = RemoteHost
    .RemotePort = Port
    .Connect
  End With
 
  ' Warten bis verbunden, oder Timeout eingetreten ist
  Do
    DoEvents
    If TimeOut < (GetTickCount / 1000) Then Exit Do
  Loop Until CmdSock.State >= sckConnected
 
  ' Besteht eine Verbindung?
  If CmdSock.State <> sckConnected Then
    ' Nein!
    Disconnect    
    Exit Function
  End If

Konnte eine Verbindung hergestellt werden, muss jetzt auf die Willkommensnachricht vom Server gewartet werden.

  ' Warten auf die Willkommensnachricht
  If SendCommand("", ConnectMsg, ConnectMsg, -1&, 0&, _
    "Verbunden mit " & CmdSock.RemoteHostIP) = False Then
 
    ' Abgewiesen!
    Disconnect
    Exit Function
  End If

Hat der Server sein OK gegeben, wird der Benutzername übermittelt. Der Benutzername wird daraufhin vom Server geprüft. Ist der Server mit dem Benutzername einverstanden, kann es aber sein, dass zum Endgültigen "Einloggen" noch zusätzlich ein Passwort benötigt wird. War die Antwort PasswordOK (230), so wird kein Passwort benötigt. Andernfalls muss jetzt vom Client das Benutzer-Passwort übermittelt werden.

  ' Benutzernamen senden und warten, ob Passwort benötigt wird
  If SendCommand(User & UserName, UserNameOK, PasswordOK, _
    UserNameFail, 0&, "Anmelden") = False Then
 
    ' Abgewiesen! Ungültiger Benutzer
    Disconnect
    Exit Function
  End If
 
  ' Wird ein Passwort benötigt?
  If LastServerCmd = PasswordOK Then
    ' Kein Passwort notwendig!
    Connect = True
    Exit Function
  End If
 
  ' Passwort senden und auf Bestätigung der Verbindung warten
  If SendCommand(Password & Pass, PasswordOK, PasswordOK, _
    UserCantLogin, 0&, "Erfolgreich angemeldet.") = False Then
 
    ' Abgewiesen! Ungültiges Passwort
    Disconnect
    Exit Function
  End If
 
  ' Erfolgreich verbunden!
  Connect = True
End Function

Immer wenn ein Verbindungsversuch mit dem Server gestartet wird, muss dem Server die eingehende Verbindung bestätigt bzw. akzeptiert werden. Das WinSock-Control löst hierfür das ConnectionRequest-Ereignis aus.

' Verbindungs-Bestätigung, wenn der Server eine Verbindung
' anfordert
Private Sub CmdSock_ConnectionRequest(ByVal requestID As Long)
  CmdSock.Accept requestID
End Sub

In der Connect-Funktion wurde sehr häufig der Befehl SendCommand eingesetzt. Hierbei handelt es sich nicht um einen Standard-Befehl, sondern um eine Hilfsfunktion, welche Ihnen noch sehr häufig im Laufe unseres Workshops begegnen wird.

Kommandos an den Server senden und auf Antwort warten

Die Funktion SendCommand sendet ein bestimmtes Kommand an den Server (wie z.B. Username, Passwort oder Verzeichniswechsel) und wartet solange, bis der Server auf die "Anfrage" reagiert bzw. antwort. Als Funktionsparameter werden u.a. die gewünschten Antworten angegeben. Die Funktion gibt dann entweder True (wenn die gewünschte Antwort vom Server bestätigt wurde) oder False (wenn der Server das Kommando abgelehnt hat oder eine Zeitüberschreitung aufgetreten ist) zurück.

' Sendet ein Kommando an den Server und wartet auf Antwort
Private Function SendCommand(ByVal CmdStr As String, _
  ByVal WaitFor As ServerMessages, _
  ByVal WaitFor2 As ServerMessages, _
  ByVal WrongAbort As ServerMessages, _
  Optional ByVal IgnoreValue As ServerMessages = 0, _
  Optional ByVal StatusText As String = "") As Boolean
 
  Dim TimeOut As Long
 
  ' letzte Servernachricht zurücksetzen
  LastServerCmd = IgnoreValue
 
  ' TimeOut setzen
  TimeOut = (GetTickCount / 1000) + ReplyTimeOut
 
  ' Kommando an den Server senden
  If CmdStr <> "" Then
    CmdSock.SendData CmdStr & vbCrLf
  End If
 
  ' Warten bis die Serverantwort eintrifft
  ' oder ein TimeOut eintritt
  Do
    DoEvents
    If LastServerCmd = WrongAbort Then Exit Do
    If CmdSock.State <> sckConnected Then Exit Do
    If TimeOut < (GetTickCount / 1000) Then Exit Do
  Loop Until LastServerCmd <> IgnoreValue
 
  ' Wenn Verbindung unterbrochen wurde, dann alle Socks schliessen
  If CmdSock.State <> sckConnected Then
    Disconnect
    Exit Function
  End If
 
  ' Wenn Antowrt OK dann Funktion erfolgreich beenden
  If LastServerCmd = WaitFor Or LastServerCmd = WaitFor2 Then
    SendCommand = True
  End If
End Function

Ankommende Servernachrichten verarbeiten

Der nachfolgende Code wird ausgeführt, wenn uns Servernachrichten erreichen. Die Nachricht speichern wir in zwei Variablen:

LastServerCmd: enthält den dreistelligen Nachrichten-Wert
LastServerValue: enthält alle zusätzlichen Parameter der Servernachricht

Beide Variablen werden dann später in einer Funktion ausgewertet, um entsprechend auf die Servernachrichten reagieren bzw. diese auswerten zu können.

' Server-Nachrichten treffen ein
Private Sub CmdSock_DataArrival(ByVal bytestotal As Long)
  Dim ServerCmd As String
  Dim TmpServerCmd As String
 
  ' Nachrichten-Daten ermitteln
  CmdSock.GetData ServerCmd
  TmpServerCmd = Left$(ServerCmd, 3)
 
  ' Das Kommando ist nur abgeschlossen, wenn nach dem Commado-ID
  ' ein Leerzeichen folgt
  If InStr(1, ServerCmd, TmpServerCmd & " ") <> 0 Then
    LastServerCmd = Left$(TmpServerCmd, 3)
 
    ' Server TimeOut
    If LastServerCmd = ConnectionTimeOut Then
      Disconnect
      Exit Sub
    End If
 
    LastServerValue = Replace(Mid$(ServerCmd, _
      InStr(1, ServerCmd, TmpServerCmd & " ") + 4), vbCrLf, "")
  End If
End Sub

Ein paar Verzeichnisfunktionen vorweg

Mit dem bisherigen Programmcode lässt sich nun schon einiges anfangen. Nachdem die Verbindung zum FTP-Server hergestellt wurde, kann man z.B. das aktuelle Verzeichnis ermitteln, in ein bestimmtes oder in einen übergeordneten Ordner wechseln.

' Aktuelles Verzeichnis auf dem Server ermitteln
Friend Function GetCurrentDir() As String
  Dim sCurDir As String
 
  If SendCommand(CurrentDir, CurrentDirectory, CurrentDirectory, _
    CommandFail) = False Then
    Exit Function
  End If
 
  ' Verzeichnisnamen
  sCurDir = Mid$(LastServerValue, 2)
  GetCurrentDir = Left$(sCurDir, InStr(1, sCurDir, """") - 1)
End Function
' Verzeichnis wechseln
Friend Function ChangeDir(ByVal DirName As String) As String
  If SendCommand(ChangeFolder & DirName, ChangeDirOK, _
    ChangeDirOK, CommandFail) Then
 
    ' Im Erfolgsfall, aktuelles Verzeichnis zurückgeben
    ChangeDir = GetCurrentDir
  End If
End Function
' In den übergeordneten Ordner wechseln
Friend Function ChangeDirUp() As String
  If SendCommand(ChangeFolderUP, ChangeDirOK, ChangeDirOK, _
    CommandFail) Then
 
    ' Im Erfolgsfall, aktuelles Verzeichnis zurückgeben
    ChangeDirUp = GetCurrentDir
  End If
End Function

Verzeichnisinformationen vom Server erfragen

Aktuelles Verzeichnis ermitteln oder in ein anderes Verzeichnis wechseln - alles schön und gut, doch noch viel interessanter ist das Ermitteln der kompletten Verzeichnisstruktur (welche Verzeichnisse existieren und welche Dateien befinden sich in den Verzeichnissen). Für eine solche Anfrage an den Server verwenden wir ein eigenes Winsock-Control - in unserem Workshop DataSock genannt. Zunächst muss wir den lokalen Datenport auf Empfang stellen und dem Server diesen Port mitteilen, so dass er sich mit dem Datenport verbinden kann. Der Server erwartet hier einen String, der die IP-Adresse mit zwei folgenden Werten enthält, um den Port zu berechnen.

Einen Port für die Datenübertragung berechnen

Die nachfolgende Funktion erstellt einen eindeutigen Port und den dazugehörigen Port-String für den Server.

' Erstellen eines eindeutigen Ports und den dazugehörigen
' Portstring für den Server
Private Function GetNewPort(ByRef Port As Long, _
  ByRef PortStr As String)
 
  Static PortCount As Long
  Static PortValueCount As Long
 
  Dim TmpPort As Long
  Dim TmpPortValue As Long
 
  ' eindeutigen Port errechnen
  TmpPort = PortCount + PortStartValue
  TmpPortValue = PortValueCount + PortValueStartValue
  Port = TmpPort * 256 Or TmpPortValue
 
  ' PortString für den Server erstellen
  PortStr = Replace(CmdSock.LocalIP, ".", ",") & "," & TmpPort _
    & "," & TmpPortValue
 
  ' Port bei jedem neuen Aufruf um eins erhöhen
  ' (darf jedoch 255 nicht übersteigen)
  If (PortValueCount + PortValueStartValue) + 1 >= 255 Then
    PortCount = PortCount + 1
    PortValueCount = 0
  Else
    PortValueCount = PortValueCount + 1
  End If
  If (PortCount + PortStartValue) >= 255 Then PortCount = 0
End Function

Verzeichnisinformationen in ein Array konvertieren

Die vom Server übermittelten Verzeichnisinformationen sind relativ schwer auszulesen. Die einzelnen Verzeichnisse und Dateien sind hierbei durch eine vbCrLf-Zeichenfolge voneinander getrennt. Alle weiteren Detail-Informationen befinden sich quasi in einer Zeile und sind durch Leerzeichen voneinander getrennt. Das ganze stellt sich deshalb als schwierig heraus, da man nicht im vornherein sagen kann, wie viele Informationen bzw. wie viele Leerzeichen eine solche "Datenzeile" enthält. Und dann kann es noch vorkommen, dass der Dateiname selbst, sowie die Datumsangabe ebenfalls Leerzeichen enthalten. Knackpunkt ist also, die "Trenn-Leerzeichen" von den ggf. zusätzlich vorhandenen Leerzeichen zu unterscheiden. Und genau diese Aufgabe erledigt die nachfolgende Funktion. Die Funktion gibt dann das Ergebnis als Array zurück.

' Erstellt aus den Verzeichnisinformationen des Servers ein Array
Private Function KonvertDirList(ByVal DirData As String) As Variant
  Dim AllFiles() As String
  Dim TmpArray() As String
  Dim i As Long
  Dim k As Long
  Dim TmpPos As Long
 
 ' Vorabinforamtionen des Servers entfernen
 If StrConv(Left$(DirData, 5), vbLowerCase) = "total" Then _
   DirData = Mid$(DirData, InStr(1, DirData, vbCrLf) + 2)
 
 If DirData = "" Then Exit Function
 
 ' Array dimensionieren
 AllFiles = Split(DirData, vbCrLf)
 ReDim TmpArray(0 To UBound(AllFiles) - 1, 3)
 
 ' Für jede Datei die entsprechenden Informationen ermitteln
 For i = 0 To UBound(AllFiles) - 1
 
   ' Attribute separieren
   TmpArray(i, 0) = Trim$(Left$(AllFiles(i), _
     InStr(1, AllFiles(i), " ") - 1))
   AllFiles(i) = Trim$(Replace(AllFiles(i), TmpArray(i, 0), "", 1, 1))
 
   ' dazwischenliegene Leerzeichen ausgrenzen
   For k = 0 To 2
     TmpPos = InStr(1, AllFiles(i), " ")
     AllFiles(i) = Trim$(Mid$(AllFiles(i), TmpPos))
   Next k
 
   ' Dateigröße
   TmpArray(i, 1) = Trim$(Left$(AllFiles(i), _
     InStr(1, AllFiles(i), " ") - 1))
   AllFiles(i) = Trim$(Replace(AllFiles(i), TmpArray(i, 1), "", 1, 1))
 
   ' Datum
   TmpArray(i, 2) = Left$(AllFiles(i), 12)
   AllFiles(i) = Trim$(Replace(AllFiles(i), TmpArray(i, 2), "", 1, 1))
 
   ' Dateiname
   TmpArray(i, 3) = Trim$(AllFiles(i))
 Next i
 
 ' Array zurückgeben
 KonvertDirList = TmpArray
End Function

Datenport öffnen und Verzeichnisinformationen ermitteln

Die nachfolgende Funktion sendet eine Anfrage an den Server, mit der "Bitte" die Verzeichnisinformationen des aktuellen Ordners zurückzugeben. Zunächst muss der Übertragungsmode auf Ascii gesetzt werden. Danach wird ein neuer (Daten)-Port erstellt, der dann geöffnet wird. Den Port teilt man anschliessend dem Server mit, so dass dieser eine Verbindung zum Datenport herstellen kann. Über den LIST - Befehl wird dem Server mitgeteilt, dass man die aktuellen Verzeichnisinformationen ermitteln möchte.

' Inhalt eines Verzeichnisses als Array zurückgeben
Friend Function GetDirList() As Variant
  Dim TmpPort As Long
  Dim TmpPortStr As String
 
  ' Servermode auf Ascii stellen
  If SendCommand(OvermitAscii, CommandOk, CommandOk, CommandFail, _
    0&, "Verzeichnissinhalt ermitteln") = False Then
    Exit Function
  End If
 
NewUniquePort:
  ' Neuen Portstring erstellen
  Call GetNewPort(TmpPort, TmpPortStr)
 
  ' Datenleitung mit neuem Port öffnen
  DataSock.Close
  DataSock.LocalPort = TmpPort
  DataSock.Listen
 
  ' Den neu geöffneten Port dem Server mitteilen
  If SendCommand(SetPort & TmpPortStr, CommandOk, CommandOk, _
    CommandFail, 0&, "Neuer Port geöffnet") = False Then
    Exit Function
  End If
 
  ' Dem Server mitteilen, dass wir die Verzeichnisinformationen
  ' benötigen
  Data = ""
  If SendCommand(ListFolder, TransferComplete, TransferComplete, _
    DataConnectionClosed, TransferStart, _
    "Verzeichnis übermittelt") = False Then
 
    GoTo NewUniquePort
  End If
 
  ' Ist noch ein Datenpäckchen unterwegs?
  Do
    DoEvents
  Loop Until DataSock.State <> 7
 
  ' Sock schließen
  DataSock.Close
 
  ' Falls die Verbindung zur Datenleitung nicht aufgebaut werden
  ' konnte, nochmals versuchen
  If LastServerCmd = DataConnectionError Or _
    LastServerCmd = DataConnectionClosed Then GoTo NewUniquePort
 
  GetDirList = KonvertDirList(Trim$(Data))
End Function

Nachdem wir dem Sever den Datenport mitgeteilt haben, versucht der Server eine Verbindung aufzubauen. Hierzu wird das ConnectionRequest-Ereignis ausgelöst, in welchem wir den Server-Verbindungsaufbau "genehmigen" müssen.

' Server-Verbindungsaufbau "genehmigen"
Private Sub DataSock_ConnectionRequest(ByVal requestID As Long)
  If DataSock.State = sckListening Then
    DataSock.Close
    Do
      DoEvents
    Loop Until DataSock.State = sckClosed
    DataSock.Accept requestID
  End If
End Sub

Die Verzeichnisinformationen selbst werden in einzelne "Datenpäckchen" gesendet. Immer wenn neue Daten eintreffen wird das DataArrival-Ereignis ausgelöst. Hier müssen wir die angekommenen Daten sammeln. Wir speichern diese also in eine Puffer-Variable.

' Ankommende Daten in den Puffer "Daten" kopieren
Private Sub DataSock_DataArrival(ByVal bytestotal As Long)
  Dim TmpData As String
  If bytestotal = 0 Then Exit Sub
 
  ' Daten aus dem Pufferpool holen
  DataSock.GetData TmpData
 
  ' Jedes ankommende Datenpäckchen in unseren Puffer übertragen
  Data = Data & TmpData
End Sub

Ermitteln der Dateigröße einer Datei

Das Ermitteln der Dateigröße einer Datei erfolgt in der Regel über das Kommando SIZE. Einige Server unterstützen diesen Befehl jedoch nicht. Dann muss die Dateigröße über die Verzeichnisinformationen ermittelt werden.

' Dateigröße ermitteln
Friend Function GetFileSize(ByVal FileName As String) As Long
  Dim TmpFiles As Variant
  Dim i As Long
 
  ' Dateigröße über das SIZE-Kommando ermitteln
  If SendCommand(FileSize & FileName, FileSizeOK, FileSizeOK, _
    CommandFail) = True Then
 
    GetFileSize = LastServerValue
    Exit Function
  End If
 
  ' Falls das SIZE-Kommando vom Server nicht unterstützt wird
  ' muss die Dateigröße über die Verzeichnisinformationen
  ' ermittelt werden
  Call GetDirList
  TmpFiles = KonvertDirList(Data)
  If IsArray(TmpFiles) Then
    For i = 0 To UBound(TmpFiles)
      If Mid$(FileName, InStrRev(FileName, "/") + 1) = TmpFiles(i, 3) Then
        GetFileSize = TmpFiles(i, 1)
        Exit Function
      End If
    Next i
  End If
 
  ' Fehler!
  GetFileSize = -1
End Function

Beenden der Verbindung

Beim Beenden der Verbindung zum Server muss das Kommando QuitSession gesendet werden. Danach kann die Close-Methode des CmdSock-Controls ausgeführt werden. Eine evtl. offene Datenleitung (Beim Beenden der Verbindung zum Server muss das Kommando QuitSession gesendet werden. Danach kann die Close-Methode des CmdSock-Controls ausgeführt werden. Eine evtl. offene Datenleitung (DataSock) sollte jetzt ebenfalls geschlossen werden.) sollte jetzt ebenfalls geschlossen werden.

' Verbindung trennen
Friend Function Disconnect()
  Dim TmpIP As String
 
  ' Verbindung zum Server trennen
  TmpIP = CmdSock.RemoteHost
  If CmdSock.State = sckConnected Then
    CmdSock.SendData QuitSession
    CmdSock.Close
  End If
 
  ' Daten-Verbindung ebenfalls schliessen
  If DataSock.State = sckConnected Then
    DataSock.Close
  End If
 
  ' Anzeige aktualisieren
  ' Siehe Workshop-Projekt
  ' ...
  TAbort = True
End Function

Zusammenfassung

Mit den bisher vorgestellten Befehlen und Funktionen lässt sich schon eine Art "Datei-Explorer" realisieren.

Hierzu sind folgende Schritte durchzuführen

  • Anmelden und Verbinden mit dem FTP-Sever
  • Verzeichnisinformationen lesen und in einer Liste anzeigen
  • Wechseln in über- und untergeordnete Verzeichnisse und Inhalt anzeigen
  • Verbindung beenden

Hinweis zum Abschluss
Bitte beachten Sie, dass das Debugging des Projekts bei laufender Server-Verbindung und Daten-Übertragung sehr schwierig ist, da ankommende Serverdaten im "Speicher-Nirvana" landen, wenn sich der Debugger in der Ruhephase befindet!

Das Beispielsprojekt zum Teil 1Das Beispielsprojekt zum Teil 1 des FTP-Workshops zeigt, wie man sich mit einem FTP-Server verbindet und sich dann innerhalb des Server-Verzeichnisses bewegen kann (also Verzeichnisse und Dateien anzeigen, inkl. Verzeichniswechsel)

Vorschau auf Teil 2

Im zweiten Teil unseres FTP-Workshops zeigen wir Ihnen, wie sich ein Up- und Download von Dateien realisieren lässt. Auch erfahren Sie, wie sich ein abgebrochener Up- oder Download fortsetzen lässt.

Am Ende des zweiten Teils verfügen Sie somit über alle Informationen, um einen eigenen FTP-Client zu erstellen.



Anzeige

Kauftipp Unser Dauerbrenner!Diesen und auch alle anderen Workshops finden Sie auch auf unserer aktuellen vb@rchiv  Vol.6
(einschl. Beispielprojekt!)

Ein absolutes Muss - Geballtes Wissen aus mehr als 8 Jahren vb@rchiv!
- nahezu alle Tipps & Tricks und Workshops mit Beispielprojekten
- Symbol-Galerie mit mehr als 3.200 Icons im modernen Look
Weitere Infos - 4 Entwickler-Vollversionen (u.a. sevFTP für .NET), Online-Update-Funktion u.v.m.
 
 
Copyright ©2000-2024 vb@rchiv Dieter OtterAlle Rechte vorbehalten.


Microsoft, Windows und Visual Basic sind entweder eingetragene Marken oder Marken der Microsoft Corporation in den USA und/oder anderen Ländern. Weitere auf dieser Homepage aufgeführten Produkt- und Firmennamen können geschützte Marken ihrer jeweiligen Inhaber sein.