Coderblog

Chat via Socketserver und JavaScript Teil 1

Posted by Christian Weber on Donnerstag, Februar 19th, 2009

Ich werde eine kurze Übersicht über die benötigten Techniken, Klassen und Funktionen geben um einen SocketServer, sowie das benötigte Frontend zu schreiben.

continue reading

Back

Posted by Christian Weber on Donnerstag, Februar 19th, 2009

Endlich habe ich mal wieder etwas Zeit zu bloggen und werde mich künftig doch mit etwas komplexeren Themen beschäftigen. Das werden Dinge wie Socketbridges, gdLib2 etc. sein. Wer Interesse hat etwas mehr über mich zu erfahren sollte mal auf mein Portfolio schauen oder sich mein aktuelles [...]

continue reading

Chat via Socketserver und JavaScript Teil 1

Posted by Christian Weber on Donnerstag, Februar 19th, 2009

bookmark bookmark bookmark bookmark bookmark bookmark bookmark bookmark bookmark


Dass Sockets via PHP zu programmieren sind, ist mittlerweile vielen bekannt; doch dass man über einen kleinen Trick Sockets auch ohne Probleme in JavaScript nutzen kann, ist den Meisten noch weitgehend fremd. Ich habe diese Techniken bei CWidget erfolgreich verwendet und möchte hier einen kurzen Einblick in die Umsetzung geben. Im ersten Teil dieses Artikels werden wir uns lediglich mit der serverseitigen Programmierung beschäftigen.

Um einen Socket-Server effektiv betreiben zu können, ist ein Root-Server dringend notwendig, denn der Socket-Server muss kontinuierlich als Dienst laufen. Zum Testen und Entwickeln kann man ihn vorerst auch im Browser starten; doch nach einiger Zeit wird es unweigerlicher zum Stillstand des Scripts kommen.

Fangen wir mit den Grundeinstellungen unseres Mini-Chat-Servers an:

#!/usr/bin/php
< ?php
set_time_limit(0);
ob_implicit_flush();
 
$address = "1.2.3.4";
$port = 1234;

Was hier genau passiert ist im Prinzip nichts Weltbewegendes; nichts desto trotz sind diese Zeilen extrem wichtig für das korrekte Arbeiten des Socket-Servers:

#!/usr/bin/php

Diese Zeile macht im Prinzip nichts außer festlegen, dass die PHP Datei in der Shell als normaler Prozess ausgeführt werden kann. Bitte passt hier auch euren Pfad zu PHP an.

set_time_limit(0);
ob_implicit_flush();

set_time_limit(0) sorgt dafür, dass der Server durchgehend aktiv bleibt, indem es die maximale Laufzeit eines PHP Scriptes ignoriert. ob_implicit_flush() hilft uns mit den Ausgaben. Hierdurch können echos und Ähnliches direkt ausgegeben werden, ohne dass das Script fertig geladen sein muss. $address und $port benötigen wir später für den Verbindungsaufbau zum eigenen Server. Die IP muss die des eigenen Servers sein und der Port kann ein gewünschter Port sein, auf dem man auf eingehende Verbindungen warten möchte.

Kommen wir nun zum nächsten Teil unseres Socket-Servers. In diesem Abschnitt werden wir die Verbindung aufbauen und auf den gewünschten Ports auf eingehende Verbindungen warten:

	if (($master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) > 0) {
		echo "Fehler: " . socket_strerror($master) . "\n";
		exit();
	}
 
	socket_set_option($master, SOL_SOCKET,SO_REUSEADDR, 1);
 
	if (($ret = socket_bind($master, $address, $port)) > 0) {
		echo "Fehler: " . socket_strerror($ret) . "\n";
		exit();
	}
 
	if (($ret = socket_listen($master, 5)) > 0) {
		echo "Fehler: " . socket_strerror($ret) . "\n";
		exit();
	}

if (($master = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) > 0) {
echo "Fehler: " . socket_strerror($master) . "\n";
exit();
}

Hier stellen wir den Master-Socket ein, über den alles laufen wird. Detaillierte Informationen zu den Einstellungsmöglichkeiten findet ihr auf php.net.


if (($ret = socket_bind($master, $address, $port)) > 0) {
echo "Fehler: " . socket_strerror($ret) . "\n";
exit();
}

Hier verbinden wir unseren Master-Socket mit dem oben definierten Port und der IP. Wie sonst sollte unser Socket wissen von wo die gewünschten Daten kommen sollen? :)


if (($ret = socket_listen($master, 5)) > 0) {
echo "Fehler: " . socket_strerror($ret) . "\n";
exit();
}

Jetzt weisen wir unseren Socket an auf eingehende Verbindungen zu warten. Der übergebene Parameter ‚5’ steht hierbei für einen Backlog. Dies ermöglicht bei vielen Anfragen eine interne Schleife zur weiteren Verarbeitung. Mehr hierzu auf php.net

Jetzt kommen wir zum Herzstück unseres Socket-Servers: Der Endloschleife. Da dieser Teil sehr umfangreich ist, habe ich mir erlaubt, hier auf PHP-Kommentare zurückzugreifen. Falls es von eurer Seite Einwände oder Missverständnisse geben sollte, werde ich mich darum bemühen diesen Teil nochmal abzuändern.

// Socketvariable
	$allsockets = array($master);
 
	// Abfragen ob die Verbindung unseres Mastersockets steht, falls nicht Server beenden.
	if($master && $ret) {
		// Endlosschleife starten
		while (true) {
			// Wir speichern die Socketliste in ein neues Array
			$changed_sockets = $allsockets;
			// Wir holen uns alle Sockets, die ansprechbar sind
			$num_changed_sockets = socket_select($changed_sockets, $write = NULL, $except = NULL, 1);
			// Wir durchlaufen alle ansprechbaren Sockets
			foreach($changed_sockets as $socket) {
				// Überprüfung ob es ein neues Socket ist
				if ($socket == $master) {
					// Wir überprüfen ob es gelingt eine Verbindung zu dem Socket aufzubauen
					if (($client = socket_accept($master)) < 0) {
						// Falls nicht, geben wir einen Fehler aus
						echo "fehler: " . socket_strerror($msgsock) . "\n";
						continue;
					} else {
						// Verbindung wurde erfolgreich aufgebaut, wir fügen Ihn zu unserer Socketliste hinzu
						array_push($allsockets, $client);
					}
				} else {
					// Da es kein neues Socket ist, durchlaufen wir die bestehenden Sockets
					// als erstes lesen wir das Socket komplett aus
					$bytes = socket_recv($socket, $buffer, 2048, 0);
					// Falls 0 bytes zurückommen und im buffer keine flash policy request vorkommt, ist der client tot
					if ($bytes == 0 && $buffer !== '<policy-file-request/>') {
						// Socket in array suchen
						$index = array_search($socket, $allsockets);
						// aus socketliste löschen
						unset($allsockets[$index]);
						// verbindung zum socket schliessen
						socket_close($socket);
 
					} 
					// Wir überprüfen ob es sich um ein policy-file-request handelt, welches von einem Flashsocket immer gesendet wird. Dies ist sehr wichtig für Teil 2 unseres Artikels, da wir indirekt eine Flash-Socketverbindung aufbauen werden.
					elseif (preg_match("/policy-file-request/i", $buffer) || preg_match("/crossdomain/i", $buffer))
					{
					  // Flash erwartet eine Antwort als XML. Hier wird die IP/Domain sowie der Port definiert. * sind auch hier als WildCard möglich. Ohne diese Antwort wird Flash keine Verbindung aufbauen. Dies gibt ein Stück Sicherheit. Flash wird bei Erfolg eine normale Socketanfrage stellen, aus diesem Grund wird diese Verbindung nach dem senden des Policy-Files wieder geschlossen.
					  $contents='< ?xml version="1.0"?><cross -domain-policy><allow -access-from domain="DOMAIN" to-ports="PORT" /></cross>';
					  // xml senden
					  socket_write($socket,$contents);
					  // socket suchen
					  $index = array_search($socket, $allsockets);
					  // socket aus socketliste löschen
					  unset($allsockets[$index]);
					  //verbindung zerstören
					  socket_shutdown($socket, 2);
					  // verbindung zum socket schliessen
					  socket_close($socket);
					} 
					// falls eine verbindung via get / post oder http kommt, zerstören
					elseif (( preg_match("/GET/", $buffer) || preg_match("/POST/", $buffer)) && preg_match("/HTTP/", $buffer))
					{
					  if (preg_match("/favicon.ico/i", $buffer))
					  {
						//ignorieren 
					  }
					  else
					  {
						// ignorieren
					  }
					  // socket suchen
					  $index = array_search($socket, $allsockets);
					  // socket aus socketliste löschen
					  unset($allsockets[$index]);
					  // verbindung zerstören
					  @socket_shutdown($socket, 2);
					  // verbindung zum socket schliessen
					  @socket_close($socket);
					} else {
						// wir speichern die nachricht des sockets zur weiteren verarbeitung
						$msg = $buffer;
					}
 
					// Auf valide Daten uberpruefen
					if(isset($msg) && !empty($msg) && trim($msg) !== '') {
						// überprüfen ob der socket in der socketliste vorhanden ist, falls nicht fügen wir ihn hinzu, da er neu ist
						if(isset($allsockets[$socket])) {
							// Nun senden wir das Geschriebene eines Sockets, an alle anderen Sockets
							foreach($allsockets as $s) {
								// Nachricht an Socket senden
								socket_write($s,$msg);
							}
						} else {
								// socket zur socketliste hinzufügen
								$allsockets[$socket] = $socket;
								// wir schicken dem user eine kurze verbindungsbestätigung
								socket_write($socket,"<strong>Erfolgreich verbunden</strong><br />");
 
							} 
							// nachricht löschen um speicher freizugeben
							unset($msg);
						}
					} 
 
 
				}
 
			}
			// Dies ist extrem wichtig, damit der Server stabil laufen kann. Dadurch macht der Server bei jedem Durchlauf 10 Mikrosekunden Pause. Das ist nicht spürbar, aber entlastet den Server extrem.
			usleep(10);
		}
	} else {
		// fehler ausgeben, da server port belegt ist.
		echo "Port ist noch belegt.";
	}

Nun haben wir ein funktionsfähigen Chatserver auf PHP-Basis, doch wie gehts weiter? Dieser Chatserver ist extrem simpel und erlaubt lediglich das Kommunizieren mit Leuten die sich auch zur selben Zeit in diesem Chat befinden. Es gibt keine Channels, Usernamen o.Ä. Das ist natürlich relativ mager. Man sollte nun Authentifizierungsmethoden, Channels, Private Messaging und besonders Prozesskontrolle einbauen. Ihr wollt sicherlich nicht, dass euer Server mehrfach läuft. Hierfür gibt es einige nützliche PHP- und Linux-Funktionen.

Im nächsten Teil werde ich beschreiben, wie man via JavaScript zu solch einem ChatServer eine Verbindung aufbauen kann, um darüber zu chatten.

Posted in: Featured, PHP.

1 Star2 Stars3 Stars4 Stars5 Stars (3 votes, average: 5.00 out of 5)
Loading ... Loading ...

2 Responses to “Chat via Socketserver und JavaScript Teil 1”

  1. Chat via Socketserver und JavaScript Teil 1 | PHP-Blog.com Says:

    [...] the original: Chat via Socketserver und JavaScript Teil 1 Related ArticlesBookmarksTags There are no related articles. Digg it Stumble [...]

  2. Adrianus Says:

    Nabend. Eine etwas unpassende Frage aber wie kann ich diesen Blog zu meinem Google Feedreader hinzufuegen? Finde keinen Link. Auf jedenfall ein toller Beitrag.

Leave a Reply

Spam Protection by WP-SpamFree