Python erlaubt Verbindungen über Sockets, also auf der Transportschicht.

  • Man muss den Zielhost und den gewünschten Port eingeben
  • Es werden Bytes übertragen. Strings müssen codiert/decodiert werden.

# Quote-of-the-Day-Client

Eine der einfachsten Anwendungsprotokolle:
Der Client verbindet, der Server schickt ein Zitat und trennt die Verbindung.

import socket

# Verbindungseinstellungen
HOST = "djxmmx.net"
PORT = 17

# Socket erstellen und Verbindung aufnehmen
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST , PORT))

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

print(reply)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Zeile 1
Das Paket socket wird importiert.
Zeile 4
Die Konstante HOST wird auf den Hostnamen oder eine IP-Adresse gesetzt.
Zeile 5
Die Konstante PORT wird auf den Wert 17 gesetzt (gemäss QOTD-Protokoll)
Zeile 8
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.
Zeile 9
mit connect() wird die Verbindung hergestellt.
Zeile 12
recv() empfängt Daten. Bytes können mit decode() in String umgewandelt werden.
Zeile 14
Zur Kontrolle wird der empfangene Text in der Shell ausgegeben.

Aufgabe

  1. Teste das obenstehendene Programm
  2. Stelle den Server auf admin um
  3. Programmiere selbst einen Daytime-Client (überlege dir was du ändern musst – es ist nicht viel!)

# Echo-Client

Im Gegensatz zum obigen Beispiel, muss der Client beim Echo-Protokoll zuerst etwas an den Server schicken, bevor dieser dann antwortet.

import socket

# Verbindungseinstellungen
HOST = "admin.ad.kinet.ch"
PORT = 7

# Socket erstellen und Verbindung aufnehmen
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST , PORT))

# Daten schicken
s.sendall("Hallo Welt!".encode())

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

print(reply)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Zeile 12
sendall() schickt Daten. Text kann mit encode() in Bytes umgewandelt werden.

# GUI - graphical user interface

Wir haben mit dem Mu-Editor und Pygame Zero programmieren gelernt. Wir werden versuchen mit Pygame Zero ein Benutzerinterface zu erstellen für den Chat-Client. Dabei wollen wir aber den etwas fortgeschrittenen Editor Thonny verwenden.
(Bei uns am Gymer installiert, hier (opens new window) erhältlich)

Das einfachste Pygame Zero-Programm sieht nun wie folgt aus:

import pgzrun

WIDTH = 800
HEIGHT = 600

def draw():
    screen.clear()
    screen.draw.circle((400, 300), 30, 'white')

pgzrun.go()
1
2
3
4
5
6
7
8
9
10

Im Gegensatz zum ersten Programm im Python-Kapitel, sind nur die erste und die letzte Zeile hinzugekommen.

# Tastatureingaben

In Pygame Zero haben wir die Funktion on_key_down(key, mod, unicode) um auf Tasteneingaben zu reagieren. Wir müssen uns den Text aber selber zusammensetzen – die Funktion wird ja bei jedem einzelnen Tastendruck augerufen.

import pgzrun

WIDTH = 800
HEIGHT = 600

line = ""

def draw():
    screen.clear()
    screen.draw.text(line, (50, 30), color="orange")
    screen.draw.circle((400, 300), 30, 'white')

def on_key_down(key, mod, unicode):
    global line
    line = line + unicode

pgzrun.go()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

Was ist aber wenn wir uns vertippen? Wenn wir Backspace drücken, wird der Unicode für Backspace angehängt, es wird aber nicht das letzte Zeichen gelöscht. Also müssen wir bevor wir einfach anhängen, die Taste überprüfen. Die Funktion on_key_down könnte also wie folgt aussehen:

def on_key_down(key, mod, unicode):
    global line
    if key == keys.BACKSPACE:
        line = line[:-1]
    else:
        line = line + unicode
1
2
3
4
5
6

Aufgabe

  1. Teste das obenstehende Beispiel
  2. Baue die Löschfunktion mit Backspace ein
  3. Verschönere deinen Text
    https://pygame-zero.readthedocs.io/en/stable/ptext.html (opens new window)

# Threads

Das folgende Beispiel basiert auf dem obenstehenden Beispiel (GUI-Tastatureingaben). Die zusätzlichen Zeilen sind hervorgehoben und werden unterhalb diskutiert.


 
 





 






 
 
 
 
 






 
 
 
 
 
 


 
 



import pgzrun
import threading
import time

WIDTH = 800
HEIGHT = 600

line = ""
clear_line = False

def draw():
    screen.clear()
    screen.draw.text(line, (50, 30), color="orange")
    screen.draw.circle((400, 300), 30, 'white')
    
def update():
    global clear_line, line
    if clear_line:
        line = ""
        clear_line = False
    

def on_key_down(key, mod, unicode):
    global line
    line = line + unicode

def thread_function():
    global clear_line
    while True:
        time.sleep(7)
        print("sleep")
        clear_line = True


thread = threading.Thread(target=thread_function, args=())
thread.start()

pgzrun.go()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
Zeilen 35 und 36
Ein neuer Thread wird erzeugt, basierend auf der Funktion thread_function). Der Thread wird gestartet.
Zeilen 27-32
Diese Funktion soll in einem eigenen Thread ablaufen. Der Rest des Programms wird nicht blockiert.
Alle 7 Sekunden wird eine Variable clear_line auf True gesetzt.
Zeile 9
clear_line wird definiert
Zeilen 16-20
Die Update-Funktion ist Teil von Pygame Zero und wird regelmässig aufgerufen. Sie löscht die Zeile line, wenn clear_line gesetzt ist.
wenn wir line direkt in thread_function verändern, merkt Pygame Zero nicht, dass was geändert worden ist und somit wird draw nicht sofort aufgerufen.
Zeile 2 und 3
threading und time werden importiert

# String-Operationen

messages = [
    "/ERROR asdfasdf",
    "/JOINED asdfasdf",
    "/LEFT adsfasdf",
    "@me asdfasdf",
    "asdfasdf asdf"
]

def receive(message):
    if message.startswith("/"):
        print("Status-Nachricht von Server")
        status, message = message.split(maxsplit=1)
        print(status)
        print(message)      
    elif message.startswith("@me"):
        print("private Nachricht")
        print(message.replace("@me ", ""))        
    else:
        print("normale Chat-Nachricht")
        print(message.strip())
    print("")

for message in messages:
    receive(message)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

# startswith

str.startswith(prefix)
1

Liefert True falls str mit prefix beginnt. Andernfalls False.

Liefert eine Kopie von str, worin old durch new ersetzt wurde

Beispiel

str = "Guten Morgen"
if str.startswith("Guten"):
    print("Ihnen auch!")
1
2
3
Ihnen auch!

# replace

str.replace(old, new)
1

Liefert eine Kopie von str, worin old durch new ersetzt wurde

Beispiel

str = "Guten Morgen"
result = str.replace("Morgen", "Abend")
print(result)
1
2
3
Guten Abend

# strip

str.strip()
1

Liefert eine Kopie von str ohne vorangestellte oder hintenangestellte Leerzeichen

Beispiel

str = "    Hallo du "
result = str.strip()
print(result)
1
2
3
Hallo du

# split

str.split()
1

Liefert eine Liste von «Wörter», die im ursprünglichen String str durch Leerschläge getrennt waren. Das Argument maxsplit gibt an wie oft geteilt wird.

Beispiel

str = "Guten Morgen zusammen"
result = str.split(maxsplit=1)
for item in result:
    print(item)
1
2
3
4
Guten
Morgen zusammen
Letzte Änderung: 4.11.2020, 11:31:33