Forum: PC-Programmierung Socket offen halten/Client identifizieren (Python)


von Silvan K. (silvan) Benutzerseite


Lesenswert?

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
32
# more thread for each request
33
server_thread = threading.Thread(target=server.serve_forever)
34
# Exit the server thread when the main thread terminates
35
server_thread.daemon = True
36
server_thread.start()
37
print("Server loop running in thread:", server_thread.name)

Client:
1
import settings
2
import socket
3
4
ip = settings.host
5
port = settings.port
6
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
7
sock.connect((ip, port))
8
    
9
10
def send(message):  #beispiel
11
    sock.sendall(bytes(message+"\n", 'utf8'))
12
    response = str(sock.recv(1024), 'utf8')
13
    print("Received: {}".format(response))

von Läubi .. (laeubi) Benutzerseite


Lesenswert?

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".

von Silvan K. (silvan) Benutzerseite


Lesenswert?

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... :-/

von D. I. (Gast)


Lesenswert?

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?

von Silvan K. (silvan) Benutzerseite


Lesenswert?

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 ;-)

von D. I. (Gast)


Lesenswert?

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.

von Silvan K. (silvan) Benutzerseite


Lesenswert?

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 ;-)

von D. I. (Gast)


Lesenswert?

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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.