Lösungen

Aufgabe: Datenraten verstehen

Die Einheit MB für Megabyte wird nicht einheitlich verwendet. Je nach Kontext beschreibt sie \(10^6 = 1000000\) oder \(2^{10} = 1048576\) Bytes (siehe Wikipedia). Im Netzwerkkontext und zur Angabe der Speicherkapazität von Festplatten beschreibt ein MB meist \(10^6\) Bytes, weshalb im Folgenden dieser Wert zu Grunde gelegt wird. Gbps steht für Gigabit pro Sekunde, misst also die Datenrate. 1 Gbps sind \(10^9\) Bits (nicht Bytes!) oder \(125\) MB pro Sekunde.

1 TB sind \(10^{12}\) Bytes oder \(8 \cdot 10^{12}\) Bits, 24 Stunden sind \(24 \cdot 60 \cdot 60\) Sekunden. Der Transport von 1 TB in 24 Stunden entspricht also einer Datenrate von \(8 \cdot 10^{12} / 24 \cdot 60 \cdot 60\) Bits pro Sekunde oder knapp 93 Mbps. Gängige DSL Anschlüsse bieten eine Datenrate von 16 Mbps, der WLAN Standard 802.11g bis zu 54 Mbps.

2000 Server mit jeweils 500 GB Speicherkapazität speichern zusammen \(10^6\) GB oder 1 Petabyte Daten. Bei einem Transport über zehn Stunden ergibt sich eine Datenrate von \(8 \cdot 10^{15} / 10 \cdot 60 \cdot 60\) Bits pro Sekunde, also gut 222 Gbps oder knapp ein vierzigstel der Datenrate eines im Jahr 2001 gelegten transatlantischen Glasfaserkabels.

Aufgabe: Prüfbits programmieren

Zur Berechnung von Prüfbits ist die folgende Hilfsfunktion nützlich, die zählt, wie viele Einsen eine Zeichenkette aus Nullen und Einsen enthält.

def count_ones(s):
    count = 0
    for i in range(0, len(s)):
        if s[i] == '1':
            count = count + 1
    return count

Mit ihrer Hilfe können wir die Funktion add_checksum definieren, indem wir ihr Ergebnis modulo zwei an die Eingabe anhängen.

def add_checksum(s):
    return s + str(count_ones(s) % 2)

Zur Überprüfung verwenden wir ebenfalls count_ones, um zu testen, ob die Anzahl der Einsen in der Eingabe gerade ist. Falls nicht, haben wir einen Übertragungsfehler erkannt.

def is_valid(s):
    return count_ones(s) % 2 == 0

Die folgenden Beispielaufrufe zeigen das Verhalten der definierten Funktionen. Wenn der Funktion is_valid das Ergebnis von add_checksum übergeben wird, liefert sie True zurück. Der zweite Aufruf von valid ändert die Eingabe an einer Stelle, der dritte an zwei Stellen. Die erste Änderung wird erkannt, die zweite nicht.

>>> add_checksum('101010')
'1010101'
>>> is_valid('1010101')
True
>>> is_valid('1010001')
False
>>> is_valid('1000001')
True

Bonusaufgabe: Prüfbits variabler Länge programmieren

Neben der in der vorherigen Aufgabe implementierten Hilfsfunktion count_ones ist die folgende Funktion hilfreich, die die Binärdarstellung einer natürlichen Zahl als Zeichenkette liefert.

def binary(n):
    if n == 0:
        return '0'
    else:
        bin = ''
        while n > 0:
            bin = str(n % 2) + bin
            n = n // 2
        return bin

Wir können nun die Prüfsumme berechnen, indem wir die Funktion binary auf das Ergebnis von count_ones anwenden und gegebenenfalls führende Nullen ergänzen.

def add_block_checksum(s,n):
    checksum = binary(count_ones(s) % (2**n))
    return s + ('0' * (n - len(checksum))) + checksum

Zur Überprüfung einer Prüfsumme können wir diese Funktion auf das Anfangsstück der Eingabe anwenden, das der ursprünglichen Nachricht entspricht und dann vergleichen, ob wir das selbe Ergebnis erhalten.

def is_valid_block(s,n):
    message = s[0:len(s)-n]
    return s == add_block_checksum(message,n)

Wir können bei \(n = 2\) bis zu drei Einsen in Nullen umwandeln und den so entstehenden Übertragungsfehler erkennen.

>>> add_block_checksum('10110100101',2)
'1011010010110'
>>> is_valid_block('1011010010110',2)
True
>>> is_valid_block('1001010010110',2)
False
>>> is_valid_block('1000010010110',2)
False
>>> is_valid_block('1000000010110',2)
False
>>> is_valid_block('1000000000110',2)
True

Im letzten Fall bleibt der Übertragungsfehler also unerkannt. Dies geschieht auch dann, wenn die Anzahl der Einsen gleich bleibt:

>>> is_valid_block('1111110000010',2)
True

Auch Übertragungsfehler in der Prüfsumme können unerkannt bleiben, wenn es zu ihr passende Übertragungsfehler in der Nachricht gibt:

>>> is_valid_block('1011011010111',2)
True

Hier wurde sowohl in der Prüfsumme als auch in der Nachricht eine Null in eine Eins umgewandelt, ohne dass dies erkannt wird.

Um blockweise Prüfsummen zu berechnen fügen wir der Nachricht zunächst Nullen hinzu, wenn ihre Länge noch kein Vielfaches der Blockgröße ist. Dann verwenden wir die Funktion add_block_checksum in einer Schleife für jeden Block. Obwohl die Anzahl der Schleifendurchläufe vorab bekannt ist, verwenden wir eine while-Schleife, um die Zählvariable index in jedem Schritt um die Blockgröße b hochzuzählen.

def add_block_checksums(s,b,n):
    mod = len(s) % b

    if mod > 0:
        s = s + ('0' * (b - mod))
    
    index = 0
    message = ''
    while index < len(s):
        message = message + add_block_checksum(s[index:index+b],n)
        index = index + b
    
    return message

Zur Überprüfung wenden wir is_valid_block in einer Schleife auf jeden Block an und brechen ab, wenn wir einen Fehler gefunden haben.

def has_valid_blocks(s,b,n):
    index = 0
    valid = True
    while valid and index < len(s):
        valid = is_valid_block(s[index:index+b+n],n)
        index = index + b + n
    return valid

Es ist nun deutlich schwieriger durch zielloses Manipulieren einen unentdeckten Übertragungsfehler zu erzeugen.

>>> add_block_checksums('1100101100011011111011011',6,2)
'1100101111000111101111011011010010000001'
>>> has_valid_blocks('1100101111000111101111011011010010000001',6,2)
True
>>> has_valid_blocks('1100101111000111100011011011010010000001',6,2)
False
>>> has_valid_blocks('1100101111011111100011011011010010000001',6,2)
False
>>> has_valid_blocks('1100101111011111100011011011010010011101',6,2)
False

Bonusaufgabe: Fehlerkorrektur programmieren

Zur Implementierung von encode durchlaufen wir die Eingabe in einer Zählschleife und fügen dabei der Ausgabe jedes Zeichen dreimal hinzu.

def encode(s):
    code = ''
    for i in range(0,len(s)):
        code = code + s[i] * 3
    return code

Zur Dekodierung verwenden wir die Funktion count_ones, die wir schon zur Implementierung von Prüfsummen verwendet haben. Diesmal durchlaufen wir die Eingabe in einer bedingten Schleife um die Zählvariable in Dreierschritten hochzuzählen.

def decode(s):
    msg = ''
    index = 0
    while index < len(s):
        if count_ones(s[index:index+3]) >= 2:
            msg = msg + '1'
        else:
            msg = msg + '0'
        index = index + 3
    return msg

Die folgenden Aufrufe zeigen das Kodieren sowie das Dekodieren mit keinem, einem oder zwei Übertragungsfehlern. Im letzten Fall schlägt die Fehlerkorrektur fehl, da zu viele Einsen im letzten Block in Nullen umgewandelt wurden.

>>> encode('101101')
'111000111111000111'
>>> decode('111000111111000111')
'101101'
>>> decode('111000111111000101')
'101101'
>>> decode('111000111111000001')
'101100'

Aufgabe: Distance Vector Routing nachvollziehen

Wir betrachten das folgende Netzwerk aus den Hosts A, B, C und D.

A --- B
|     |
C --- D

Zu Beginn speichert jeder Host in seiner Routing-Tabelle nur eine Verbindung mit Kosten Null zu sich selbst.

Danach könnte zum Beispiel A seine Information an B und C schicken. Die Routing-Tabelle von B sähe danach wie folgt aus.

  Ziel  über    Entfernung
------  ------  ------------
     B  B       0
     A  A       1

Angenommen, D schickt nun seine Tabelle an seine Nachbarn B und C. Dann wird die Tabelle von B wie folgt erweitert.

  Ziel  über    Entfernung
------  ------  ------------
     B  B       0
     A  A       1
     D  D       1

Wenn B nun seinerseits diese Information an seine Nachbarn verschickt, kann A die folgende Tabelle aufbauen.

  Ziel  über    Entfernung
------  ------  ------------
     A  A       0
     B  B       1
     D  B       2

Der Eintrag für A wird nicht übernommen, da schon ein kürzerer Weg bekannt ist. Sobald A eine Nachricht von C erhält, wird seine Tabelle wie folgt komplettiert.

  Ziel  über    Entfernung
------  ------  ------------
     A  A       0
     B  B       1
     D  B       2
     C  C       1

Die anderen Hosts berechnen ihre Tabellen analog, zum Beispiel mit den folgenden Ergebnissen.

  Ziel  über    Entfernung
------  ------  ------------
     B  B       0
     A  A       1
     D  D       1
     C  A       2

  Ziel  über    Entfernung
------  ------  ------------
     C  C       0
     A  A       1
     D  D       1
     B  D       2

  Ziel  über    Entfernung
------  ------  ------------
     D  D       0
     A  B       2
     B  B       1
     C  C       1

Angenommen, nun würde die Verbindung zwischen A und B getrennt. Diese beiden Hosts erhalten nun keine Nachrichten mehr voneinander, so dass sie sich nicht mehr als Nachbarn betrachten. Dadurch ändern sich mit der Zeit die Routing-Tabellen aller Hosts wie folgt.

  Ziel  über    Entfernung
------  ------  ------------
     A  A       0
     B  C       3
     D  C       2
     C  C       1

  Ziel  über    Entfernung
------  ------  ------------
     B  B       0
     A  D       3
     D  D       1
     C  D       2

  Ziel  über    Entfernung
------  ------  ------------
     C  C       0
     A  A       1
     D  D       1
     B  D       2

  Ziel  über    Entfernung
------  ------  ------------
     D  D       0
     A  C       2
     B  B       1
     C  C       1

Wird nun zum Beispiel auch noch die Verbindung zwischen A und C getrennt, ist A vom Rest des Netzwerkes abgeschnitten. Normalerweise merken das die anderen Hosts und entfernen entsprechene Routen aus ihrer Tabelle. Im ungünstigen Fall, dass zum Beispiel C die Nachricht von D bekommt, dass dieser A über zwei Hops erreichen kann, bevor C selbst die Nachricht verbreitet hat, dass es A nicht mehr erreicht, kann es jedoch zum sogenannten count to infinity problem kommen.

Dabei speichert C nun ab, dass es A über D mit Entfernung 3 erreichen kann, woraufhin D notiert, dass es A über C mit Entfernung 4 erreichen kann, woraufhin C notiert, dass es A über D mit Entfernung 5 erreichen kann und so weiter. Es gibt verschiedenen Mechanismen, dieses Problem zu vermeiden, die wir hier aber nicht weiter besprechen.

Aufgabe: Email-Format verstehen und ausgeben

from datetime import datetime

print("Wie ist Deine Adresse?")
frm = input()
print("An welche Adresse willst Du schicken?")
to = input()
print("Betreff?")
subject = input()
print("Nachricht eingeben (mit zwei Leerzeilen beenden):")
text  = ""
blank = False
line  = input()
while not blank or line != "":
  text  = text + line + "\n"
  blank = line == ""
  line  = input()
print("From: <" + frm + ">")
print("To: <" + to + ">")
print("Subject: " + subject)
print("Date: " + datetime.now().isoformat())
print()
print()
print(text)

Aufgabe: HTML-Quelltext generieren

Das folgende python-Programm gibt eine Multiplikationstabelle für das kleine Einmaleins aus.

n = 10

print("<table>")
for i in range(0,n):
    print("  <tr>")
    for j in range(0,n):
        print("    <td>" + str((i+1)*(j+1)) + "</td>")
    print("  </tr>")
print("</table>")

Für n = 3 ergibt sich die folgende Ausgabe, die wir in eine HMTL-Datei kopieren können, um die Tabelle im Browser anzusehen.

<table>
  <tr>
    <td>1</td>
    <td>2</td>
    <td>3</td>
  </tr>
  <tr>
    <td>2</td>
    <td>4</td>
    <td>6</td>
  </tr>
  <tr>
    <td>3</td>
    <td>6</td>
    <td>9</td>
  </tr>
</table>

Sie wird in etwa wie folgt angezeigt.

123
246
369