Schlagwort: Softwareentwicklung

Zeitserver mit C# abfragen 👍 👎

Im Folgenden möchte ich drei Möglichkeiten vorstellen, wie man mit C# über ein Netzwerk eine Zeitangabe abrufen kann. Es gilt zu beachten, dass es sich um sehr reduzierte Beispiele handelt und beispielsweise keine umfassende Fehlerbehandlung berücksichtigt wird. Wir verwenden standardmäßig jeweils einen Server des NIST.

Time Protocol

Beim Time Protocol handelt es sich um ein sehr einfaches (und etwas betagtes) Protokoll. Der Dienst steht für gewöhnlich unter Port 37 zur Verfügung. Details zum Protokoll können in RFC 868 nachgelesen werden.

Methode zur Zeitabfrage per Time Protocol
010203040506070809101112131415161718
public static DateTime GetTime(string host = "time.nist.gov", int port = 37) {    using(TcpClient client = new TcpClient(host, port)) {        using(NetworkStream networkStream = client.GetStream()) {            // Antwort lesen            byte[] timeBuffer = new byte[4];            networkStream.Read(timeBuffer, 0, timeBuffer.Length);
// Byte-Reihenfolge ggf. umkehren if(BitConverter.IsLittleEndian) { Array.Reverse(timeBuffer); }
return (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)) .AddSeconds(BitConverter.ToUInt32(timeBuffer, 0)) .ToLocalTime(); } }}

Daytime Protocol

Beim Daytime Protocol verhält es sich ähnlich, ist jedoch textbasiert. Der Dienst steht für gewöhnlich unter Port 13 zur Verfügung. Details zum Rückgabeformat können in RFC 867 und der Dienstbeschreibung des NIST nachgelesen werden – wir werden uns auf den minimalen Part (Datum und Uhrzeit) beschränken.

Methode zur Zeitabfrage per Daytime Protocol
010203040506070809101112131415161718
public static DateTime GetDaytime(string host = "time.nist.gov", int port = 13) {    using(TcpClient client = new TcpClient(host, port)) {        using(StreamReader streamReader = new StreamReader(client.GetStream())) {            // Antwort lesen und auftrennen            string[] response = streamReader.ReadToEnd().Split(' ');
// Datum und Uhrzeit ermitteln int[] date = Array.ConvertAll(response[1].Split('-'), Int32.Parse); int[] time = Array.ConvertAll(response[2].Split(':'), Int32.Parse);
return new DateTime( CultureInfo.CurrentCulture.Calendar.ToFourDigitYear(date[0]), date[1], date[2], time[0], time[1], time[2], DateTimeKind.Utc ).ToLocalTime(); } }}

Network Time Protocol (NTP)

Beim Network Time Protocol handelt es sich um einen weit verbreiteten Standard zur Zeitsynchronisierung in Computernetzwerken über Port 123. Details zum Protokoll können in RFC 5905 nachgelesen werden – wir werden uns wieder auf einen minimalen Teil beschränken. Als Server bietet sich hier natürlich auch die PTB an.

Methode zur Zeitabfrage per NTP
010203040506070809101112131415161718192021222324252627282930313233343536
public static DateTime GetNetworkTime(string host = "time.nist.gov", int port = 123) {    using(UdpClient client = new UdpClient()) {        client.Connect(host, port);
// Anfrage senden const byte header = ( (0 << 6) // LI = 0 (keine Warnung; zur Verdeutlichung ausformuliert) | (4 << 3) // VN = 4 (Version) | (3 << 0) // Mode = 3 (Client) ); // 00_100_011
byte[] request = new byte[48];
request[0] = header;
client.Send(request, request.Length);
// Antwort lesen IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 0); byte[] response = client.Receive(ref endPoint);
// Daten ab Offset aus ggf. umgekehrter Byte-Reihenfolge ermitteln const int offset = 40;
if(BitConverter.IsLittleEndian) { Array.Reverse(response, offset, 4); Array.Reverse(response, (offset + 4), 4); }
ulong integerPart = BitConverter.ToUInt32(response, offset); ulong fractionalPart = BitConverter.ToUInt32(response, (offset + 4));
return (new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc)) .AddMilliseconds((integerPart * 1000) + ((fractionalPart * 1000) / UInt32.MaxValue)) .ToLocalTime(); }}
Insbesondere bei diesem letzten Beispiel möchte ich noch einmal explizit darauf hinweisen, dass es sich um eine sehr reduzierte Implementierung handelt. Die Rückgabe erfolgt bei allen Beispielen jeweils bereits in lokaler Zeit.

Parameter einer Java-Methode per Reflexion ermitteln 👍 👎

Für ein kleines Projekt (mehr dazu in einem separaten Beitrag) war es notwendig, die Parameter von Methoden per Reflexion in Java zu ermitteln. Die entsprechende Funktionalität findet sich im Paket java.lang.reflect.
Parameter einer Java-Methode per Reflexion ermitteln
0102030405060708091011121314151617181920
public static List<Map<String,String>> GetMethodParameterList(    String typeName,    String methodName) throws ClassNotFoundException {    List<Map<String,String>> methodList = new ArrayList<>();
for(Method method : Class.forName(typeName).getMethods()) { if(method.getName().equals(methodName)) { HashMap<String,String> parameterList = new HashMap<>();
for(Parameter parameter : method.getParameters()) { parameterList.put(parameter.getName(), parameter.getType().getCanonicalName()); }
methodList.add(parameterList); } }
return methodList;}
Ähnlich zu LINQ bei C# gibt es für Java sogenannte Streams; wir können obige Implementierung damit beispielsweise wie folgt unter einem funktionalem Paradigma schreiben:
Implementierung mit Java-Streams
010203040506070809101112
public static List<Map<String,String>> GetMethodParameterList(    String typeName,    String methodName) throws ClassNotFoundException {    return Stream.of(Class.forName(typeName).getMethods())        .filter(m -> m.getName().equals(methodName))        .map(m -> Stream.of(m.getParameters()).collect(Collectors.toMap(            k -> k.getName(),            v -> v.getType().getCanonicalName()        )))        .collect(Collectors.toList());}
Die Verwendung gestaltet sich jeweils identisch:
Methode verwenden und Ergebnis auf der Standardausgabe ausgeben
01020304050607
List<Map<String,String>> methodList = GetMethodParameterList(typeName, methodName);
for(Map<String,String> parameterList : methodList) { for(Map.Entry<String,String> parameter : parameterList.entrySet()) { System.out.println(parameter.getValue() + " " + parameter.getKey()); }}
Es gilt schließlich noch eine kleine Besonderheit zu berücksichtigen: Standardmäßig stehen die Bezeichner der Parameter unter Java nicht zur Verfügung, sondern würden schlicht durchnummeriert. Um die tatsächlichen Bezeichner zu erhalten, muss mit der Option -parameters kompiliert werden. Verbreitete Entwicklungsumgebungen wie Eclipse oder NetBeans bieten hierfür entsprechende Einstellungen.

Wir bereits früher angekündigt werde ich zur Reflexion in C# noch einen etwas ausführlicheren Beitrag schreiben und dann dort eine entsprechende Implementierung ähnlicher Funktionalität vorstellen.

Wissen(schaft)spodcasts 👍 👎

In früheren Beiträgen hatte ich bereits ein paar Podcast-Empfehlungen ausgesprochen und auf mein Feedreader-Projekt hingewiesen, welches ebenfalls einige Podcasts aus verschiedenen Themenbereichen bereithält.

Mit diesem Beitrag möchte ich darüber hinaus gerne auf Wissenschaftspodcasts hinweisen. Diese Seite wurde von einigen Podcastern aus dem Bereich der Wissenschafts- und Wissensvermittlung ins Leben gerufen. Zu den Gründern gehören u. a. auch Nicolas Wöhrl von methodisch inkorrekt und Markus Völter von omega tau – beides Podcasts, die ich schon seit sehr langer Zeit verfolge und auch hier bereits beworben habe.

Die von den genannten und weiteren Personen eröffnete Seite bietet nun eine Sammlung verschiedenster Podcasts aus den Bereichen Wissen und Wissenschaft. Das Themenspektrum ist breit abgedeckt und reicht von Archäologie, Geschichte und Technik über Astronomie, Forschung im Allgemeinen und Speziellen bis hin zu Mathematik und Naturwissenschaften. Ich gehe also davon aus, dass für (fast) alle meiner – sicherlich hauptsächlich technisch interessierten – Besucher etwas dabei sein dürfte. Ihr könnt darüber hinaus auch neue Vorschläge einreichen.

Da mir die Vermittlung von Wissen und das Gespräch über wissenschaftliche Erkenntnisse persönlich äußerst wichtige Angelegenheiten sind, würde ich mich sehr freuen, wenn ihr etwas Passendes findet und die Seite(n) weiterempfehlt. Der Vollständigkeit wegen möchte ich abschließend noch kurz darauf hinweisen, dass ich mit den genannten Seiten in keiner weiteren Verbindung außer als Zuhörer einiger Podcasts stehe.

Hintergrundaufgaben unter ASP.NET ausführen 👍 👎

Im Vergleich zu manch anderen Laufzeitumgebungen für Web-Anwendungen ist es bei ASP.NET auch hier recht einfach, Aufgaben im Hintergrund (beispielsweise über separate Threads) auszuführen. Obwohl das grundsätzlich jederzeit sehr einfach – abgesehen von den inhärenten Schwierigkeiten, die Nebenläufigkeit mit sich bringen kann – möglich ist, ist das einfache Vorgehen, wie man es aus dem Desktop-Bereich kennt, fehleranfällig.

Im einfachsten Fall würde man asynchron per Task.Run(…) (als mit häufig genutzten Standardwerten vorbelegte Abkürzung für Task.Factory.StartNew(…)) zur Verwendung des Standard-Thread-Pools oder manuell per Thread-Klasse arbeiten. Dies ist jedoch insofern problematisch, als dass die ASP.NET-Anwendungsdomäne davon nicht benachrichtigt wird und daher beispielsweise einerseits auf die Beendigung des Vorgangs nicht gewartet und andererseits auf das Herunterfahren des Arbeitsprozesses nicht reagiert werden kann.

Bereits seit längerer Zeit werden daher u. a. die beiden Methoden RegisterObject und UnregisterObject der HostingEnvironment-Klasse zur Verfügung gestellt, deren Verwendung jedoch etwas umständlich ist und auf Grund neuerer Möglichkeiten, die gleich im Anschluss vorgestellt werden, an dieser Stelle nicht weiter ausgeführt werden soll. Wir verwenden stattdessen die seit .NET 4.5.2 verfügbare QueueBackgroundWorkItem-Methode:
Hintergrundaufgabe hinzufügen
010203
HostingEnvironment.QueueBackgroundWorkItem(cT => {    /* Implementierung der Aufgabe */});
Etwas ungeschickt ist es jedoch, wenn dies beispielsweise in einem separaten Projekt ausgelagert werden soll, um jeweils von einer Desktop- und Web-Anwendung darauf zuzugreifen – außerhalb der ASP.NET-Laufzeitumgebung ist die Verwendung dieser Methode nämlich nicht möglich. Daher stellen wir für diesen Fall eine Alternative zur Verfügung, um zumindest ähnliches Verhalten für alle Anwendungsfälle zur Verfügung zu stellen:
Hilfsmethode (inkl. Fallback) implementieren
010203040506070809101112
public static class TaskUtility {    public static void DoBackgroundWork(        Action<CancellationToken> action,        CancellationToken cancellationToken = default(CancellationToken)    ) {        if(HostingEnvironment.IsHosted) {            HostingEnvironment.QueueBackgroundWorkItem(action);        } else {            Task.Run(() => action(cancellationToken), cancellationToken);        }    }}
Hilfsmethode verwenden
010203
TaskUtility.DoBackgroundWork(cT => {    /* Implementierung der Aufgabe */});
Alternativ wäre es natürlich auch möglich, nur die eigentliche Aufgabe auszulagern und dann anwendungsspezifisch zu starten. Weiterführende Informationen zur Verwendung des CancellationToken stellt das MSDN insbesondere über die Struktur CancellationToken und die Klasse CancellationTokenSource zur Verfügung.

ZIP-Archive mit C# (ent-)packen 👍 👎

In einem früheren Beitrag zur (De-)Kompression mit C# hatte ich bereits einen Beitrag zu ZIP-Archiven in Aussicht gestellt. Diese sind praktisch, um Dateien und Verzeichnisstrukturen komprimiert und zusammenhängend speichern und austauschen zu können und werden außerdem auf praktisch jeder Plattform unterstützt. Daher möchte ich mit diesem Beitrag zeigen, wie sich mit C# entsprechende Archive automatisch erstellen lassen.

Das .NET-Framework liefert im Namensraum System.IO.Compression grundlegende Funktionalität zur Arbeit mit entsprechenden Archiven, auf die wir in diesem Beitrag setzen werden. Darüber hinaus gibt es jedoch eine Vielzahl weiterer Implementierungen (z. B. DotNetZip oder SharpZipLib), welche beispielsweise die Erstellung von passwortgeschützten Archiven ermöglichen und weitere Konfigurationsmöglichkeiten bieten.

Zuerst möchte ich ein paar Möglichkeiten aufzeigen, um ZIP-Archive zu erstellen:
Archiv aus bestehendem Verzeichnis erstellen
01
ZipFile.CreateFromDirectory("data", "data.zip");
Archiv aus bestehenden Dateien erstellen
010203040506
using(FileStream fileStream = File.OpenWrite("data.zip")) {    using(ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create)) {        zipArchive.CreateEntryFromFile("data/a.txt", "a.txt");        zipArchive.CreateEntryFromFile("data/b.txt", "b.txt");    }}
Archiv manuell zusammenstellen
010203040506070809
using(FileStream fileStream = File.OpenWrite("data.zip")) {    using(ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Create)) {        ZipArchiveEntry zipArchiveEntry = zipArchive.CreateEntry("README");
using(StreamWriter streamWriter = new StreamWriter(zipArchiveEntry.Open())) { streamWriter.Write("Hallo Welt!"); } }}
Abschließend möchten wir auf bestehende Archive zugreifen:
Archiv vollständig entpacken
01
ZipFile.ExtractToDirectory("data.zip", "data");
Archiv einlesen und verarbeiten
01020304050607080910111213141516
using(FileStream fileStream = File.OpenRead("data.zip")) {    using(ZipArchive zipArchive = new ZipArchive(fileStream, ZipArchiveMode.Read)) {        foreach(ZipArchiveEntry zipArchiveEntry in zipArchive.Entries) {              // "README" separat berücksichtigen            if(zipArchiveEntry.Name == "README") {                using(StreamReader streamReader = new StreamReader(zipArchiveEntry.Open())) {                      // Inhalt einlesen und auf Konsole ausgeben                    Console.WriteLine(streamReader.ReadToEnd());                }            } else {                  // Eintrag als Datei speichern                zipArchiveEntry.ExtractToFile(zipArchiveEntry.Name);            }        }    }}
Sehr viel mehr Möglichkeiten stehen leider tatsächlich nicht zur Verfügung; für weitergehende Einstellungen gibt es jedoch beispielsweise die bereits erwähnten externen Bibliotheken. Durch die Verwendung von Datenströmen besteht aber grundsätzlich die Möglichkeit, nahezu beliebige Quellen (und Ziele) für Archiv-Einträge heranzuziehen.

Projektverweise

Kategorien / Archiv  |  Übersicht RSS-Feed

Schlagworte

Suche