EINFÜHRUNG |
Der Austausch von Daten zwischen Rechnersystemen spielt in unserer vernetzten Welt eine ausserordentlich wichtige Rolle. Darum wird oft gemeinsam von den Computer- und Kommunikationstechnologien gesprochen, die es zu beherrschen gilt. In diesem Kapitel erarbeitest du Basiswissen in der Computerkommunikation zwischen zwei Rechnern mit dem Protokoll TCP/IP, auf dem auch alle Internetverbindungen aufbauen, also beispielsweise das Web und alle Streaming-Dienste (Datenclouds, Sprach-, Musik-, Videoübertragung). |
TCPCOM: EINE EVENTGESTEUERTE SOCKET-BIBLIOTHEK |
Die Socket-Programmierung baut auf dem Client-Server-Modell auf, das bereits im Kapitel 6.2 beschrieben wurde. Dabei laufen auf dem Client und dem Server Programme, die nicht vollständig symmetrisch sind. Vielmehr muss zuerst das Server-Programm gestartet sein, bevor das Client-Programm eine Verbindung zum Server aufbauen kann. Um die beiden Computer auf dem Internet zu identifizieren, wird ihre IP-Adresse verwendet. Zudem verfügen beide über 65536 Kommunikationskanäle (IP ports), die man mit einer Zahl im Bereich 0..65535 auswählt. Beim Start des Server erstellt dieser einen Server-Socket (wie eine Steckdose) auf einem bestimmten Port und begibt sich in einen Wartezustand. Anschaulich spricht man davon, dass der Server jetzt auf die Anmeldung eines Clients "hört", sich also im LISTENING-Zustand befindet. Der Client erstellt seinerseits beim Start einen Client-Socket (das Steckdosen-Gegenstück) und versucht unter Verwendung der IP-Adresse und der Portnummer eine Verbindung mit dem Server zu erstellen. Die Bibliothek tcpcom vereinfacht die Socket-Programmierung wesentlich, da sie den aktuellen Zustand von Client und Server mit einer Zustandsvariablen state beschreibt und die Zustandsänderungen und der Datenaustausch durch Ereignisse (Events) erfasst werden. Dabei wird der Callback stateChanged(state, msg) aufrufen. Das Python-Modul ist in TigerJython integriert, kann aber auch von hier heruntergeladen werden, um es zu studieren oder ausserhalb von TigerJython zu verwenden.Der Server wird mit der Erzeugung eines TCPServer-Objekts unter Angabe des Ports und der Callbackfunktion onStateChanged() gestartet und begibt sich in LISTENING-Zustand. from tcpcom import TCPServer server = TCPServer(port, stateChanged = onStateChanged) Der Callback onStateChanged(state, msg) hat zwei String-Parameter state und msg, die den Zustandswechsel des Servers beschreiben:
Der Client wird mit der Erzeugung eines TCPClient-Objekts unter Angabe der IP-Adresse des Servers, des Ports und der Callbackfunktion onStateChanged() gestartet. Mit dem Aufruf connect() versucht er eine Verbindungsaufnahme. from tcpcom import TCPClient client = TCPClient(host, port, stateChanged = onStateChanged) client.connect()
Der Aufruf von connect() ist blockierend, das heisst, dass die Funktion mit dem Rückgabewert True zurückkehrt, sobald die Verbindungsaufnahme gelungen ist oder aber erst nach einer bestimmten Timeout-Zeit (von rund 10 s) mit False, falls die Verbindungsaufnahme misslingt. Die Information über den Erfolg bzw. Misserfolg der Verbindungsaufnahme kann aber auch über den Callback erfasst werden. Client-Server-Programme kannst du durch Starten von zwei TigerJython-Fenstern auf dem gleichen PC austesten. Als IP-Adresse wählst du in diesem Fall den Alias localhost. Verwendest du etwas wirklichkeitsnaher zwei verschiedene Computer, so müssen diese mit einem Netzwerkkabel oder über Wireless LAN verbunden und der gewählte Port für Socket-Kommunikation offen sein. Klappt es mit einer WLAN-Verbindung über deinen normalen Hotspot (WLAN Access Point) nicht, so kannst du auf deinem Smartphone einen Mobilen Hostspot starten und dich mit beiden Computern darauf verbinden. Ein Zugang des Handys zum Internet ist nicht notwendig. Für den Einstieg in die Socketprogrammierung erstellst du einen Server, der einen Zeitservice anbietet. Wenn sich ein Client anmeldet, so sendet er die aktuelle Zeit (mit Datum) an den Client zurück. Es gibt auf dem Internet zahlreiche solcher Zeitserver und du kannst stolz sein, dass du bereits jetzt in der Lage bist, eine professionelle Serveranwendung zu erstellen. Um den Zeitserver wieder abschalten zu können, greifst du zu einem bekannten Trick: Das Hauptprogramm "hängt" in einer blockierenden Funktion, die nach der Rückkehr den Server mit terminate() beendet. Der in TigerJython vordefinierte Message-Dialog ist dazu bestens geeignet. from tcpcom import TCPServer import datetime def onStateChanged(state, msg): print(state, msg) if state == TCPServer.CONNECTED: server.sendMessage(str(datetime.datetime.now())) server.disconnect() port = 5000 server = TCPServer(port, stateChanged = onStateChanged) msgDlg("Time Server running. OK to stop") server.terminate() Der Client fragt zuerst nach der IP-Adresse und versucht mit connect() eine Verbindung mit dem Server herzustellen. Die vom Server zurück erhaltene Zeitinformation schreibst du in ein Dialog-Fenster. from tcpcom import TCPClient def onStateChanged(state, msg): print(state, msg) if state == TCPClient.MESSAGE: msgDlg("Server reports local date/time: " + msg) if state == TCPClient.CONNECTION_FAILED: msgDlg("Server " + host + " not available") host = inputString("Time Server IP Address?") port = 5000 client = TCPClient(host, port, stateChanged = onStateChanged) client.connect()
|
MEMO |
Sowohl der Server wie der Client verwenden das Event-Modell mit einer Callbackfunktion onStateChanged(state, msg). Diese liefert in den zwei Parameterwerten wichtige Informationen über die Art des Ereignisses. Achte darauf, dass du den Server mit terminate() beendest, damit der IP-Port freigegeben wird. |
ECHO-SERVER |
Das nächste Übungsbeispiel ist berühmt, da es sich um den Archetyp einer Client-Server-Kommunikation handelt. Der Server macht dabei nichts anderes, als die vom Client erhaltene Message unverändert wieder zurück zu senden. Aus diesem Grund wird er Echo-Server genannt. Das System kann leicht so verändert werden, dass der Server die erhaltene Message analysiert und eine entsprechende Antwort zurück sendet. Wichtigstes Anwendungsbeispiel sind Webserver, die auf einen Request des Clients (Browser) mit einem Reply gemäss dem HTTP-Protokoll antworten. Du schreibst als erstes den Echo-Server und kannst ihn auch gleich starten. Er unterscheidet sich nur unwesentlich vom Zeitserver. Zur Illustration und zum Debugging schreibst du noch state und msg in das Ausgabefenster. from tcpcom import TCPServer def onStateChanged(state, msg): print(state, msg) if state == TCPServer.MESSAGE: server.sendMessage(msg) port = 5000 server = TCPServer(port, stateChanged = onStateChanged) msgDlg("Echo Server running. OK to stop") server.terminate() Beim Client gibst du dir bereits etwas mehr Mühe und verwendest mit der Klasse EntryDialog einen nicht-modalen Dialog, um Statusinformationen auszuschreiben. EntryDialog ist auch in anderen Zusammenhängen sehr praktisch, da du neben editierbaren und nicht-editierbaren Textfeldern auch noch andere GUI-Elemente wie verschiedene Sorten von Buttons und Schieberegler einbauen kannst. from tcpcom import TCPClient from entrydialog import * import time def onStateChanged(state, msg): print(state, msg) if state == TCPClient.MESSAGE: status.setValue("Reply: " + msg) if state == TCPClient.DISCONNECTED: status.setValue("Server died") def showStatusDialog(): global dlg, btn, status status = StringEntry("Status: ") status.setEditable(False) pane1 = EntryPane(status) btn = ButtonEntry("Finish") pane2 = EntryPane(btn) dlg = EntryDialog(pane1, pane2) dlg.setTitle("Client Information") dlg.show() host = "localhost" port = 5000 showStatusDialog() client = TCPClient(host, port, stateChanged = onStateChanged) status.setValue("Trying to connect to " + host + ":" + str(port) + "...") time.sleep(2) rc = client.connect() if rc: time.sleep(2) n = 0 while not dlg.isDisposed(): if client.isConnected(): status.setValue("Sending: " + str(n)) time.sleep(0.5) client.sendMessage(str(n), 5) # block for max 5 s n += 1 if btn.isTouched(): dlg.dispose() time.sleep(0.5) client.disconnect() else: status.setValue("Connection failed.") |
MEMO |
Im Client-Programm verwendest du die Funktion sendMessage(msg, timeout) mit einem zusätzlichen Parameter timeout. Diese blockiert während maximal der Zeit timeout (in s), bis der Server eine Antwort zurücksendet. sendMessage() liefert als Rückgabewert die Antwort des Servers (oder None, wenn innerhalb von timeout keine Antwort empfangen wird). Es ist für dich wichtig, den Unterschied zwischen modalen und nicht-modalen Dialogfenstern zu kennen. Während das modale Fenster das Programm blockiert, bis es geschlossen wird, läuft das Programm bei einem nicht-modalen Fenster weiter und es können laufend Informationen hineingeschrieben oder Eingabewerte ausgelesen werden. |
ZWEIPERSONEN-SPIELE MIT TURTLEGRAFIK |
Das Spiel "Schiffchen versenken" ist ein bekanntes und beliebtes Spiel zwischen zwei Personen, bei dem das Erinnerungsvermögen eine wichtige Rolle spielt. In den Zellen eines ein- oder zweidimensionalen Gitters jedes Spielers werden gleiche oder verschiedenartige und damit verschiedenwertige Schiffe angeordnet, die auch mehrere Zellen belegen können. Ein Zug besteht darin, eine Zelle des Gegners zu bezeichnen, in der sozusagen eine Bombe abgeworfen wird. Befindet sich ein Teil eines Schiff darin, wird dieses bei diesem Abwurf versenkt, also vom Spielbrett entfernt und dem Bombenwerfer die entsprechende Wertigkeit gutgeschrieben. Wenn einer der Spieler keine Schiffe mehr hat, ist das Spiel beendet und es gewinnt der Spieler mit der grösseren Punktezahl. In der einfachsten Version werden die Schiffe in einer eindimensionalen Anordnung durch gefüllte quadratische Zellen dargestellt und alle Schiffe haben die gleiche Wertigkeit. Es gewinnt derjenige Spieler, der zuerst alle Schiffe des Gegners versenkt hat. Was die Spiellogik angeht, sind Client und Server weitgehend identisch. Für die Grafik genügt es, elementare Zeichnungsoperationen aus der Turtlegrafik zu verwenden, die du schon lange kennst. Du wählst eine Zellennummerierung von 1 bis 10 und musst für die zufällige Wahl des Standorts der Schiffe 4 verschiedene zufällige Zahlen von 1..10 erzeugen. Es ist elegant, dazu die Funktion random.sample() heranzuziehen. Beim Bombenabwurf sendest du den ganzen Python-Programmbefehl an den Partner, der ihn mit exec() ausführen kann. Dies ist nur in wenigen Programmiersprachen möglich. Um herauszufinden, ob getroffen wurde, prüfst du mit getPixelColorStr() die Hintergrundfarbe. Beim Spiel sind die beiden Spieler gleichwertig, ihre Programme unterscheiden sich aber etwas, je nachdem wer Server und wer Client ist. Zudem muss der Server zuerst starten. from gturtle import * from tcpcom import TCPServer from random import sample def initGame(): clear("white") for x in range(-250, 250, 50): setPos(x, 0) setFillColor("gray") startPath() repeat 4: forward(50) right(90) fillPath() def createShips(): setFillColor("red") li = sample(range(1, 10), 4) # 4 unique random numbers 1..10 for i in li: fill(-275 + i * 50, 25) def onMouseHit(x, y): global isMyTurn setPos(x, y) if getPixelColorStr() == "white" or isOver or not isMyTurn: return server.sendMessage("setPos(" + str(x) + "," + str(y) + ")") isMyTurn = False def onCloseClicked(): server.terminate() dispose() def onStateChanged(state, msg): global isMyTurn, myHits, partnerHits if state == TCPServer.LISTENING: setStatusText("Waiting for game partner...") initGame() if state == TCPServer.CONNECTED: setStatusText("Partner entered my game room") createShips() if state == TCPServer.MESSAGE: if msg == "hit": myHits += 1 setStatusText("Hit! Partner's remaining fleet size " + str(4 - myHits)) if myHits == 4: setStatusText("Game over, You won!") isOver = True elif msg == "miss": setStatusText("Miss! Partner's remaining fleet size " + str(4 - myHits)) else: exec(msg) if getPixelColorStr() != "gray": server.sendMessage("hit") setFillColor("gray") fill() partnerHits += 1 if partnerHits == 4: setStatusText("Game over, Play partner won!") isOver = True return else: server.sendMessage("miss") setStatusText("Make your move") isMyTurn = True makeTurtle(mouseHit = onMouseHit, closeClicked = onCloseClicked) addStatusBar(30) hideTurtle() port = 5000 server = TCPServer(port, stateChanged = onStateChanged) isOver = False isMyTurn = False myHits = 0 partnerHits = 0 Das Programm des Clients ist fast identisch: from gturtle import * from random import sample from tcpcom import TCPClient def initGame(): for x in range(-250, 250, 50): setPos(x, 0) setFillColor("gray") startPath() repeat 4: forward(50) right(90) fillPath() def createShips(): setFillColor("green") li = sample(range(1, 10), 4) # 4 unique random numbers 1..10 for i in li: fill(-275 + i * 50, 25) def onMouseHit(x, y): global isMyTurn setPos(x, y) if getPixelColorStr() == "white" or isOver or not isMyTurn: return client.sendMessage("setPos(" + str(x) + "," + str(y) + ")") isMyTurn = False def onCloseClicked(): client.disconnect() dispose() def onStateChanged(state, msg): global isMyTurn, myHits, partnerHits if state == TCPClient.DISCONNECTED: setStatusText("Partner disappeared") initGame() elif state == TCPClient.MESSAGE: if msg == "hit": myHits += 1 setStatusText("Hit! Partner's remaining fleet size " + str(4 - myHits)) if myHits == 4: setStatusText("Game over, You won!") isOver = True elif msg == "miss": setStatusText("Miss! Partner's remaining fleet size " + str(4 - myHits)) else: exec(msg) if getPixelColorStr() != "gray": client.sendMessage("hit") setFillColor("gray") fill() partnerHits += 1 if partnerHits == 4: setStatusText("Game over, Play partner won") isOver = True return else: client.sendMessage("miss") setStatusText("Make your move") isMyTurn = True makeTurtle(mouseHit = onMouseHit, closeClicked = onCloseClicked) addStatusBar(30) hideTurtle() initGame() host = "localhost" port = 5000 client = TCPClient(host, port, stateChanged = onStateChanged) setStatusText("Client connecting...") isOver = False myHits = 0 partnerHits = 0 if client.connect(): setStatusText("Connected. Make your first move!") createShips() isMyTurn = True else: setStatusText("Server game room closed")
|
MEMO |
Bei Zweipersonenspielen können beide Spieler dasselbe oder auch unterschiedliche Spielfelder haben. Im zweiten Fall, zu denen auch die meisten Kartenspiele gehören, muss es notwendigerweise mit zwei Computern gespielt werden, da die Spielsituation vor dem Gegner geheim gehalten wird. Statt das immer der Client mit dem ersten Zug beginnt, könnte der Spielbeginn auch zufällig oder durch Aushandeln erfolgen. Beide Programme werden durch Klicken des Close-Buttons in der Titelzeile beendet. Dabei müssen aber noch zusätzliche "Aufräumarbeiten" wie das Beenden des Servers oder die Unterbrechung der Verbindung ausgeführt werden. Darum registrierst du den Callback onCloseClicked(), wodurch das normale Beenden unterbunden wird. Es liegt nun an dir, im Callback die richtigen Operationen, einschliesslich dem Schliessen des Turtlefensters mit dispose(), durchzuführen. Unterlässt du dies, so bleibt dir nur noch der Griff zum Taskmanager, um das Programm abzuschiessen. |
ZWEIPERSONEN-ONLINE-SPIELE MIT GAMEGRID |
Für etwas komplexere Spiele ist es von grossem Vorteil, eine Game-Library heranzuziehen, wodurch der Programmcode wesentlich vereinfacht wird. Du hast in Kapitel 7 bereits GameGrid kennen gelernt und gesehen, wie man Spiele damit programmiert. Kombinierst du GameGrid mit tcpcom, so kannst du anspruchsvolle Mehrpersonen-Online-Spiele erstellen, bei denen sich die Spielpartner irgendwo auf der Welt befinden. Zur Illustration erweiterst du das Spiel Schiffchen versenken auf 2 Dimensionen. Die Spiellogik kannst du fast unverändert übernehmen. Allerdings überträgst du nun beim Bombenabwurf die x- und y-Zellenkoordinaten. Der Partner kann damit herausfinden, ob ein Schiff getroffen wurde und hit oder miss zurückmelden. Beim Programmieren von Games ist es wichtig, sich Mühe zu geben, dass sich das Game "vernünftig" abläuft, selbst wenn die beiden Spieler sich etwas unvernünftig verhalten. So muss das Abschiessen von Bomben nur gestattet sein, wenn der Spieler tatsächlich am Zug ist. Bei unvorhergesehenem Beenden des Clients oder des Servers sollte der Partner davon unterrichtet werden. Diese Überprüfungen blähen zwar den Code etwas auf, sind aber das Markenzeichen solider und gewissenhafter Programmierung. Da ein grosser Teil des Codes für den Server und Client identisch ist und die Codeduplikationen zu den grössten Sünden des Programmierens gehört, wird hier im Gegensatz zum vorhergehenden Beispiel der gemeinsame Code in ein Modul shiplib.py ausgelagert, das von beiden Applikationen durch einen import verwendet wird. Unterschiedliches Verhalten berücksichtigt man durch Parameter wie hier node, der entweder auf ein TCPServer- oder TCPClient-Objekt verweist. Library: # shiplib.py from gamegrid import * isOver = False isMyMove = False dropLoc = None myHits = 0 partnerHits = 0 nbShips = 2 class Ship(Actor): def __init__(self): Actor.__init__(self, "sprites/boat.gif") def handleMousePress(node, loc): global isMyMove, dropLoc dropLoc = loc if not isMyMove or isOver: return node.sendMessage("" + str(dropLoc.x) + str(dropLoc.y)) # send location setStatusText("Bomb fired. Wait for result...") isMyMove = False def handleMessage(node, state, msg): global isMyMove, myHits, partnerHits, first, isOver if msg == "hit": myHits += 1 setStatusText("Hit! Partner's fleet size " + str(nbShips - myHits) + ". Wait for partner's move!") addActor(Actor("sprites/checkgreen.gif"), dropLoc) if myHits == nbShips: setStatusText("Game over, You won!") isOver = True elif msg == "miss": setStatusText("Miss! Partner's fleet size " + str(nbShips - myHits) + ". Wait for partner's move!") addActor(Actor("sprites/checkred.gif"), dropLoc) else: x = int(msg[0]) y = int(msg[1]) loc = Location(x, y) bomb = Actor("sprites/explosion.gif") addActor(bomb, loc) delay(2000) bomb.removeSelf() refresh() actor = getOneActorAt(loc, Ship) if actor != None: actor.removeSelf() refresh() node.sendMessage("hit") partnerHits += 1 if partnerHits == nbShips: setStatusText("Game over! Partner won") isOver = True return else: node.sendMessage("miss") isMyMove = True setStatusText("You fire!") Der Code des Server und des Clients wird durch die Verwendung von shiplib.py wesentlich einfacher und übersichtlicher. Server: from gamegrid import * from tcpcom import TCPServer import shiplib def onMousePressed(e): loc = toLocationInGrid(e.getX(), e.getY()) shiplib.handleMousePress(server, loc) def onStateChanged(state, msg): global first if state == TCPServer.PORT_IN_USE: setStatusText("TCP port occupied. Restart IDE.") elif state == TCPServer.LISTENING: setStatusText("Waiting for a partner to play") if first: first = False else: removeAllActors() for i in range(shiplib.nbShips): addActor(shiplib.Ship(), getRandomEmptyLocation()) elif state == TCPServer.CONNECTED: setStatusText("Client connected. Wait for partner's move!") elif state == TCPServer.MESSAGE: shiplib.handleMessage(server, state, msg) def onNotifyExit(): server.terminate() dispose() makeGameGrid(6, 6, 50, Color.red, False, mousePressed = onMousePressed, notifyExit = onNotifyExit) addStatusBar(30) for i in range(shiplib.nbShips): addActor(shiplib.Ship(), getRandomEmptyLocation()) show() port = 5000 first = True server = TCPServer(port, stateChanged = onStateChanged) shiplib.node = server Client: from gamegrid import * from tcpcom import TCPClient import shiplib def onMousePressed(e): loc = toLocationInGrid(e.getX(), e.getY()) shiplib.handleMousePress(client, loc) def onStateChanged(state, msg): if state == TCPClient.CONNECTED: setStatusText("Connection established. You fire!") shiplib.isMyMove = True elif state == TCPClient.CONNECTION_FAILED: setStatusText("Connection failed") elif state == TCPClient.DISCONNECTED: setStatusText("Server died") shiplib.isMyMove = False elif state == TCPClient.MESSAGE: shiplib.handleMessage(client, state, msg) def onNotifyExit(): client.disconnect() dispose() makeGameGrid(6, 6, 50, Color.red, False, mousePressed = onMousePressed, notifyExit = onNotifyExit) addStatusBar(30) for i in range(shiplib.nbShips): addActor(shiplib.Ship(), getRandomEmptyLocation()) show() host = "localhost" port = 5000 client = TCPClient(host, port, stateChanged = onStateChanged) client.connect()
|
MEMO |
Auch das Ende des Spiels muss sorgfältig programmiert werden. Da es sich um einen "Ausnahmezustand" handelt, verwendest du ein globales Flag isOver, das bei Spielende auf True gesetzt wird. Es stellt sich auch die Frage, ob das Spiel ohne Neustart des Programm noch einmal gespielt werden kann. In dieser Implementierung ist es so, dass der Client sein Programm beenden muss, der Server aber dann wieder in den LISTENING-Zustand geht, und auf einen neuen Client wartet. Die Programme sind in vielerlei Hinsicht noch verbesserungsfähig. Bei der Spielprogrammierung sind der Phantasie und dem Einfallsreichtum des Entwicklers kaum Grenzen gesetzt. Aber nach erfolgreicher Arbeit ist auch das Spielen ein Vergnügen. Damit die beiden Spielpartner ihre Programme nicht gemäss der Vorschrift "Zuerst der Server und dann der Client" starten müssen, könnte auch der folgende Trick angewendet werden: Ein Programm startet immer zuerst als Client und versucht einen Verbindung zum Server zu erstellen. Misslingt dies, so startet es einen Server [mehr... Man nennt ein solches System auch "Peer-to-Peer-Communication"] Die beiden Programme sind in diesem Fall identisch]. |
KOMMUNIKATION MIT HANDSHAKE |
Bei der Kommunikation zwischen zwei Rechnersystemen ist es wichtig, dass die zeitliche Abfolge der Aktionen zwischen den beiden Knoten synchronisiert wird. Insbesondere dürfen Daten erst dann gesendet werden, wenn der Empfänger auch tatsächlich für die Empfang und die Weiterverarbeitung bereit ist. Die Missachtung dieser Regel kann zum Datenverlust oder gar zum Blockieren der Programme führen. Unterschiedliche Rechenleistung der beiden Knoten und eine variable Übertragungszeit müssen dabei berücksichtigt werden. Ein bekanntes Verfahren, um diese Timing-Probleme in den Griff zu kriegen, besteht darin, Rückmeldungen des Empfängers an den Sender vorzusehen, die man mit einem einvernehmlichen Händedruck (Handshake) zwischen zwei Partnern vergleichen kann. Das Verfahren ist grundsätzlich einfach: Daten werden blockweise übertragen und der Empfänger quittiert den richtigen Empfang und und die Bereitschaft für den nächsten Block mit einer Rückmeldung. Erst wenn diese eintrifft, wird der nächste Block gesendet. Die Rückmeldung kann bei einem fehlerhaften Empfang auch dazu führen, dass der Sender den gleichen Block nochmals überträgt [mehr... Unterschiedliches zeitliches Verhalten kann auch durch Implementierung von Puffern (Queues, Fifo-Warteschlangen) ausgeglichen werden]. Um das Handshake zu demonstrieren, zeichnet in deinem Programm die Turtle des Servers relativ langsam Linien, die der Client mit Mausklicks vorgibt. Die Turtle des Clients bewegt sich dabei viel schneller als die des Servers. Der Client muss also von Klick zu Klick warten, bis der Server zurückmeldet, dass er die Linie fertig gezeichnet hat und bereit für ein nächstes Kommando ist. Server: from gturtle import * from tcpcom import TCPServer def onCloseClicked(): server.terminate() dispose() def onStateChanged(state, msg): if state == TCPServer.MESSAGE: li = msg.split(",") x = float(li[0]) y = float(li[1]) moveTo(x, y) dot(10) server.sendMessage("ok") makeTurtle(mouseHit = onMouseHit, closeClicked = onCloseClicked) port = 5000 server = TCPServer(port, stateChanged = onStateChanged) Client: from gturtle import * from tcpcom import TCPClient def onMouseHit(x, y): global isReady if not isReady: return isReady = False client.sendMessage(str(x) + "," + str(y)) moveTo(x, y) dot(10) def onCloseClicked(): client.disconnect() dispose() def onStateChanged(state, msg): global isReady if state == TCPClient.MESSAGE: isReady = True makeTurtle(mouseHit = onMouseHit, closeClicked = onCloseClicked) speed(-1) host = "localhost" port = 5000 isReady = True client = TCPClient(host, port, stateChanged = onStateChanged) client.connect()
|
MEMO |
Um die Aktionen von Sender und Empfänger in eine geordnete zeitliche Abfolge zu bringen, führt man Rückmeldungen des Empfängers an den Sender ein und verhindert, dass der Sender weitere Daten senden kann, bevor die Rückmeldung eingetroffen ist. |
AUFGABEN |
|
|