Chat auf Basis von Memcache

M1cha

Grünschnabel
Erstmal Hi, bin neu hier und freue mich dieser Community anzugehören ;)

Ich hab damit angefangen, einen Chat auf Basis von PHP/Memcache zu programmieren.
Dieser funktioniert bis auf eine grundlegende Sache auch recht gut:
Das Array $key, das auf dem Memcache-Server liegt anthält alle Nachrichten.
Neue Nachrichten werden mit $key[]=... hinzugefügt.
Das Problem: Ich muss Das Array aus dem Cache in eine Variable schreiben, die Neue Nachricht anfügen, und das Array in dem Cache damit aktualisieren.
Wenn aber in dem Moment, wo Chatter1 dass Array bearbeitet, Chatter2 das Array mit seiner neuen Nachricht aktualisiert, so geht eben genau diese Nachricht(von Chatter2) in dem Moment verloren, wo Chatter1 das Array aktualisiert.

Lange Rede, kurzer Sinn, aber ich hoffe, es versteht jeder.
Demo: http://ajax-shoutbox.com/board/Chat/gui.php
Download des Scripts: Anhang anzeigen memcache_chat.zip
 
Hi, eine gute Lösung für solche Probleme sind Semaphoren. Leider bietet PHP diese nicht in der Standardinstallation und soweit ich weiss auch nur auf LINUX-Systemen. Falls diese Lösung bei dir nicht einsetzbar ist, gibt es noch eine sehr einfache Alternative: du könntest mit der Funktion flock() arbeiten. Das ganze kann dann ungefähr so aussehen:
PHP:
$file = 'lock.dat'; // Beliebige Datei (muss nicht unbedingt schon existieren) die zur Kontrolle benutzt wird
$fp = fopen($file, 'w'); // Datei öffnen.
flock($fp, LOCK_EX); // Schreibzugriff für andere sperren (Die anderen Skripte halten dann hier so lange an, bis die Datei wieder freigegeben wird).

// Hier jetzt die Daten aus dem MemCache holen, die Nachricht anfügen und zurückspeichern. Evtl. daran denken, dass das Array nicht zu viele Nachrichten enthalten sollte, also eine alte Nachricht löschen, wenn was neues dazukommt.

flock($fp, LOCK_UN); // Sperre aufheben.
fclose($fp); // Datei schließen.
// unlink($file); // Optional: Damit kannst du die Datei nach Verwendung löschen lassen. Ist eigentlich nur sinnvoll, wenn du sehr viele Sperrdateien benötigst. In deinem Fall könntest du es sicher weglassen.
 
Da wir einen Linux-Root-Server besitzen, wäre ein fehlendes PHP-Plugin kein Problem, das einzige Problem ist nur, dass ich nicht genau verstehe wofür die Funktion "sem_get" ist.
Aber die Lösung mit flock ist sehr gut.
Trotzdem würden mich Semaphoren interessieren und eventuell auch ob deren Abarbeitung schneller ist als flock.

Mit freundlichen Grüssen, M1cha
 
Hi, also Semaphoren müssten deutlich schneller sein, als die flock-Lösung, da sie direkt vom Betriebssystem verwaltet werden und keine zusätzlichen Dateizugriffe erforderlich sind. Also das Beispielt mit Semaphoren könnte so aussehen (ungetestet, da ich momentan kein Linux habe)
PHP:
$project = 'C'; // Projektbezeichner, ein Buchstabe, kann beliebig gewählt werden.
$key = ftok(__FILE__, $project); // Aus der aktuellen Datei und dem Projektbezeichner einen Schlüssel erzeugen, der als Name der Semaphore dient.
$max = 1; // Maximale Anzahl gleichzeitiger Ausführungen. In deinem Fall ist das immer 1.

$sem = sem_get($key, $max, 0777, 1); // Semaphor erzeugen.

// Wichtig: Alle Skriptaufrufe, die versuchen den Zugriff auf die selbe Semaphore zu erhalten warten an der Stelle, an der die sem_aquire() Anweisung steht und werden erst nach sem_release() weiter ausgeführt

if($sem === false)
{
  die('Semaphorfehler');
}

sem_aquire($sem); // Semaphore holen, ab hier ist sichergestellt, das immer nur 1 Skript gleichzeitig den Block bis sem_release() oder dem Ende des Skripts ausführen kann.

// Hier jetzt wie gehabt die Daten im MemCache bearbeiten.

sem_release($sem); // Semaphor freigeben, ab jetzt können andere Aufrufe des Skripts ihre Daten in den MemCache schreiben.
So, das müsste eigentlich so zeimlich alles sein.

Nachtrag: Noch ein paar Informationen zur Installation.
 
Zuletzt bearbeitet:
Die Semaphoren-Version habe ich jetzt noch nicht ausprobiert, da ich keinen root-access habe und der admin nicht on ist.

Aber kann es sein, dass wenn das array zu groß wird, teile davon verloren gehen können? weil manchmal kommt eine Nachricht die ich absende nicht im cache an.

PS: Vielen Dank für die schnellen Antworten :-)
 
Hallo und schöne Weihnachten erstmal. ;)

Ich glaube nicht, dass es da mit MemCache Probleme geben sollte, wahrscheinlich eher ein Fehler auf PHP-Seite. Versuch auf jeden Fall mal folgendes an den Anfang der PHP-Dateien zum Lesen und Schreiben des Caches zu schreiben:
PHP:
error_reporting(E_ALL);
ini_set('display_errors', true);
Evtl. solltest du dann noch bei den AJAX Anfragen mal die Antworten mittels alert() oder ähnlichem anzeigen, vor allem beim Schreiben von neuen Daten.
 
Wer glaubt es, es lag am AJAX-Script.
Wenn er den Status-Check nicht bestanden hat, hat er die Message einfach nicht gesendet.
Ich mach jetzt gar keinen CHeck mehr sondern nur noch:
Code:
xmlHttp.open('POST', 'write.php', false);
                xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                xmlHttp.send('msg='+msg+"&usr="+usr+"&usr_color="+usr_color);
                alert(xmlHttp.responseText);

Ist das ok, oder sollte kann man das besser machen?
Zu den Semaphoren: Wie heißt das Paket das man auf dem Server installieren muss? Weil wir müssen alles über apt-get machen.

EDIT: Einfach keine überprüfungen zu machen ist natürlich Blödsinn. Das funktioniert auch nicht richtig. Ich habe die Fehlerausgabe in der Datei write.php aktiviert und gebe diese an ein kontroll-DIV weiter.
Chat-Bereich:
Code:
M1kka: 1
M1kka: 2
M1kka: 3
M1kka: 4
M1kka: 7

Kontroll-DIV(Ich lasse in der write.php immer die Memcache-version ausgeben):
Code:
M1kka: 1
Memcache-Version: 1.1.12

M1kka: 2
Memcache-Version: 1.1.12

M1kka: 3
Memcache-Version: 1.1.12

M1kka: 4
Memcache-Version: 1.1.12

M1kka: 5
Memcache-Version: 1.1.12

M1kka: 6
Memcache-Version: 1.1.12

M1kka: 7
Memcache-Version: 1.1.12
Wie man sieht sind Message 5 und 6 verschwunden.

mein AJAX-Code:
Code:
// send Message
        function sendMSG()
        {
            var usr = encodeURIComponent(document.frmshoutbox.usr.value);
            var msg = encodeURIComponent(document.frmshoutbox.msg.value);
            var usr_color = encodeURIComponent(document.frmshoutbox.usr_color.value);
            if(usr == '' || msg == '') alert("Bitte fuelle Name und Nachricht aus");
            else {
                xmlHttp.open('POST', 'write.php', false);
                xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
                xmlHttp.send('msg='+msg+"&usr="+usr+"&usr_color="+usr_color);
                switch (xmlHttp.readyState) {
                    case 4:
                        if (xmlHttp.status != 200) {
                            alert("Fehler: "+xmlHttp.status);
                            setTimeout('sendMSG();',1000);
                        }
                        else {
                            document.getElementById("zwei").innerHTML+='<b>'+document.frmshoutbox.usr.value+': </b>'+document.frmshoutbox.msg.value+'<br />'+xmlHttp.responseText+'<br /><br />';
                            // Zum Ende Scrollen
                            setTimeout('Scrolling2();',1);
                            setTimeout('Scrolling2();',750);
                            setTimeout('Scrolling2();',1500);
                            document.frmshoutbox.msg.value = '';
                            document.frmshoutbox.msg.focus();
                        }
                        break;
                    default:
                        break;
                }
            }
        }
        /* */

MfG M1cha
 
Zuletzt bearbeitet:
Hi, du darfst nicht einfach so den "readyState" abfragen, stattdessen gibt es extra die Eigenschaft "onreadystatechange", der du eine Funktion übergeben musst, die dieses Abfragen dann erledigt. In deinem Beispiel sollte das ungefähr so aussehen:
Code:
// send Message
        function sendMSG()
        {
            var usr = encodeURIComponent(document.frmshoutbox.usr.value);
            var msg = encodeURIComponent(document.frmshoutbox.msg.value);
            var usr_color = encodeURIComponent(document.frmshoutbox.usr_color.value);
            if(usr == '' || msg == '') alert("Bitte fuelle Name und Nachricht aus");
            else {
                xmlHttp.open('POST', 'write.php', true);
                xmlHttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

               // Diese Funktion übernimmt die Antwort und führt die Aktionen aus, sie wird
               // immer dann ausgeführt, wenn sich der readyState ändert.
               xmlHttp.onreadystatechange = function()
               {
                   switch (xmlHttp.readyState) {
                        case 4:
                            if (xmlHttp.status != 200) {
                                alert("Fehler: "+xmlHttp.status);
                                setTimeout('sendMSG();',1000);
                            }
                            else {
                            document.getElementById("zwei").innerHTML+='<b>'+document.frmshoutbox.usr.value+': </b>'+document.frmshoutbox.msg.value+'<br />'+xmlHttp.responseText+'<br /><br />';
                                // Zum Ende Scrollen
                                setTimeout('Scrolling2();',1);
                                setTimeout('Scrolling2();',750);
                                setTimeout('Scrolling2();',1500);
                                document.frmshoutbox.msg.value = '';
                                document.frmshoutbox.msg.focus();
                            }
                        break;
                    }
                }

               // Nun ist alles bereit und der Request kann gesendet werden.
                xmlHttp.send('msg='+msg+"&usr="+usr+"&usr_color="+usr_color);
             }
        }
EDIT: Habe gerade gesehen, dass deine ursprüngliche Version eigentlich auch funktionieren müsste, da du ja einen synchronen Request machst. Probier einfach mal meine Version aus, und schau, ob der gleiche Fehler auftritt. Ansonsten kann ich dir die Firefox Extension Firebug empfehlen, die ist sehr, sehr hilfreich bei solchen Dingen.
 
Zuletzt bearbeitet:
jetzt sendet er die Messages, die normalerweise verloren gehen doppelt^^
Er gibt dann die Meldung Error:0 aus.
Chat-Feld:
PHP:
M1kka: 1
M1kka: 2
M1kka: 3
M1kka: 4
M1kka: 5
M1kka: 6
M1kka: 7
M1kka: 7
M1kka: 8

Check:
PHP:
M1kka: 1
M1kka: 2
M1kka: 3
M1kka: 4
M1kka: 5
M1kka: 6
M1kka: 7
M1kka: 8

Das Tool Firebug habe ich natürlich schon :)
nur leider hilft es mir bei diesem Problem nicht besonders weiter.
Vielleicht hat ja auch noch jemand einen tipp. Ich werde mir auch nochmal genau die AJAX-Doku durchlesen und hoffentlich finden wir dann eine Lösung.

MfG M1cha
 
Zurück