mmofacts.com

Script wird nur zu 1/2 ausgeführt

gepostet vor 19 Jahre, 11 Monate von MannaZ
HALT! - zuerst komplett lesen, bevor ein vorschnelles Kommentar abgegeben wird!

Also Grundsätzlich:
Wenn der User eine Aktion startet- zB durch klick auf einen Link oder durch FormSubmit wird ja die neue Seite mit dem PHPScript geldaden.

Wenn der User jetzt - während die neue Seite noch läd - nocheinmal auf den "Aktivator" klickt bricht das Script am aktuellen Status ab, und startet sich von neuem.

Problem:
Wenn ich Werte (zB Geld, Resourcen...) von einem User zum anderen "verschieben" möchte wir der Wert zwar am neuen User aufgebucht, jedoch noch nicht abgebucht, wenn der User erneut läd.
Dadurch kann er sozusagen Resourcen aus dem nichts "erschaffen".

Was ich schon probiert habe:
ignore_user_abort(0) auf 1 gesetzt
bei Form`s submit once scripts eingefügt (Problem: tritt auch bei links auf)

Zusätzlich ist mir aufgefallen, das diese Methode nur bei Internet Explorer von Microsoft funktioniert. Mit meinen aktuellen Versionen von Firefox und Mozilla läuft das Script am Server ganz normal weiter.


Ich hoffe ich habe meinen Fehler deutlich genug beschrieben und hoffe auf klärende Antworten & Vorschläge.

MannaZ
gepostet vor 19 Jahre, 11 Monate von Mudder
Du solltest dir evtl. Gedanken darum machen wiso ein Script, welches vom User ausgeführt wird, so lange braucht!
Spieler sind kleine dumme Idioten die sich tierisch freuen wenn der Programmierer nen Rechtschreibfehler gemacht hat und welche auch mit ner ISDN-Leitung erwarten das die Seite sofort da ist.

Und da das Script auch vor allem beim IE auftritt siehts ehr nach nem Strukturfehler deiner Seits aus..


===
In diesem Beitrag wurden viele schlechte Dinge über kleine, dumme Spieler gesagt. Um keinen Ärger mit der Eltervereinigung zu bekommen möchte ich mich bei den lieben, schlauen und knuddeligen Spielern entschuldigen und betonen, dass dies nur als Anschauungsbeispiel gemeint war.
Allerdings gibts hier keine lieben, schlauen und knuddeligen Spieler.. Schade!
gepostet vor 19 Jahre, 11 Monate von MannaZ
Erstmal danke für die relativ schnelle Antwort.

Das Script ist nicht langsam - es besteht jediglich aus einer Datenbankabfrage und zwei Datenbankupdates - und hat eine Laufzeit von weniger als 300 ms


Auf das Problem hat mich ein erlicher User aufmerksam gemacht, der sich durch "Klickenwiewild" ein Vermögen ergaunert hat. Habs daraufhin mit allen möglichen Browsern probiert und es trat NUR mit IE auf.

Was kann von der Struktur her der Fehler sein?
Meinst du PHP code Struktur, oder gar HTML ?
- falls HTML: ich glaub nicht, das man bei einem Link viel bei der Struktur falsch machen kann

MannaZ
gepostet vor 19 Jahre, 11 Monate von zodiac2k
Eine andere Möglichkeit wäre die Auslagerung in eine Transaktionssteuerung, die sekündlich vom Server aufgerufen wird und bei der "ganz in Ruhe" die Bedingungen geprüft werden.
Nicht so elegant ;-)

Die bessere Lösung wäre eine Transaktionssichere Datenbank und mit Start Transaction / Commit / Rollback arbeiten.
gepostet vor 19 Jahre, 11 Monate von MannaZ
Original von zodiac2k
Die bessere Lösung wäre eine Transaktionssichere Datenbank und mit Start Transaction / Commit / Rollback arbeiten.


Sorry, aber ich hab keinen Plan wovon du redest.
Bin mit den Spezialausdrücken nicht so bewand.
gepostet vor 19 Jahre, 11 Monate von schokofreak
Lösung für Minimalisten ( 2 Schritte):

Schritt 1:
=======
Ganz am Anfang des Scriptes eine Session starten.
Ganz am Ende des Scriptes die Session erst beenden.

Grund:
In einem (normalen / sauber aufgesetztem) System ist eine Session blokierend. Das heist?
Wenn SessionA nun eine URL anfrägt...
dort wird gerechnet (zwischen Session Open und Session Close): sagen wir mal 10 sekunden.
Nun wird von Session A noch eine andere URL aufgerufen. Sobald dort das Session Open kommt, so wird GEWARTET bis der erste Aufruf das Session Close erreicht hat.

Auch bekannt als Frame Lade Problematik in PHP... Hier ist es allerdings ausnahmsweise ein Vorteil.

Schritt 2:
=======
 

func final_func() {
if(processingdata==true) {
//sollt ned passieren; rollback durchführen
}
session.close;
}
session open:
processingData = true;
ignore_user_abort(true);
register_shutdown_function(final_func);
//todo... daten
processingData = false;


dem aufmerksamen leser fällt sicher auf, dass schritt 2 im prinzip sauber codierter schgritt 1 ist. Problem adee...

Gruss
gepostet vor 19 Jahre, 11 Monate von Qmaster
Ist es nicht

Unter anderem kann man alle wichtigen routinen eine prüfvariable verpassen.

Beim reingehen auf false setzen. Keine andere Aktion kann dann vom Spieler ausgeführt werden, egal wie schnell er klickt. Beim verlassen wieder auf true.

Und schwups hat man zumindest kein schaden vom scriptabbrechen.
gepostet vor 19 Jahre, 11 Monate von BLUESCREEN
Original von MannaZ
Auf das Problem hat mich ein erlicher User aufmerksam gemacht, der sich durch "Klickenwiewild" ein Vermögen ergaunert hat.

Dieses Problem kannst du nicht mit dem ignore_user_abort lösen.
Statt dessen musst du wie oben bereits vorgeschlagen entweder Transaktionen benutzen oder einfach die betroffenen Tabellen sperren ("LOCK TABLES").
gepostet vor 19 Jahre, 11 Monate von MannaZ
@ Schokofreak:
Deine Beispiele scheinen mir sinnvoll, nur liegt das problem ja darin, das das script eben nicht bis zum ende durchgelaufen wird (trotz ignore_user_abort).
DH auch die "final func" nicht aufgerufen wird.
gepostet vor 19 Jahre, 11 Monate von KEEN
Ne ganz einfache Lösung, die auch mit der ganz normalen MyISAM MySql Datenbank funktioniert.

Bei allen Aktionen bei der eine Aktion durchgeführt werden soll, gibt man eine zusätzliche Variable mit, in meinem Spiel seite.php?a=1
 

$spieler['lock'] = 0;
If(isset($_REQUEST['a']) && is_numeric($_REQUEST['a']))
{
$aendern = "UPDATE spieler Set lock = 1 WHERE uid = '$spieler[uid]' && lock = '0'";
$update = mysql_query($aendern);
$spieler['lock'] = mysql_affected_rows();
If($spieler['lock'] != 1){$game['stop'] = 13;}
}


Erst wenn das Script vollständig aufgeführt wurde, wird am Ende der Seite der Wert in der Spalte lock wieder auf 0 gesetzt.

Erklärung:
Wird eine Aktion durchgeführt, wird versucht die Spalte lock von 0 auf 1 zu setzten. Ist die Spalte bereits auf 1 liefert mysql_affected_rows() die Zahl 0. Die Variable $game['stop'] verhindert, dass die Aktion ein weiteres mal durchgeführt wird. mysql_affected_rows() liefert nur dann 0 wenn das andere Script (z.B. duch doppeltes Abschicken eines Formulars) noch nicht abgeschlossen ist.

Ich hoffe die Erklärung war verständlich, es ist eine Lösung die absolut zuverlässig ist, aber den Nachteil hat, dass durch einen Serverabsturz oder Bug die Spalte am Schlusss nicht wieder zurück auf 0 gesetzt wird. Aber auch dafür gibt es Lösungen.
gepostet vor 19 Jahre, 11 Monate von schokofreak
HALT, HALT, HALT

1. Lock Table ist extrem gefährlich und bewirkt mit Garantie, dass euch Irgendwann so alle 1 bis 2 Monate. Genau dann, wenn du vor einem Werbekunden für ne Demo stehst, dass das ganze nicht mehr funktioniert.
-> Es ist Death Lock gefärdet. Zudem äusserst UNPERFORMANT und noch viel extremer "nicht sauber"

2. Keens lösung ist noch viel schlimmer. Die Lösung bewirkt, dass es anstatt 2 Monate wohl mehr so 2, 3 Wochen geht bis einfach mal nichts mehr geht. Einzig ist noch viel schlimmer, dass nur so für 1 User nix mehr geht.
-> Alle user fällt auf; 1 User geht entweder zum support (viel aufwand für Supporter) oder löscht
Des weiteren ist Keens lösung grobfahrlässig und sollt mit einer standesgemässen Hinrichtung des Entwicklers abgegolten werden.

@ QMaster: Du hast im prinzip recht. Nur, wenn da 2 Anfragen genau parallel kommen, hast du das Problem auch nicht gelöst. du löst das Problem nur, wenn ein user 10 fenster parallel öffnet, und dann 10 mal den selben Request absetzt. -> auch nicht des rätsels lösung.


SEHR wichtig ist, dass das ignore_user_abort drinnen ist. Wenn das nicht geht, könnte dies an der Server Config liegen (Safe mode? Kenn mich da nicht so genau aus). Oder es liegt an irgend einem komischen Fehler im PHP script (frag mich ned was).

===>>> kuck dass dir das ignore_user_abort funzt; wenns ned funzt hacke nach da das essentiell ist

Danach gibt es 2 Möglichkeiten.
Man kann das ganze auf DB Ebene sperren. Da seien mal die Begriffe Transaktionen, stored Procedures, ... erwähnt. Funktioniert in MySQL leider NICHT so wie es sollte... -> kann immer wieder zu problemen führen

Man kann das ganze pro user session lösen.
-> Session Trick; siehe oben.
NACHTEIL: Mehrere userSession können immer noch parallel auf einen Record zugreiffen.
Da lässt sich am einfachsten damit arbeiten, dass man von hand FallBack Methoden schreibt; die sind meist sseeeehr simpel.
 

select wert from tabelle;
-> wert irgendwie umrechnen; DARF ich z.B. wert = wert - x machen?
=> wert update
affectedrows = update tabelle set wert -= x where wert=alterWert;
//WICHTIG: Mit Relativen veränderungen, ned absoluten arbeiten
if(affectedrows < 1) return;
affectedrows = update tabelle2 set wert -= x where wert=alterWert;
if(affedtedrows < 1) {
//rollback
update tabelle1 set wert+= X;
}


Obiges Konstrukt funktioniert theoretisch und praktisch fehlerlos.
-> In Zusammenhang mit einer Session Sperre (siehe Oben) ist es auch sehr effektiv.
Unschönheit ist einzig, dass rollback von Hand gecodet werden muss.
=> Fehleranfälligkeit

Deshalb. Für simple sachen ist sowas gut... wirds komplex; besser Transaktionen nehmen. -> wobei ich mir einen Wechsel weg von PHP überlegen würde.

Praktiker Ansatz:
============
-> Session Trick einbauen.
Sense.

Nun können so gut wie alle schon nix mehr machen. Dass sie auf die Idee kommen müssten, 2 Accounts parallel zu Scripten um einen Erfolg zu erhalten. Darauf müsste man kommen. UND das Timing für die 2 Parallelen Requests ist schon seeeehr knapp.
Wenn man dann noch sauberes sql codet (NIE Absolute werte setzen!) Ist selbst im worstCase so gut wie kein Schaden vorhanden.

Gruss
gepostet vor 19 Jahre, 11 Monate von knalli
Dieses "Von-Hand-Rollback-Check" Verfahren verwenden wir erfolgreich, es funktioniert und verhindert nebenbei auch unschöne Dinge wie "Geld < 0".
Wenn man einmal nach dem Schema arbeitet, kann man nicht mehr viel falsch machen.. problematisch wird ggf nur, wenn das Rollback Query nicht erfolgreich durchgeführt wird.. (Überlastung)
gepostet vor 19 Jahre, 11 Monate von BLUESCREEN
Original von schokofreak
HALT, HALT, HALT

1. Lock Table ist extrem gefährlich und bewirkt mit Garantie, dass euch Irgendwann so alle 1 bis 2 Monate. Genau dann, wenn du vor einem Werbekunden für ne Demo stehst, dass das ganze nicht mehr funktioniert.
-> Es ist Death Lock gefärdet. Zudem äusserst UNPERFORMANT und noch viel extremer "nicht sauber"

Schön übertrieben.
Es funktioniert bestens, ist sicher und der wohl einfachste Weg zur Lösung des Problems.
Das einzige, was stimmt ist, dass es die Performance etwas runterzieht, weil MySQL kein zeilenweises Locking unterstützt, sondern immer die kompletten Tabellen sperrt.

Und zu den Deadlocks wirst du mit etwas googlen rausfinden, dass das verhindert wird.

@MannaZ: Hast du schonmal versucht, ignore_user_abort in der php.ini zu aktivieren anstatt über die Funktion?
gepostet vor 19 Jahre, 11 Monate von MannaZ
Danke für euere zahlreichen Vorschläge und Tipps.

Das mit der Sicherheit war nie ein Problem, ich verwende den Session Trick in verbindung mit einer Laufzeitvariablen.

Das einzige problem ist das mit dem ignore_user_abort().

Ich hatte früher
ignore_user_abort(1)
was nicht funktioniert hat, und versuche jetzt
ignore_user_abort(TRUE)
wovon ich noch keine Ergebnise habe.


Wenn das auch nicht hinhaut muss ich mich an meinen Provider wenden, da ich keinen direkten Zugriff auf die php.ini habe um ignore_user_abort in der php.ini zu aktivieren .
gepostet vor 19 Jahre, 11 Monate von KEEN
Original von schokofreak

2. Keens lösung ist noch viel schlimmer. Die Lösung bewirkt, dass es anstatt 2 Monate wohl mehr so 2, 3 Wochen geht bis einfach mal nichts mehr geht.
...Des weiteren ist Keens lösung grobfahrlässig und sollt mit einer standesgemässen Hinrichtung des Entwicklers abgegolten werden.


Sag mal schokofreak, woher nimmst du die Arroganz, darüber so mal schnell zu Urteilen ohne es je mal selber getestet zu haben. Sollte deine Aussage auf tiefen fundiertes Wissen beruhen, wäre ich über ein PM dankbar, damit dieser "grobfahrlässige" Diletant hier noch was lernen darf :wink:

Nachdem ich es über ein halbes Jahr mit über 2000 Spielern erfolgreich getestet habe, kann ich mit gutem Gewissen sagen, dass es zu 100% reibungslos funktioniert. Alle paar Wochen kommt bei einem Spieler mal ein Lock vor, dann wird der Spieler auf eine Fehlerseite umgeleitet, dort kann er den Lock wieder aufheben.
gepostet vor 19 Jahre, 11 Monate von zodiac2k
KEEN, das ist eine Lösung aber sicher nicht die eleganteste. Wie du schon sagst, tritt das Problem von Zeit zu Zeit auf, bzw dieses Locking kommt ab und zu vor... genau dieses ab und zu sind erste Grenzbereiche oder Auslastungshochpunkte der Datenbank. Die ist noch nicht mit 2k Usern erreicht und kommt bei den meisten Spielen je nach Server erst im 4k-5k Bereich regelmässig vor zu den Peakhours.
Das macht dir jetzt noch keine Probleme, aber es wird zu einem Problem werden.. schokofreak hat vielleicht etwas übertrieben, aber im Prinzip liegt er garnicht so falsch.
gepostet vor 19 Jahre, 11 Monate von KEEN
Obs der beste Weg ist, darüber lässt sich natürlich streiten. Deshalb hab ich in meinem ersten Post bereits auf diesen kleinen Nachteil hingewiesen. Ich fand seinen Ton und die Überheblichkeit von schokofreak nur einfach nicht angebracht.

Das die Schwachstellen erst bei einer Serverüberlastung deutlich werden ist klar. Das Spiel lief früher mal auf einem sehr schwachen Server, der bei 180 Usern gleichzeitig online deutlich seine Grenzen erreichte. Von daher ist das System schon hinreichend getestet.
gepostet vor 19 Jahre, 11 Monate von TheUndeadable
Nutzt doch einfach File-Locking. Das wird auch bei Abbruch des Skriptes einwandfrei wieder gelöst.
gepostet vor 19 Jahre, 11 Monate von schokofreak
Original von TheUndeadable
Nutzt doch einfach File-Locking. Das wird auch bei Abbruch des Skriptes einwandfrei wieder gelöst.

Kopfnick...

Ist eine der einfachsten und wirkungsvollsten Methoden.
Sämtliche anderen, mir bekannten, Methoden benötigen zwingend, dass man ein Script so konfigurieren kann das es nicht abbricht.
-> ignore_user_abort und Konsorten.

FileLocking ist zwar auch Death-Lock gefährdet, allerdings lässt es sich da viel besser kontrollieren.
-> Bei Locks z.B. in der DB kann man nie so 100 % sicher sein, z.B. in welcher Reihenfolge die Locks ausgeführt werden...
=> denkt nur mal, was da so alles passieren könnte bei einem P-Connect oder einem irtümlichen Abbruch eines Scriptes?

Wieso ich sage, dass man das ganze nicht so machen sollte?
Aus dem einfachen Grund:
- über kurz oder Lang wirst du wegen solch einem Fehler grosse Fehler in deinem Datenbestand haben; Sehr viel Source neu schreiben.

Während man bei gewissen Methoden:
- Session Lock
- FileLock

absolut Kein Verwaltungsaufwand hat, mit sehr hoher Sicherheit.
Bieten einem die Methoden:
- manuelles RollBack (siehe mein beitrag)
- MySQL NICHT-Transaktionen (aus dem grund da das in Mysql oft probleme gibt)

nur minim verbesserte Sicherheit gegenüber Session / File Lock.

Und Methoden wie:
- Tabellen Lock
Bieten keine weitere Sicherheitssteigerung; dafür aber sehr grosse Fehlerquellen. Denkt daran, pro 100 Zeilen Code stecken mit garantie 2, 3 Fehler.

Dann sind noch Methoden wie:
- Row Locking handMade
welche im Bezug auf Sicherheit acuh nichts neues bringen; aber die Fehlerrisiken noch viel stärker in die höhe treiben.

Fazit:
Die Obersten bieten einfach so gut wie dei beste Sicherheit; vorallem im Vergleich zur Fehleranfälligkeit.
Dass ein Betriebssystem Locking nicht beherrscht könnt ihr als nicht realistisch annehmen. Und Variante File Lock und Session lock funktionieren genau so.

Für den inSider: SessionLock ist implizip ein FileLock.
-> Session eines Users wird in einem File gespeichert.
=> Session open öffnet File; File bleibt gesperrt.
Aus File werden Daten De-Serealisiert
bei session Close werden date Serealisiert
File geschrieben
File Freigegeben.

Unterschied ist jedoch, dass File Lock im Sinne von TuD viel mächtiger ist, da er explizit für gewisse CodeSchnipsel Locks macht (nicht nur für eine parallelisierung von Anfragen einzelner user).

Gruss
gepostet vor 19 Jahre, 11 Monate von BLUESCREEN
Original von TheUndeadable
Nutzt doch einfach File-Locking. Das wird auch bei Abbruch des Skriptes einwandfrei wieder gelöst.

Worauf bezieht sich das nun?
Und welche Dateien meinst du?

@schokofreak:
Das einzig sichere ist, die Funktionen der Datenbank zu nutzen: Locking und Transaktionen.
Die Datenbank sorgt dann schon dafür, dass nichts schief läuft, wenn z.B. ein Script abbricht.

Ich benutze seit ca. einem Jahr die Locking-Methode und hatte bisher dadurch keinen einzigen Fehler. Es gibt auch keinen Grund, warum es welche geben sollte.
gepostet vor 19 Jahre, 11 Monate von TheUndeadable
Du erzeugst einfach eine Datei in einem temporären Verzeichnis und setzt ein Lock drauf. Möchte nun ein anderes Skript ebenfalls diese Datei erzeugen, so wartet es solange, bis die Datei freigegeben wurde. Der Inhalt der Datei ist leer, die Datei ist einfach nur ein Instrument zum Locken.

Mein Locking Code, jeder Programmierer sollte die Klasse nach seinem Gutdünken verändern können.

 


class DeponNetLocking
{
var $m_szDir; // Directory, in which locking files can be created
var $m_aFiles; // Associative Array of filehandels


function Lock ( $szSection, $bRead = true )
{
if ( isset ( $this->m_hFile [ $szSection ] ) )
{
return true;
}

if ( $bRead )
{
// Read lock

$this->m_hFile [ $szSection ] = @fopen ( "{$this->m_szDir}$szSection", 'r' );

if ( $this->m_hFile [ $szSection ] )
{
flock ( $this->m_hFile [ $szSection ], LOCK_SH );
}
}
else
{
// Write lock

$this->m_hFile [ $szSection ] = fopen ( "{$this->m_szDir}$szSection", 'w' );

if ( $this->m_hFile [ $szSection ] )
{

flock ( $this->m_hFile [ $szSection ], LOCK_EX );
}

}
}

function Unlock ( $szSection )
{
if ( $this->m_hFile [ $szSection ] )
{
flock ( $this->m_hFile [ $szSection ], LOCK_UN );

fclose ( $this->m_hFile [ $szSection ] );

unset ( $this->m_hFile [ $szSection ] );
}

}
};




Ich ruf dann nur noch $oLocking->Lock ( 'resources' ); auf und dann weiß ich, dass nur noch eine Instanz über die Rohstoffe wacht (konsequentes Locking in allen Skripts vorausgesetzt)
gepostet vor 19 Jahre, 8 Monate von Cays
(Habe nur die ertse und die letzte Seite gelesen... also wenn sowas schon jemand geschrieben hat, entschuldige ich mich)
Also ich hatte auch so ein Problem....
Ich habe es gelößt, indem ich ech den Wert" $_SESSION[count] = 0;" erstellt habe... gleich beim Login. Sobald das Script aufgerufen wird wird der Counter um eins erhöht. er steht also auf "1" dann habe ich per
if($_SESSION[cont] == 1)
{
#script
}
else
{

window.location.href="zielscript";

}
Überprüft ob das Script schon einmal im vorraus ausgeführt wurde. Wenn ja wird auf ein Skriot weitergeleitet, das den Wert wieder auf 0 setzt.
Weiß nicht ob es dir hilft. Aber wenn ja freue ich mich, dass ich dir helfen konnte.
MfG.Cays

Auf diese Diskussion antworten