tcp zufälliges würfeln

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
vicarious
User
Beiträge: 2
Registriert: Samstag 29. November 2014, 14:12

Hallo,

ich hab eigentlich ein ganz einfaches Skript, welches unter UDP prima lief, nun aber auf TCP umgeschrieben werden soll, um dann in eine Grid geschickt zu werden, um verteiltest rechnen zu üben.
Dabei geht es darum die Gleichverteilung von zufällig gewürfelten Zahlen zu zeigen, d.h. dass bei großen Zahlen, alle Augenzahlen in etwa gleichoft geworfen worden sind.

Allerdings gibt mein Skript bei Senden alle Werte in einem Schub zurück und eben nicht als einzelne Werte, weswegen des Programm nicht terminiert.

Hier mal der Code:
Server:

Code: Alles auswählen

#!/usr/bin/python2
import math
import socket
import random


TCP_IP = '127.0.0.1'
TCP_PORT = 5005
BUFFER_SIZE = 128  # Normally 1024, but we want fast response

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(1)

roll = [0,1,2,3,4,5]

conn, addr = s.accept()
print "Connection address: " , addr
while 1:
    data = conn.recv(BUFFER_SIZE)
    N = int(data)
    for x in range(0,N):
        dice = random.randint(0,5)
        roll[dice] = roll[dice] + 1
    for x in range(0,6):
        data = str(roll[x])
        conn.send(data)  # echo
    for x in range(0,6):
        roll[x] = 0
    if not data: break

conn.close()
Client:

Code: Alles auswählen

#!/usr/bin/python2

import socket
import math
import random

TCP_IP = '127.0.0.1'
TCP_PORT = 5005
BUFFER_SIZE = 8

DATA = raw_input("Anzahl der Wuerfe: ")
N = int(DATA)

roll = [0,1,2,3,4,5]
Ni1 = N + math.sqrt(N)
Ni2 = N - math.sqrt(N)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
s.send(DATA)
for x in range(0,6):
    print str(x) + "recieved!"
    roll[x] = int(s.recv(BUFFER_SIZE))
    print str(roll[x])
s.close()
for x in range(0,6):
    if roll[x] < Ni1 and roll[x] > Ni2:
        print str(x+1) +  " times " +  str(roll[x]) + "in range"
    else:
        print str(x+1) + " is not in range"
print str(Ni1)
print str(Ni2)
Meine Frage ist, wie kann ich die for-Schleife im Client anpassen, dass er die Werte nicht in einem String bekommt? Und warum hat das in UDP funktioniert?
BlackJack

@vicarious: Das hat bei UDP funktioniert weil da bei jedem Senden und Empfangen kleine, voneinander unabhängige Datenpakete über's Netz gehen. TCP dagegen ist ein Datenstrom. Wenn Du da so etwas wie mehrere Pakete/Nachrichten über die gleiche *Verbindung* haben willst, dann musst Du selbst dafür sorgen das beim senden nicht zu viel gepuffert wird, und vor allem beim Empfangen Code schreiben der den Datenstrom wieder in einzelne Pakete/Nachrichten auftrennt um die einzeln Verarbeiten zu können. Dabei haben das was in einem `send()`/`sendall()` gesendet wurde, und das was bei einem `recv()` empfangen wird nicht zwingend miteinander zu tun. Du musst beim Empfangen damit klar kommen können wenn jedes `recv()` nur ein Byte empfängt, also auch damit das ein `recv()` mehr Daten liefern kann als zu einer Nachricht gehören.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@vicarious: Dein Code ist an vielen Stellen kaputt. Der Kommentar zu BUFFER_SIZE ist quatsch. recv gibt sofort den TCP-Puffer zurück, wenn auch nur 1 Byte darin ist. Die BUFFER_SIZE gibt nur eine obere Grenze an. Warum wird »roll« mit gerade diesen Werte initialisiert? Warum bekommt die 5 5 Würfe Vorsprung? Wie BlackJack schon geschrieben hat, ist nicht sicher gestellt, dass »recv« die komplette gesendete Zahl enthält.
In Zeile 25 iterierst Du über den Index der Liste »roll«, statt direkt über die Elemente. Da Du die Zahlen ohne irgendwelche Trennzeichen sendest, wirst Du auch nur einen Haufen Ziffern bekommen, die Du nicht wieder in 6 Zahlen aufspalten kannst.
Warum setzt Du nach der Rechnung alle Elemente von «roll» wieder auf 0? Es ist viel klarer vor einer Berechnung die Liste zu initialisieren, als darauf zu hoffen, dass sie schon irgendwie sinnvolle Werte enthält.
Die Schleife wird nie ohne Exception beendet, da «data» nicht den ursprünglichen Wert von »recv« enthält, sondern die letzte Zahl der Liste »roll« aus Zeile 26 und ist damit niemals leer. Auch wenn »data« das enthält, was Du denkst, wäre schon in Zeile 21 ein Fehler aufgetreten.
vicarious
User
Beiträge: 2
Registriert: Samstag 29. November 2014, 14:12

Erstmal danke für die schnelle Hilfe.
Ich habe das Skript angepasst und es funktioniert auch soweit. Allerdings weiß ich noch nicht, wie ich den Server nun vernünftig beende. Ich dachte, dass der s.close den Socket terminiert.
Leider hat uns niemand gesagt, wie der Code aussehen sollte (exception-handling etc hatten wir nicht ), sondern einfach mal machen lassen, mit dem tollen Zusatz, dass das unser zweites Python Programm ist.

Code: Alles auswählen

#!/usr/bin/python2
#Server tcp
import math
import socket
import random


TCP_IP = '127.0.0.1'
TCP_PORT = 5005
BUFFER_SIZE = 1024 # Normally 1024, but we want fast response

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((TCP_IP, TCP_PORT))
s.listen(1)

roll = [0,0,0,0,0,0]

conn, addr = s.accept()
print "Connection address: " , addr
while 1:
    data = conn.recv(BUFFER_SIZE)
    N = int(data)
    
    for x in range(0,N):
        dice = random.randint(0,5)
        roll[dice] = roll[dice] + 1
        data = ""
    for x in range(0,6):
        data += str(roll[x]) + ","
        print data
    conn.send(data)  # echo
conn.close()

Code: Alles auswählen

#!/usr/bin/python2
#Client tcp
import socket
import math
import random

TCP_IP = '127.0.0.1'
TCP_PORT = 5005

BUFFER_SIZE = 1024
buf = 0 
i = 0

DATA = raw_input("Anzahl der Wuerfe: ")
N = int(DATA)

roll = [0,0,0,0,0,0]
Ni1 = N/6 + math.sqrt(N)
Ni2 = N/6 - math.sqrt(N)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((TCP_IP, TCP_PORT))
s.send(DATA)
data = s.recv(BUFFER_SIZE)
s.close()

for x in range(len(data)):
    if data[x] == ",":
        roll[i] = int(data[buf:x])
        buf = x + 1
        i = i +1

for x in range(0,6):
    if roll[x] < Ni1 and roll[x] > Ni2:
        print str(x+1) + " " +  str(roll[x]) + " times " +  str(roll[x]) + " in uniform distribution"
    else:
        print str(x+1) + " " + str(roll[x]) + " is not in uniform distribution"
print str(Ni1)
print str(Ni2)
BlackJack

@vicarious: Das schliessen des Sockets beim Client sollte den Server beenden. Der bricht dann mit einer Ausnahme ab weil Du da dann versuchst die leere Zeichenkette in einer Zahl umzuwandeln, was nicht funktioniert. Womit auch das ``conn.close()`` nach der Schleife nie explizit ausgeführt werden kann, denn die ``while``-Schleife wird ja nie auf normalem Weg verlassen.

Für Wahrheitswerte gibt es in Python den Typ `bool` und dessen Werte `True` und `False`. Da sollte man keine Zahlen für missbrauchen.

Die Probleme mit `recv()` hast Du immer noch. Der Aufruf garantiert das mindestens ein Byte gelesen wird, aber er garantiert nicht das alle Daten gelesen werden die auf der anderen Seite abgeschickt wurden. Ein ”nacktes” `recv()` ohne zusätzlichen Code der sicherstellt das man das so oft macht bis alle Daten vollständig beisammen sind, macht nur in dem Sonderfall sind, dass man den Datenstrom byteweise verarbeitet. Was sehr sehr selten vorkommt. Ähnliches gilt für `send()`. Der Aufruf gibt Anzahl die tatsächlich gesendeten Bytes zurück, das muss nicht alles sein was man als Argument übergeben hat! Auch hier muss man in einer Schleife so lange `send()` mit den (Rest)Daten aufrufen bis auch tatsächlich sicher alles raus ist. Oder man verwendet `sendall()`, was genau das für einen macht.

Da das Python 2 Code ist solltest Du mit `range()` vorsichtig sein. Das erzeugt eine *Liste* mit den ganzen Zahlen, nimmt also Speicherplatz in Anspruch. Und beim Server hängt die Menge von dem Datum ab welches ein Client sendet. Da kann man mit einer schön grossen Zahl den Serverspeicher lustig vollmüllen. Wenn man die Liste mit den Zahlen nicht benötigt nimmt man besser `xrange()`.

Beim Server macht es wenig Sinn `data` bei jedem Durchlauf der ersten ``for``-Schleife wieder und wieder an eine leere Zeichenkette zu binden.

Schleifen über Indexlaufvariablen nur um mit denen dann auf die Elemente von einer Sequenz (Liste, Zeichenkette, …) zuzugreifen sind in Python ein „anti pattern”. Man kann in einer Schleife *direkt* über die Elemente von Sequenzen iterieren, ohne den Umweg über einen Index. Insbesondere etwas nach dem Muster ``for i in range(len(sequence)):`` ist fast immer unnötig umständlich. Falls man den Index *zusätzlich* zum jeweiligen Element benötigt, gibt es die `enumerate()`-Funktion.

Zeichenketten mit einer Trennzeichenkette zusammenfügen oder zu trennen macht man nicht mit ``+`` oder selbstgebasteltem „slicing” — dafür gibt es die Methoden `join()` und `split()` auf Zeichenketten.

Namen komplett in Grossbuchstaben sind per Konvention für Konstanten. Weder `DATA` noch `N` erfüllen dieses Kriterium.

Das „vorinitialisieren” von Variablen am Anfang die erst viel später tatsächlich benötigt werden ist unüblich und macht Programme unübersichtlicher weil zusammengehörige Code-Teile so über den ganzen Quelltext verstreut werden. Es erschwert deshalb auch das isolieren und herausziehen von Funktionen, weil man sich dann erst alles dafür zusammensuchen muss.

Darüber was bei ``N/6`` heraus kommt wenn `N` eine ganze Zahl ist (in Python 2 ohne ``from __future__ import division``), solltest Du noch mal nachdenken. Gegebenenfalls mal ausprobieren.

Man kann Vergleichsoperationen verketten und damit eine kürzere, aus der Mathematik bekannte, Formulierung verwenden wenn in zwei Vergleich jeweils ein Operand der selbe ist. Also statt ``a op1 b and b op2 c`` kann man auch schreiben ``a op1 b op2 c``, wenn op1 und op2 binäre Operatoren sind.

Zeicheketten und Werte mit `str()` und ``+`` zusammensetzen ist eher BASIC als Python. In Python gibt es für so etwas die `format()`-Methode auf Zeichenketten.

Einzelwerte die man mit ``print`` ausgibt braucht man nicht explizit mit `str()` umwandeln, das macht ``print`` schon selbst.

Edit: Ergänzend zum unnötig frühen ”initialisieren” von Variablen: `rolls` beim Client muss überhaupt nicht vorbelegt werden. Man erstellt einfach aus den vom Server empfangenen Daten eine Liste mit den Werten.
Antworten