Moin!
Ich beschäftige mich zurzeit mit Netzwerkprogrammierung in Python. Ich
möchte gerne ein kleines Spiel schreiben. Das Spiel soll auf einem
Server laufen, mit dem sich dann mehrere Clients verbinden können um an
dem Spiel teilzunehmen.
Mein Problem ist, dass dieses Spiel es erfordert, dass die Clients
identifiziert werden müssen. Wie kann ich sie identifizieren?
Die unten stehenden Code-Ausschnitte sind weitestgehend mit den
Beispielen in der Python Dokumentation des Paketes "socketserver"
identisch.
Der Servercode zeigt, dass ich versucht habe die Clients anhand des
Threadnamens zu identifizieren. Das klappte jedoch nicht, weil die
ursprüngliche (Beispiel-) Implementierung von send() jedes mal eine neue
Verbindung öffnet und am Ende wieder schließt. Deshalb habe ich mir
gedacht, dass ich einfach das öffnen/schließen vor/nach Funktionsaufruf
einmalig ausführe (siehe unten).
In dieser Version klappt der erste Aufruf von send() auch. Der zweite
funktioniert auch, es kommt jedoch keine Antwort zurück und der Server
reagiert auch nicht. Beim dritten Mal dann folgende Fehlermeldung:
1
sock.sendall(bytes(message+"\n", 'utf8'))
2
ConnectionAbortedError: [WinError 10053] Eine bestehende Verbindung wurde softwaregesteuert
3
durch den Hostcomputer abgebrochen
Ich schließe daraus, dass ich eine TCP-Stream-Verbindung nur für eine
Übertragung/Verbindung nutzen kann. Das würde dann bedeuten, dass ich
mein Protokoll so auslegen muss, dass der Server dem Client bei der
ersten Verbindung eine Art ID gibt, mit der der Client den Server von
dortan kontaktieren muss.
Gibt es dafür eine elegantere oder sogar vorgefertigte Lösung? Könnte
ich die Verbindung irgendwie "offen" halten und dann den Client anhand
des sockets (bzw. dessen filedescriptor) identifizieren? Wie wird
soetwas normalerweise gelöst?
PS: Es muss unter Windows XP laufen. Ich verwende das aktuelle Python
3.3.1
Server:
1
import socket
2
import threading
3
import socketserver
4
import settings
5
from tables import ServerDurakTable
6
7
tables = []
8
tables.append(ServerDurakTable())
9
10
class ThreadedTCPRequestHandler(socketserver.StreamRequestHandler):
11
def handle(self):
12
cur_thread = threading.current_thread()
13
print("aktueller thread: "+str(cur_thread))
14
this_table = tables[0] #es gibt bislang nur diesen einen
15
cmd = str(self.rfile.readline().strip(), "utf-8")
16
answer = this_table.handle_cmd(cmd, cur_thread)
17
print("request: "+str(cmd))
18
print("answer: "+str(answer))
19
self.wfile.write(bytes("test", "ascii"))
20
21
22
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
23
pass
24
25
26
27
server = ThreadedTCPServer((settings.host, settings.port), \
28
ThreadedTCPRequestHandler)
29
ip, port = server.server_address
30
31
# Start a thread with the server -- that thread will then start one
Du must pro client eine TCP Verbindung aufbauen, i.A. läuft das
folgendermaßen:
- Server wartet auf Anfrage an Port
- Client stellt Anfrage
- Server Empfängt die Anfrage und erhält für diesen Client ein Socket
Objekt
- Server und Client kommunizieren über den Socket
Man kann dann der einfachheitshalber pro client einen Thread machen,
durch dne Thread/Socket ist auch jeder Client "identifiziert".
Argh!
Vielen Dank, Läubi!
Dann verstehe ich jetzt auch, was der socketserver macht. Er wartet auf
die Anfrage, startet einen neuen Thread und ich muss dann innerhalb
dieses Threads die Kommunikation über die gesamte Laufzeit hinweg
abwickeln.
Ich hatte erwartet, dass der socketserver die Sockets verwaltet und bei
jeder eingehenden Übertragung den entsprechenden Thread startet.
Die Lösung ist also in der handle()-Methode eine while-schleife laufen
zu lassen. Sooo einfach... :-/
Wie löst du das Problem mit Verbindungsabbrüchen?
Endet dann das Spiel? Was wenn zwei oder mehr Verbindungen abbrechen und
die sich wieder reconnecten, wie machst du die Zuordnung der
Neuverbindungen zu den ursprünglichen Spielern?
Aus Neugier: Was für ein Spiel hast du denn implementiert?
Das sind alles ausgezeichnete Fragen. Die habe ich mir selbst auch schon
gestellt...
Der Server soll verschiedene Kartenspiele anbieten. Derzeit gibt es nur
"Durak" und das funktioniert auch noch nicht vollständig.
Die Verbindungswiederaufnahme ist für mich bislang ein eher unwichtiges
Thema. Wie gesagt könnte ich an die Benutzer IDs vergeben, an denen sie
später identifiziert werden können.
Momentan werden bei einem Verbindungsabbruch die Karten desjenigen
Benutzers einfach verworfen. Es wäre auch möglich das Spiel dann
komplett zu beenden oder neu zu starten. Vermutlich aber nicht besonders
Nutzerfreundlich ;-)
Silvan König schrieb:> Das sind alles ausgezeichnete Fragen. Die habe ich mir selbst auch schon> gestellt...>> Der Server soll verschiedene Kartenspiele anbieten. Derzeit gibt es nur> "Durak" und das funktioniert auch noch nicht vollständig.
Nett. :) Ist es später mal geplant, dass dann nur du Serverinstanzen
laufen hast oder dass man selbst einen aufsetzen kann?
> Die Verbindungswiederaufnahme ist für mich bislang ein eher unwichtiges> Thema. Wie gesagt könnte ich an die Benutzer IDs vergeben, an denen sie> später identifiziert werden können.>> Momentan werden bei einem Verbindungsabbruch die Karten desjenigen> Benutzers einfach verworfen. Es wäre auch möglich das Spiel dann> komplett zu beenden oder neu zu starten. Vermutlich aber nicht besonders> Nutzerfreundlich ;-)
Hängt an dem Server ein Datenbankbackend? Die erste Möglichkeit wäre
natürlich eine User/PW Authentifikation (später dann gerne auch über SSL
Sockets).
Hängt natürlich auch immer etwas vom konkreten Spiel ab, wie man
crashende Spieler behandelt. Bei Poker ist es leicht, man setzt den
Spieler auf aussetzen und schmeißt ihn nach ner Zeit vom Tisch. Bei
anderen Spielen ist es etwas komplizierter.
Hast du dir für die Kommunikation zwischen Client und Server ein
Protokoll ausgedacht? Welche Aufgaben übernimmt bei dir der Server und
welche die Clients?
Wie kams zur Entscheidung das in Python zu machen? Spaß an der Freude?
Ich frag deshalb so neugierig, weil ich selber auch gerne Spiele
(nach-/)programmiere und mich interessiert wie andere Leute gewisse
Probleme lösen. Vor kurzem habe ich ne Browserversion von Wizard
(http://de.wikipedia.org/wiki/Wizard_%28Spiel%29) umgesetzt (dreiste
Eigenwerbung: http://sorcerer-online.com). Ich habe im Moment noch das
Problem, wenn einer am verlieren ist könnte er einfach nicht mehr
weiterspielen, dann steht das Spiel. Habe aber vor, dass durch ein
Zeitkonto und einen Autoplay-Mechanismus zu lösen, d.h. wenn eine
bestimmte Zeit abläuft macht der Spieler automatisch einen zufälligen
Zug.
Das ist ein privates Projekt. Es ist nur für Freunde gedacht, damit wir
zusammen weiter Durak (und vielleicht später auch anderes) spielen
können.
Der Server soll (sobald er soweit ist) bei mir 24/7 auf einem Raspberry
Pi laufen.
Ich habe Python gewählt, weil ich an dieser Sprache zum einen ihre
Einfachheit schätze und zum anderen, weil ich vorher noch nie mit
Threads und Sockets gearbeitet habe. Dank der interaktiven Shell kann
man bequem Sachen testen. Beispielsweise besteht mein "Client" bislang
nur aus einem Thread, der die Antworten des Servers entgegen nimmt und
ausgibt und aus einer Funktion "send()", mit der ich direkt Nachrichten
auf Protokollebene an den Server schicken kann.
Das Protokoll ist wirklich primitiv. Der Client sendet
"<Befehl><Leerzeichen><Argumente>\n" an den Server. Mehrere Argumente
werden durch Kommata getrennt.
Als Antwort kommt dann "<Typ><Leerzeichen><Inhalt>\n" zurück. Wobei je
nach Befehl mehrere dieser Nachrichten zurückkommen können. Es kann auch
vorkommen, dass ein anderer Spieler eine Aktion ausführt, durch die eine
Nachricht an mehrere Spieler verschickt wird.
Beispiel:
Client->Server: showCards\n
Server->Client: yourCards Pik-Bube,Karo-8,Herz-Koenig,Kreuz-7,Kreuz-8\n
Der Server kontrolliert den gesamten Spielfluss. Es wird dort geprüft,
ob ein Spieler die Karte auf der Hand hat, die er ausspielen will, ob er
sie legen darf und welcher Spieler an der Reihe ist. Wer als nächster an
der Reihe ist wird benachrichtigt. Wer versucht einen illegalen Zug
auszuführen oder nicht an der Reihe ist, wird benachrichtigt.
Wenn das irgendwann so funktioniert, wie ich es gerne hätte, dann würde
ich es gerne auf C++ mit wxWidgets implementieren. Ein Android-Client
wäre auch genial, aber das ist beides noch lange nicht spruchreif ;-)
Silvan König schrieb:> Das ist ein privates Projekt. Es ist nur für Freunde gedacht, damit wir> zusammen weiter Durak (und vielleicht später auch anderes) spielen> können.>> Der Server soll (sobald er soweit ist) bei mir 24/7 auf einem Raspberry> Pi laufen.>> Ich habe Python gewählt, weil ich an dieser Sprache zum einen ihre> Einfachheit schätze und zum anderen, weil ich vorher noch nie mit> Threads und Sockets gearbeitet habe. Dank der interaktiven Shell kann> man bequem Sachen testen. Beispielsweise besteht mein "Client" bislang> nur aus einem Thread, der die Antworten des Servers entgegen nimmt und> ausgibt und aus einer Funktion "send()", mit der ich direkt Nachrichten> auf Protokollebene an den Server schicken kann.
Wär da nicht auch eine Alternative, dass mit Django (oder RoR) als
Webapplikation zu bauen und einfach auf Heroku zu hosten? Dann könnten
auch noch andere Leute das Spiel spielen. Hast du jetzt eigentlich ein
Datenbankbackend, bzw. was passiert wenn der Server abraucht? Noch dazu
kann dann jeder Browser als Client dienen.