UWP Socket File Transfer

Windows 10 erbt die WinRT API aus Windows 8. Diese API wurde als Hybrid aus Technologien von COM und JavaScript zusammengesetzt. Darauf wurde ein .NET Framework Profil gepfropft. Was sich schon etwas komplex anhört, erschwert in der Tat die Programmierung. Dieser Blog Artikel erläutert am VB.NET Prototyp einen Datei Transfer per TCP/IP.

Alle WinRT basierten API beginnen befinden sich im Namensraum Windows, alles was .net bietet beginnt mit System. Dabei gibt es häufig funktionelle Doppelgleisigkeiten, die meist nicht kompatibel sind. Für den Brückenschlag dienen dann meist Extension Methoden wie AsStreamForRead.

   1:  Imports System.Text
   2:  Imports System.Net.Sockets
   3:  Imports Windows.Networking
   4:  Imports Windows.Networking.Sockets
   5:  Imports Windows.Storage
   6:  Imports Windows.Storage.Streams

Für einen TCP/IP basierenden Filetransfer brauchen wir einen TCP Server, auch listener genannt. Obwohl system.net.sockets referenziert ist, lässt sie .net 4.5 tcplistener Klasse nicht instanziieren. Die Windows Runtime nennt diese StreamSocketListener. Dabei erhält man einen Stream vom Typ IInputStream.

Erste Einführung dazu in der MSDN Dokumentation.

Allerdings ist es auf Windows 10 Maschinen nicht möglich Socket Connections von App zu App innerhalb des Computers auszuführen. Einzig im Debug Modus und innerhalb der gleichen App gibt es keine Probleme. Tiefer gehende Informationen zu Loop Back Exempt.

Der Server wird per Button Click gestartet. Ein Streamsocket Listener lauscht auf Port 2000 und feuert bei Datenankunft ein Event OnConnection.

   1:   Private listener As StreamSocketListener
   2:   
   3:   Private Async Sub button_Click(sender As Object, e As RoutedEventArgs)
   4:          listener = New StreamSocketListener()
   5:          AddHandler listener.ConnectionReceived, AddressOf OnConnection
   6:          Await listener.BindServiceNameAsync("2000", protectionLevel:=SocketProtectionLevel.PlainSocket)
   7:  End Sub

Bereits der Verbindungsaufbau eines Client löst das Event aus. Der Code wird allerdings in einem seperaten Thread ausgeführt, so das eine Rückmeldung zum User Interface per Dispatcher erfolgen muss.

   1:    Private Async Sub OnConnection(
   2:              sender As StreamSocketListener,
   3:              args As StreamSocketListenerConnectionReceivedEventArgs)
   4:   
   5:          Await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
   6:                            Sub()
   7:                                textBox.Text = args.Socket.Information.RemotePort
   8:                            End Sub)

Der Verbindungsablauf im  Beispiel

  1. Server startet und wartet auf Verbindung
  2. Client baut Verbindung auf
  3. Server sendet “FILEOK”
  4. Client sendet Bild
  5. Server speichert Bild

Als Fortsetzung zum vorhergehenden Code Beispiel aus dem OnConnection Event, wird der Output Stream auf der aktuellen Connection genutzt. Da von der WinRt APi kommend handelt es sich um ein COM Objekt vom Typ IOutputStream. Mit der Extension Methode AsStreamForWrite wird daraus ein .net Stream Objekt. Erst mit dem Streamwriter kann darin auch geschrieben werden. 

   1:  Using raus As Stream = args.Socket.OutputStream.AsStreamForWrite()
   2:              Dim wr As StreamWriter = New StreamWriter(raus)
   3:              Await wr.WriteLineAsync("FILEOK") 'Readline
   4:              Await wr.FlushAsync()
   5:  End Using

Theoretisch könnte man auch direkt auf dem WinRT Objekt in der Form args.Socket.OutputStream.WriteAsync(CryptographicBuffer.ConvertStringToBinary("FILEOK", BinaryStringEncoding.Utf8)) geschrieben werden. Hat bei mir leider nicht geklappt.

Da noch immer das OnConnection Event codiert wird, muss noch Schritt 5, das speichern des Bildes erledigt werden. In diesem VB.NET Prototypen wird einfach ein Bild mit dem Namen ZZZZZ angelegt. Auch StorageFile ist ein WinRT Objekt bzw. der dazu nötige Stream.

Durchaus verwirrend kommt nun ein drittes Objekt zum Einsatz das einen Stream in den anderen Stream kopiert. Kein Writer, kein Flush, dafür alles über die WinRT API.

   1:  Dim file As StorageFile = Await KnownFolders.PicturesLibrary.CreateFileAsync("ZZZZZZZZZZZ.png",
CreationCollisionOption.ReplaceExisting)
   2:  Using outputStream As IOutputStream = Await file.OpenAsync(FileAccessMode.ReadWrite)
   3:              Await RandomAccessStream.CopyAndCloseAsync(args.Socket.InputStream, outputStream)
   4:  End Using

Der Client verwendet eine StreamSocket Instanz und verbindet sich zum Server auf Port 2000.

   1:  Private Async Sub button1_Click(sender As Object, e As RoutedEventArgs) Handles button1.Click
   2:          Dim client As StreamSocket
   3:          client = New StreamSocket()
   4:          Await client.ConnectAsync(New HostName("127.0.0.1"), "2000")

Da der Server unmittelbar seine Nachricht sendet, wird diese in der nächsten Code Zeile auch gelesen. Allerdings muss hier der Stream aus der WinRT Welt mit AsStreamForRead wieder in einen .net Stream gecastet werden. Dafür reicht dann einen Zeile um die Antwort des Servers zu lesen (FILEOK).

   1:  Using reader As StreamReader = New StreamReader(client.InputStream.AsStreamForRead())
   2:              Dim response = Await reader.ReadLineAsync() 'writeline
   3:              antwort.Text = response
   4:  End Using
   5:         

Wenn der Server Bereitschaft signalisiert, kann die Datei gesendet werden. Dazu wird eine Datei geöffnet. Aus der FileIO Hilfsklasse kommt eine Methode zum Einsatz die einen referenziertes Byte Array vom Typ IBuffer bereit stellt für die Leseoperation. Soweit so praktisch, da der Stream direkt ein entsprechendes Objekt in seiner Schreiboperation benötigt.

   1:  If antwort.Text = "FILEOK" Then
   2:              Dim fs = Await KnownFolders.PicturesLibrary.GetFileAsync("youtube.png")
   3:              Dim b As IBuffer = Await FileIO.ReadBufferAsync(fs)
   4:              Dim r = Await client.OutputStream.WriteAsync(b)
   5:              Await client.OutputStream.FlushAsync()
   6:              client.Dispose()
   7:  End If

Anfangs wurde mein Socket Objekt offen gehalten, da ich über eine Bidirektionale TCP Kommunikation zwischen Server und Client nachdenke. Allerdings kam dann das Bild nie am Server an. Erst das schließen der Connection per Dispose (Zeile 6) brachte den Erfolg. Hier muss ich noch weiter über die Hintergründe recherchieren.

Im Ergebnis gesehen überschaubarer Aufwand. Allerdings habe ich mehrere Stunden aufgewendet um zu einem funktionierenden Ergebnis zu kommen. Ich finde einfach keine durchgängige Logik in den Klassen. Welcher Stream schreibt wohin? Welche Hilfsklassen gibt es und wie verwendet man diese optimal. Welche Extension Methoden? Wann WinRT, wann .net? Viele Fragen für jede einzelne Zeile Code.

Kommentare sind geschlossen