Netzwerk-Client

Netzwerk

Im folgenden Beispiel wird ein connect-Request auf unseren Chatserver gemacht. Dieser sendet die gewünschte HTML-Datei.

(Das Beispiel lässt sich gut in TigerJython ausführen)

import socket 

HOST = "192.168.5.3"  # ersetzen mit korrekter IP
PORT = 1291

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.connect((HOST , PORT))

# Daten schicken
s.sendall("/connect Elia".encode())

# Daten empfangen
reply = s.recv(1024).decode()

print(reply)
  • Das Paket socket wird importiert.
  • Die Konstante HOST wird auf die IP-Adresse gesetzt.
  • Die Konstante PORT wird auf den Wert 1291 gesetzt (gemäss Chat-Protokoll)
  • Es wird ein socket-Objekt s erstellt. Zum Erstellen müssen zwei Konstanten mitgeliefert werden, welche den Socket-Typ definieren:
    • socket.AF_INET besagt dass es sich um eine IPv4-Adresse handelt.
    • socket.SOCK_STREAM gibt an dass es sich um einen TCP-Socket handelt.
  • mit connect() wird die Verbindung hergestellt.
  • sendall() schickt Daten. Text kann mit encode() in Bytes umgewandelt werden.
  • recv() empfängt Daten. Bytes können mit decode() in String umgewandelt werden.
  • Zur Kontrolle wird der empfangene Text in der Shell ausgegeben.

GUI – Grafisches Userinterface

Wir verwenden wohl am einfachsten das schon bekannte «appJar». Das untenstehende Beispiel macht ein Fenster mit einem einzeiligen Input (entry) und einer TextArea für die Ausgabe. Wird eine Eingabe mit return abgeschlossen, so wird die Funktion sendMessage() aufgerufen. Diese liest den Wert des Eingabefeldes aus, hängt diesen am Ausgabefeld an und löscht das Eingabefeld.

from appJar import gui

def sendMessage():
    message = app.getEntry("eingabe")
    print("Sending: " + message)
    app.setTextArea("ausgabe", message+"\n", True)
    app.clearEntry("eingabe")

app=gui()

app.addEntry("eingabe")
app.addScrolledTextArea("ausgabe")

app.setEntrySubmitFunction("eingabe", sendMessage)

app.go()

Threads

Der Chat-Client muss unabhängig empfangen/senden können. Dazu brauch man sogenannte Threads, also unabhängige Prozesse.

In appJar lassen sich diese relativ einfach erzeugen: http://appjar.info/pythonThreads/

Wir starten am besten einen solchen Thread für das Empfangen. Dort lesen wir in einer Endlosschleife was uns der Server schickt.
Das Senden kann dann im Hauptprogramm geschehen. Es wird ja nur auf Tastendruck ausgeführt.

Strings

Unser Protokoll basiert auf String-Nachrichten. Diese wiederum aus einem Befehlsteil und einem Inhalt. Je nach Befehlsteil müssen wir unterschiedliche Dinge anstellen mit dem Inhalt der Nachricht.

Nachrichten verschicken

Beim Versenden von Nachrichten an den Chatserver müssen Strings zusammengefügt werden – normalerweise wird ein im Protokoll definierter Befehl vorne an die Mitteilung angehängt. Z.B. beim Anmelden an den Server:

username = "Elia"
message = "/connect " + username
s.sendall(message .encode())

Der gewünschte Username ist in einer Variablen abgespeichert. Vorangestellt wird der connect-Befehl aus unserem Protokoll.

Nachrichten empfangen

Beim Empfangen von Nachrichten müssen wir den Befehl vom Inhalt trennen. Dies ist ein klein bisschen komplizierter, aber auch noch zu bewerkstelligen:

Die Nachrichten sehen eigentlich immer so aus: /befehl nachricht
Wir können die Befehls-Teil der Nachricht erkennen mit startswith() und diesen anschliessend entfernen. Somit bleibt uns noch die Mitteilung, die wir ev. auch wieder aufteilen müssen weil sie aus zwei Elementen bestehen kann.

message = "/joined Elia"
message = "/public Elia Hallo wie geht es euch? Ich bin's Elia!"

if message.startswith("/joined"):  
    username = message.replace("/joined", "", 1).strip()
    print(username + " ist dem Chat beigetreten")
elif message.startswith("/public"):
    content = message.replace("/public", "", 1).strip()
    username = content.split(" ")[0]    
    text = content.replace(username, "", 1).strip()    
    print(username + " schreibt:")
    print(text)

replace(old, new, [maxreplace]) ersetzt in einem String old durch new. maxreplace gibt an wie oft ersetzt werden soll. Wir wollen nur 1x ersetzen!

strip() entfernt allfällige Leerzeichen die sich zuvorderst oder zuhinterst am String befinden.

Mit split(sep) können wir einen String zerschneiden, und zwar dort wo sep vorkommt. Wir erhalten eine Liste von Strings. Im Beispiel zerschneiden wir beim Leerzeichen und finden den Benutzernamen im ersten Element der erhaltenen Liste.