Asynchrone FTP-Transaktionen mit Boardmitteln

    • VB.NET

    Es gibt 3 Antworten in diesem Thema. Der letzte Beitrag () ist von AliveDevil.

      Asynchrone FTP-Transaktionen mit Boardmitteln

      FTP Uploads durch externe Librarys sind nicht immer des Coders lieblinge.
      Deshalb habe ich mich entschieden ein Tutorial zu erstellen.
      Die Idee kam mir hier [VB 2010] Daten per ftp hochladen

      Um nicht lange um den heißen Brei herum zu reden, hier mal der Anfang:

      VB.NET-Quellcode

      1. Imports System.Net
      2. Imports System.Threading
      3. Imports System.IO


      Also..was wir brauchen sind die Namespaces System.Net, Threading und IO.
      So weit so gut. Damit kann man jetzt relativ viel anstellen.
      Nur noch kein FTP Upload.

      Damit wir nun auch Events etc. haben, kommt eine neue Klasse dazu ( FtpState )

      VB.NET-Quellcode

      1. Public Class FtpState
      2. End Class


      Darein kommen nun mehrere Deklarationen.

      VB.NET-Quellcode

      1. Private wait As ManualResetEvent
      2. Private m_request As FtpWebRequest
      3. Private m_filename As String
      4. Private m_operationException As Exception = Nothing
      5. Private status As String

      Die Beschreibung dazu:

      wait: Threads über das beginnen des Threads informieren
      m_ftprequest: Der FtpWebRequest ( wie WebRequest )
      m_filename: Der Dateiname ( auf dem lokalen Dateisystem )
      m_operationException: Falls eine Ausnahme auftritt
      status: Status ( falls man möchte )

      Bei einer Instanzierung braucht man nun eine Methode die das Verwaltet.

      VB.NET-Quellcode

      1. Sub New()
      2. wait = New ManualResetEvent(False)
      3. End Sub


      Dazu nun auch die Propertys:

      VB.NET-Quellcode

      1. Public ReadOnly Property OperationComplete() As ManualResetEvent
      2. Get
      3. Return wait
      4. End Get
      5. End Property
      6. Public Property Request() As FtpWebRequest
      7. Get
      8. Return m_request
      9. End Get
      10. Set(ByVal value As FtpWebRequest)
      11. m_request = value
      12. End Set
      13. End Property
      14. Public Property FileName() As String
      15. Get
      16. Return m_filename
      17. End Get
      18. Set(ByVal value As String)
      19. m_filename = value
      20. End Set
      21. End Property
      22. Public Property OperationException() As Exception
      23. Get
      24. Return m_operationException
      25. End Get
      26. Set(ByVal value As Exception)
      27. m_operationException = value
      28. End Set
      29. End Property
      30. Public Property StatusDescription() As String
      31. Get
      32. Return status
      33. End Get
      34. Set(ByVal value As String)
      35. status = value
      36. End Set
      37. End Property

      Das war auch schon die FTPState-Klasse.

      Nun brauchen wir den AsynchronenFtpUploader.
      Der Name lässt auf den Klassennamen schließen.

      VB.NET-Quellcode

      1. Public Class AsynchronousFtpUploader


      Nun fügen wir ein Event das ProgressChanged-Event ein.

      VB.NET-Quellcode

      1. Public Shared Event UploadFileProgressChanged(total As Long, done As Long)


      Folgende Methode kann genannt werden wie man möchte. Hier bleibt es "Main".

      VB.NET-Quellcode

      1. Public Property localfile As String
      2. Public Property ftpfile As String
      3. Public Property user As String
      4. Public Property password As String
      5. ''' <summary>
      6. ''' Methode bei neu Erstellung
      7. ''' </summary>
      8. ''' <param name="localfile">Die Lokale Datei. Kein file:/// sondern einfach ordner/datei.ext</param>
      9. ''' <param name="ftpfile">Die Datei auf dem Server. Zusammenbau: ftp://host:port/ordner/datei.ext</param>
      10. ''' <param name="user">Der Benutzername</param>
      11. ''' <param name="password">Das Passwort</param>
      12. ''' <remarks></remarks>
      13. Public Sub New(ByVal localfile As String, ByVal ftpfile As String, ByVal user As String, ByVal password As String)
      14. Me.localfile = localfile
      15. Me.ftpfile = ftpfile
      16. Me.user = user
      17. Me.password = password
      18. End Sub
      19. ''' <summary>
      20. ''' Starte den Upload
      21. ''' </summary>
      22. ''' <remarks></remarks>
      23. Public Sub Upload()
      24. Dim waitObject As ManualResetEvent
      25. Dim target As New Uri(localfile) ' Erstelle eine neue URI. Bei nicht richtigem Gebrauch: Exception
      26. Dim fileName As String = ftpfile ' Lokale Datei
      27. Dim state As New FtpState() ' Initialisiere neue Instanz von FtpState
      28. Dim request As FtpWebRequest = DirectCast(WebRequest.Create(target), FtpWebRequest) ' Erstelle einen neuen FtpWebRequest auf Basis des targets
      29. request.Method = WebRequestMethods.Ftp.UploadFile ' Hochladen!
      30. request.Credentials = New NetworkCredential(user, password) ' Login Daten
      31. state.Request = request ' schreibe den Request in den FtpState
      32. state.FileName = fileName ' schreibe den Dateinamen in den FtpState
      33. waitObject = state.OperationComplete ' kopiere das ManualResetEvent in das waitObject
      34. Dim requestStream As Stream = Nothing ' neuen Stream erstellen
      35. Try ' Eventuelle Fehler abfangen
      36. requestStream = request.GetRequestStream() ' kopiere den Stream in den RequestStream
      37. Const bufferLength As Integer = 2048 ' Pufferlänge. Kann angepasst werden.
      38. Dim buffer As Byte() = New Byte(bufferLength - 1) {} ' Neues Array von Byte mit der Länge von bufferLength erstellen
      39. Dim count As Integer = 0 ' für ProgressChanged wichtig!
      40. Dim readBytes As Integer = 0 ' gelesene Bytes
      41. Dim totalBytes As Integer = CInt(New FileInfo(state.FileName).Length) ' totale Anzahl der Bytes in der Datei
      42. Dim stream As FileStream = File.OpenRead(state.FileName) ' Datei zum lesen öffnen
      43. Do
      44. readBytes = stream.Read(buffer, 0, bufferLength) ' lese in buffer von Index 0 die nächsten bufferLength-Bytes
      45. requestStream.Write(buffer, 0, readBytes) ' schreibe die Datei auf dem Server
      46. count += readBytes ' addiere readBytes zu count hinzu
      47. RaiseEvent UploadFileProgressChanged(totalBytes, count) ' wirf das Event ProgressChanged
      48. Loop While readBytes <> 0 ' solange readBytes nicht 0
      49. requestStream.Close() ' schließe den Stream
      50. state.Request.BeginGetResponse(New AsyncCallback(AddressOf EndGetResponseCallBack), state) ' Bekomme eine Antwort
      51. Catch e As Exception
      52. state.OperationException = e ' Exception zugänglich machen
      53. state.OperationComplete.[Set]() ' Fertig.
      54. Return ' Abbrechen
      55. End Try
      56. If state.OperationException IsNot Nothing Then
      57. Throw state.OperationException
      58. End If
      59. End Sub

      Dies scheint zu erst ziemlich kompliziert zu sein. Aber alles was es macht, ist die Datei Blockweise auf den Server zu schreiben.

      Aber es wird einen Fehler geben. Nämlich "EndGetResponseCallBack ist kein Member von AsynchronousFtpUpLoader".
      Das wollen wir nun eleminieren:

      VB.NET-Quellcode

      1. Private Sub EndGetResponseCallBack(ByVal ar As IAsyncResult)
      2. Dim state As FtpState = DirectCast(ar.AsyncState, FtpState) ' Konvertiere den IAsyncResult in einen FtpState
      3. Dim response As FtpWebResponse = Nothing ' Neuen FtpWebResponse erstellen
      4. Try
      5. response = DirectCast(state.Request.EndGetResponse(ar), FtpWebResponse) ' konvertiere den EndGetResponse in einen FtpWebResponse
      6. response.Close() ' schließen
      7. state.StatusDescription = response.StatusDescription ' Status beschreiben
      8. state.OperationComplete.[Set]() ' Fertig.
      9. Catch e As Exception ' Fehler abfangen
      10. state.OperationException = e ' zugänglich machen
      11. state.OperationComplete.[Set]() ' Fertig.
      12. End Try
      13. End Sub


      Das alles wäre ohne Beispiel jedoch ziemlich dämlich. Deshalb:

      VB.NET-Quellcode

      1. Sub Upload(ByVal dest_file As String, ByVal src_file As String)
      2. Dim ftpClient As New AsynchronousFtpUploader ' Neuer Uploader
      3. Dim FTPHost As String = "localhost" ' Der Host
      4. Dim FTPPort As Integer = 21 ' Der Port
      5. Dim FTPPath As String = "/ordner" ' Der Pfad
      6. Dim FTPUser As String = "anonymous" ' Der User
      7. Dim FTPPasswd As String = "" ' Das Passwort für den FTPUser
      8. AddHandler ftpClient.UploadFileProgressChanged, AddressOf UploadFileProgressChanged ' Füge neuen Handler hinzu
      9. ftpclient.Main("ftp://" & FTPHost & ":" & FTPPort & If(FTPPath.StartsWith("/"), "", "/") & FTPPath & If(FTPPath.EndsWith("/"), "", "/") & dest_file, src_file, FTPUser, FTPPasswd) ' Korrekte Konvertierung^^ und Start des Uploads
      10. End Sub
      11. Sub UploadFileProgressChanged(ByVal total As Long, ByVal done As Long)
      12. ' Progress wurde geändert!
      13. End Sub


      Nun. Das wars auch schon. Viel Spass mit dem Code.
      Kennzeichnung: keine. Woanders veröffentlichen: nirgendwo, ohne Erlaubnis.
      Teile dieses Codes stammen von MSDN.
      Modifiziert von: AliveDevil

      Folgende Änderungen kommen noch

      Quellcode

      1. - Download ( done )
      2. - Remove ( Datei: did, Ordner: kommt noch )
      3. - Rename ( geht nur bei Ordnern, kommt noch )
      4. - CreateDirectory ( kommt noch )
      5. - ListDirectory ( getan! )
      6. - Beispielprojekt ( veraltet )


      Geändert: Code auf den zweiten Teil modifiziert und angepasst
      Added ListDirectory

      Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „AliveDevil“ ()

      So...der Download kommt nun auch dazu.

      Wir erweitern den oberen Code einfach durch eine zweite Methode "DownloadFile"

      Die Werte von der Sub New()
      können behalten werden.
      Also:

      VB.NET-Quellcode

      1. Public Sub Download()
      2. End Sub


      Ein Paar Änderungen müssen aber noch gemacht werden.
      Z.B. können wir den GetRequestStream() nicht benutzen, sondern die GetResponse()-Methode.

      VB.NET-Quellcode

      1. ''' <summary>
      2. ''' Starte den Download
      3. ''' </summary>
      4. ''' <remarks></remarks>
      5. Public Sub Download()
      6. Dim waitObject As ManualResetEvent
      7. Dim target As New Uri(ftpfile) ' Erstelle eine neue URI. Bei nicht richtigem Gebrauch: Exception
      8. Dim fileName As String = localfile ' Lokale Datei
      9. Dim state As New FtpState() ' Initialisiere neue Instanz von FtpState
      10. Dim request As FtpWebRequest = DirectCast(WebRequest.Create(target), FtpWebRequest) ' Erstelle einen neuen FtpWebRequest auf Basis des targets
      11. request.Method = WebRequestMethods.Ftp.DownloadFile 'Herunterladen!
      12. request.Credentials = New NetworkCredential(user, password) ' Login Daten
      13. state.Request = request ' schreibe den Request in den FtpState
      14. state.FileName = fileName ' schreibe den Dateinamen in den FtpState
      15. waitObject = state.OperationComplete ' kopiere das ManualResetEvent in das waitObject
      16. Dim requestStream As Stream = Nothing ' Neuen Stream erstellen
      17. Try
      18. Dim response As FtpWebResponse = DirectCast(request.GetResponse(), FtpWebResponse) ' Lese die Response ein
      19. requestStream = response.GetResponseStream() ' Bekomme den Stream davon
      20. Const bufferLength As Integer = 2048 ' Neue Pufferlänge, kann modifiziert werden
      21. Dim buffer As Byte() = New Byte(bufferLength - 1) {} ' Neuen Puffer erstellen
      22. Dim count As Integer = 0 ' Für Progresschanged event
      23. Dim readBytes As Integer = 0 ' Für die Verarbeitung
      24. ' Aktuell werden die Totalen Bytes nicht gezählt!
      25. Dim stream As FileStream = File.OpenWrite(state.FileName) ' der neue Stream
      26. Do
      27. readBytes = requestStream.Read(buffer, 0, bufferLength) ' Lesen
      28. stream.Write(buffer, 0, readBytes) ' Schreiben
      29. count += readBytes ' Hinzufügen
      30. RaiseEvent DownloadFileProgressChange(count) ' Event werfen
      31. Loop While readBytes <> 0 ' Weiter laufen
      32. requestStream.Close() ' schließe den Stream
      33. state.Request.BeginGetResponse(New AsyncCallback(AddressOf EndGetResponseCallBack), state) ' Bekomme eine Antwort
      34. Catch ex As Exception
      35. state.OperationException = ex ' Exception zugänglich machen
      36. state.OperationComplete.[Set]() ' Fertig.
      37. Return ' Abbrechen
      38. End Try
      39. If state.OperationException IsNot Nothing Then
      40. Throw state.OperationException
      41. End If
      42. End Sub

      Hier lesen wir, wie oben, die Source-Datei in 2048-Byte Blöcken ein und speichern die in der Datei.
      Alles in allem relativ einfach.

      Beispielprojekt:
      Animated-Coding Server

      Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „AliveDevil“ ()

      Okay....Upload und Download funktionieren nun, was noch fehlt ist das Löschen von Dateien.
      Hier ist der Code etwas kleiner aber nicht ebenso wirkungsvoll wie die zwei Funktionen vorher.

      Dazu müssen wir erstmal wieder ein neues Event einfügen

      VB.NET-Quellcode

      1. Public Shared Event DeleteFile(ByVal message As String)

      Das senden wir, wenn der Download fertig ist bzw. ein Fehler aufgetaucht ist.

      Also, ran ans Werk:

      VB.NET-Quellcode

      1. Public Sub Delete()
      2. Dim state As New FtpState() ' Neuer Status
      3. Dim request As FtpWebRequest = DirectCast(WebRequest.Create(New Uri(ftpfile)), FtpWebRequest) ' WebRequest erstellen
      4. request.Method = WebRequestMethods.Ftp.DeleteFile ' Die Methode DateiLöschen
      5. request.Credentials = New NetworkCredential(user, password) ' Die Zugangsdaten
      6. state.FileName = ftpfile ' Der Dateiname
      7. state.Request = request ' Der Request
      8. Dim response As FtpWebResponse = DirectCast(request.GetResponse(), FtpWebResponse) ' Asynchronen Vorgang starten
      9. RaiseEvent DeleteFile(response.StatusDescription) ' Event werfen
      10. End Sub

      Das wars auch schon.

      Vllt. der Code für das benutzen:

      VB.NET-Quellcode

      1. ' Sub DoWork() um Case 2: asyncftp.Delete() erweitern
      2. Private Sub btn_delete_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btn_delete.Click
      3. asyncftp = New AsynchronousFTP() With {.user = txt_username.Text, .password = txt_password.Text, .ftpfile = "ftp://" & txt_host.Text & ":" & txt_port.Text & If(txt_ftpfile.Text.StartsWith("/"), "", "/") & txt_ftpfile.Text}
      4. thr = New Threading.Thread(Sub() DoWork(2))
      5. thr.Start()
      6. End Sub


      Ich werde das Beispielprojekt später aktualisieren.
      Ich habe mich wieder rangesetzt und den Teil etwas erweitert.
      Jetzt kann man sich auch die Dateien und Ordner im Verzeichnis ansehen.
      Also
      fügen wir erstmal ein neues Event ein

      VB.NET-Quellcode

      1. Public Shared Event ListDirectory(ByVal items As List(Of String))

      dann schreiben wir irgendwo in den Code (ambesten ans Ende :P)

      VB.NET-Quellcode

      1. Public Sub ListDirectory()
      2. End Sub

      Das wird unser Anfang, wie immer^^

      Der Beginn des Codes ist wie in der Downloadroutine:

      VB.NET-Quellcode

      1. Dim waitObject As ManualResetEvent
      2. Dim state As New FtpState() ' neuen FTP State erstellen
      3. Dim request As FtpWebRequest = DirectCast(WebRequest.Create(New Uri(ftpfile)), FtpWebRequest) ' Neuen Request
      4. request.Method = WebRequestMethods.Ftp.ListDirectory ' Setze Methode zu ListDirectory
      5. request.Credentials = New NetworkCredential(user, password) ' Verbinden mit den angegebenen Daten
      6. state.FileName = ftpfile ' Der Ordner!
      7. state.Request = request ' und zuweisung des Requests
      8. waitObject = state.OperationComplete ' kA^^

      Nur dass wir statt "DownloadFile" "ListDirectory" benutzen.

      Dann kommt wieder die berüchtigte Do-While-Schleife mit ein paar Zusätzen, für später

      VB.NET-Quellcode

      1. Dim bytelist As New List(Of String) ' Neue Liste von String erstellen
      2. Dim requestStream As Stream = Nothing ' Neuen Stream erstellen
      3. Try
      4. Dim response As FtpWebResponse = DirectCast(request.GetResponse(), FtpWebResponse) ' Lese die Response ein
      5. requestStream = response.GetResponseStream() ' Bekomme den Stream davon
      6. Dim buffer As Byte() = New Byte(0) {} ' Neuen Puffer erstellen, mit länge 1!
      7. Dim readBytes As Integer = 0 ' Für die Verarbeitung
      8. Dim stream As New MemoryStream
      9. Do
      10. readBytes = requestStream.Read(buffer, 0, 1) ' Lese genau ein Byte aus
      11. bytelist.Add(System.Text.Encoding.Default.GetString(buffer)) ' Füge der ByteList ein Item hinzu
      12. Loop While readBytes <> 0 ' Weiter laufen
      13. requestStream.Close() ' schließe den Stream
      14. state.Request.BeginGetResponse(New AsyncCallback(AddressOf EndGetResponseCallBack), state) ' Bekomme eine Antwort
      15. Catch ex As Exception ' Fehler abfangen
      16. state.OperationException = ex ' Exception zugänglich machen
      17. state.OperationComplete.[Set]() ' Fertig.
      18. Return ' Abbrechen
      19. End Try

      Wäre da nicht noch das Problem mit dem Weitergeben.

      VB.NET-Quellcode

      1. If state.OperationException IsNot Nothing Then
      2. Throw state.OperationException ' Fehler!
      3. End If
      4. RaiseEvent ListDirectory(bytelist) ' und die Daten vergeben

      That's it.
      Wir sind fertig mit dem darstellen der Items^^.
      Zum benutzen:

      VB.NET-Quellcode

      1. Private Sub asyncftp_ListDirectory(ByVal items As System.Collections.Generic.List(Of String)) Handles asyncftp.ListDirectory
      2. IO.File.WriteAllText("test.txt", String.Join("", items.ToArray()))
      3. Process.Start("test.txt")
      4. End Sub

      Das ist der Handler für das Event "ListDirectory". Wie man sieht, wird das Array zu einem String zusammengebaut, das geht, da der FTP Server eine mit "\r\n" begrenzte Liste sendet.
      So sollte bis jetzt die "DoWork"-Sub aussehen:

      VB.NET-Quellcode

      1. Sub DoWork(ByVal type As Integer)
      2. Select Case type
      3. Case 0
      4. asyncftp.Upload()
      5. Case 1
      6. asyncftp.Download()
      7. Case 2
      8. asyncftp.Delete()
      9. Case 3
      10. asyncftp.ListFiles()
      11. End Select
      12. End Sub


      Viel Spaß!