Jint – JavaScripting für .NET

Viele Spiele bieten die Möglichkeit diese per Scripting zu erweitern. Dabei kann man etwa eine Map oder eine KI mit Scriptsprachen realisieren. Sehr beliebt ist hier LUA. Leider ist jedoch LUA unter den Leuten deutlich weniger bekannt und verbreitet als JavaScript, weshalb ich neulich nach einer Möglichkeit gesucht habe Scripting per JavaScript zu ermöglichen. Da ich derzeit an einem .NET Projekt arbeite, habe ich natürlich nach entsprechenden .NET Lösungen gesucht. Hierbei bin ich auf Jint – JavaScript Interpreter for .NET gestoßen. Das ganze bietet einen vollen JavaScript Interpreter, der so ziemlich die meisten JavaScript Features unterstützt (für mehr Info’s siehe Webseite). Das Coole daran ist, dass sich auch sehr einfach .NET Objekte und Funktionen in JavaScript aufrufen lassen und umgekehrt. Hier mal ein einfaches Beispiel:

string script = @"var distance = function(x1, y1, x2, y2)
{
	return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
};";

double dist = (double)new JintEngine()
			.Run(script)
			.CallFunction("distance", 5, 5, 5, 10);

Würde man script jetzt aus einer Datei lesen, könnte man dem Benutzer ermöglichen eine eigene Funktion zur Berrechnung einer Distanz zu definieren. Das gute hierbei ist auch, dass man genau Kontrollieren kann auf welche Objekte aus .NET der Benutzer Zugriff hat. (JEngine.AddPermission(), JEngine.AllowClr, JEngine.DisableSecurity())
Dadurch entstehen auch keine Sicherheitsprobleme. Man kann sogar die maximale Anzahl von Rekursionen begrenzen (JEngine.SetMaxRecursions).

Ein .NET Objekt an ein Script zu übergeben geht auch sehr einfach:

public class Randomizer {
	private Random r = new Random();
	
	public int getRandomInt()
	{
		return r.Next();
	}
}

int random123 = (int)new JintEngine()
	.setParameter("Randomizer", new Randomizer())
	.Run(@"var random_number = Randomizer.getRandomInt();
	return random_number + 123;");

Das ganze ist also wirklich einfach zu verwenden und bietet sehr viele Möglichkeiten. Man könnte ein Poker-Spiel programmieren und dann KIs per JavaScript ermöglichen, oder aber zum Beispiel Quests und NPCs in Rollenspielen per JavaScript programmieren. Ein weiterer Vorteil ist, dass die JavaScripts zur Laufzeit geladen werden können, und jeder Zeit neugeladen werden können. Das heißt auch das Testen geht wesentlich einfacher! 🙂

 

C++ CGI und mySQL

Ein CGI-Programm in C++ zu schreiben ist – eigentlich – ziemlich einfach:

#include 

using namespace std;

int main()
{
	cout << "Content-Type: text/html" << endl << endl;
	
	cout << "Hello CGI" << endl;
	cout << "

C++ says Hi to the CGI API

" << endl; cout << "" << endl; return 0; }

Das ganze kann man jetzt durch einen C++ Compiler jagen, in das cgi-bin Verzeichniss des Webservers verschrieben, chmod +x, und dann zB unter http://webserver/cgi-bin/test.cgi ansehen. Nicht sehr spannend, da man diese statische Seite ja auch direkt in HTML schreiben hätte können und sich den Umweg über CGI sparen hätte können.

Interessant wird es also, wenn man mit C++ über CGI dynamische Webseiten (dynamisch nicht im Sinne von JavaScript, sondern eher ähnlich wie PHP) generieren möchte. Ein erster Schritt wäre hier, die GET-Variablen auszulesen. Die GET-Variablen werden von der CGI in die Environment-Variable QUERY_STRING geschrieben, und zwar in folgendem Format:
[key1]=[value1]&[key2]=[value2]&....&[keyN]=[valueN]
Wir müssen uns also eine Funktion zum Parsen dieses Strings schreiben:

void getVariables(std::unordered_map &variables)
{
	// fetch the query string
	char* raw_query_string = getenv("QUERY_STRING");

	// query string empty?
	if (raw_query_string == NULL) 
	{
		variables.clear();
		return variables;
	}
	
	// key and value into a stringstream, init with an empty stream
	// to make sure get-queries such as ?step=1&confirm are parsed 
	// correctly
	std::stringstream key, value;

	key.str(std::string(""));
	value.str(std::string(""));

	// are we currently parsing a key or a value?
	bool isKey = true;

	// loop until we read all raw data
	while (*raw_query_string != '')
	{
		// if we read a &, we'll change or type to key and store 
		// are previous key and value variables into out map
		// and clear them
		if (*raw_query_string == '&') 
		{ 
			variables[key.str()] = value.str();
			isKey = true; 
			key.str(std::string(""));
			value.str(std::string(""));
		}
		// if we read a =, we'll change or type to value
		else if (*raw_query_string == '=') 
		{ 
			isKey = false; 
		}
		// anything else goes into the currently active stream
		else 
		{
			if (isKey) { key << *raw_query_string; }
			else { value << *raw_query_string; }
		}
		// move pointer to next char
		raw_query_string++;
	}
	
	// store last key value pair, because query-strings 
	// normaly don't end on &
	variables[key.str()] = value.str();
	
	return variables;
}

Das Problem ist, dass die keys und values URL-codiert sind, wenn man also Text verarbeiten möchte muss man sie noch entsprechend dekodieren. Das ist aber an dieser Stelle erstmal Aufgabe des Lesers 😉

Natürlich möchte man jetzt nicht nur GET-Variablen lesen, sondern auch Daten empfangen können, die per POST geschickt werden. Diese schickt der WebServer an stdin des CGI-Scripts. Und leider nicht -terminiert, aber das ist kein Problem, da die Umgebungsvariable CONTENT_LENGTH die genaue Länge der POST-Daten enthält. Die POST Daten sind in gleichen Format wie die GET-Daten. Das Parsen funktioniert also entsprechend ähnlich, allerdings muss man erst beispielsweise per fread die Daten aus dem stdin in ein char-buffer lesen und -terminieren bevor man sie durch entsprechende parsing Funktion schicken kann.

Mit diesen zwei Funktionalitäten kann man schon eine Menge anstellen (wie zB ein Datei-basiertes Gästebuch), allerdings muss man beim Ausgeben von Benutzereingaben daran denken diese Entsprechend zu Maskieren, damit man keine XSS-Attacken und Ähnliches erlaubt. Dafür habe ich mir folgende kleine Hilfsfunktion geschrieben:

std::string htmlspecialchars(std::string input)
{
	replaceAll("&", "&", input);

	replaceAll("", ">", input);

	replaceAll("'", "'", input);
	replaceAll(""", """, input);

	return input;
}

void replaceAll(std::string search, std::string replace, std::string &str)
{
	size_t currPos = 0;
	size_t findPos;
	while((findPos = str.find(search, currPos)) != std::string::npos) 
	{
		str.replace(findPos, search.size(), replace);
		currPos = findPos + replace.size();
	}
}

Jetzt wird’s entlich interessant: Wir möchten in unserer C++ CGI-Anwendung natürlich auch (wie gewohnt aus PHP) eine mySQL-Datenbank ansprechen. Ich habe zuerst ziemlich lange mit dem MySQL Connector/C++ rumprobiert, hab das ganze allerdings nicht wirklich vernünftig zum Laufen bekommen (wenn Details gewünscht sind, kann ich gerne noch einen weiteren Blogpost dazu schreiben :-D). Viele Probleme die bei mir aufgetreten sind waren wohl nicht unbekannt und sind (teilweise) sogar ohne Lösung auf StackOverflow zu finden. Dort bin ich dann auch auf die Idee gekommen einfach die MySQL C-API zu nehmen. Gesagt getan, ich hab mir einen kleinen Wrapper dafür geschrieben und ab dort konnte ich ohne Probleme über C++ mit meiner mySQL Datenbank reden. Um die C-API zu benutzen, muss man einfach von der MySQL Homepage den „C-Connector“ runterladen, das include-Directory im Compiler mit einbinden, das lib-Directory dem Linker geben und mit der libmysql linken. Dann kann man die Funktionen per #include „mysql.h“ laden und verwenden. Je nach Projekt kann es nötig sein noch die SOCKET-Header (unter windows winsock2.h, unter linux sys/socket.h und arpa/inet.h) einzubinden.

Kleines Beispiel-Programm:

#include 

#include "mysql.h"

using namespace std;

int main()
{
	cout << "Content-Type: text/html" << endl << endl;
	
	cout << "nmySQL TEST" << endl;
	
	MYSQL *conn = mysql_init(NULL);
	
	if (mysql_real_connect(conn, "localhost", "root", "", "testdatabase", NULL, 0, NULL) == NULL)
	{
		printf("mySQL Error #%u: %s 
n", mysql_errno(conn), mysql_error(conn)); return 0; } if (mysql_real_query(conn, "SELECT name, age FROM clients") != 0) { printf("mySQL Error #%u: %s
n", mysql_errno(conn), mysql_error(conn)); return 0; } cout << "

Client age table:

" << endl; MYSQL_RES *result = mysql_store_result(conn); MYSQL_ROW row; while (row = mysql_fetch_row(result)) { printf("n", row[0], row[1]); } mysql_free_result(result); mysql_close(conn); cout << "
Name age
%s%d
" << endl; return 0; }

Da ich ja am TWLan-Projekt mitarbeite (www.twlan.org) habe ich als Test mal die Startseite und die Registration in C++ implementiert. Dafür habe ich noch eine kleine C++ HTML Template Klasse geschrieben, ist allerdings nichts besonderes. Nun habe ich die PHP-Startseite und die C++ CGI-Startseite mal (ohne Caching) gegeneinander im Punkto Geschwindigkeit gegenübergestellt:

PHP-TWLan: 0.2501s 0.0743s 0.0771s 0.0693s 0.0672s 0.0635s 0.0775s
AVG:	   0.097s

CGI-TWLan: 0.0220s 0.0321s 0.0142s 0.0354s 0.0199s 0.0194s 0.0195s
AVG:	   0.023s

Die CGI-Version ist, selbst bei nur 2-Datenbank-Abfragen, 4x schneller als der PHP-Code. Der sehr hohe Wert bei der ersten PHP-Zeitmessung erklärt sich übrigens dadurch, dass die Zend-Engine beim ersten durchlauf den PHP-Opcode cached. Ein solches Ergebnis war zu erwarten – und wenn man es dann mal selbst erlebt ist es doch sehr motivierend weitere Web-Anwendungen in C++ zu schreiben. Wenn man dann noch FastCGI unterstützt, und zB lighttpd als Webserver verwendet, dann hat man wirklich die ultimativ-schnelle Web-Anwendung 🙂

Das „TWLanCGI“ Projekt gibts im TWLan-Forum übrigens für Windows zum Download (hier).

 

WebSocketServer

Gerade eben habe ich die erste öffentliche Version meines C++ WebSocketServers veröffentlicht. Es gibt eigentlich nicht viel dazu zu sagen, alle Informationen stehen auf der Webseite: WebSocketServer.

Zusammengefasst ist es ein kleiner Server, der das HTML5-WebSocket Protokoll unterstützt. Um Anwendungen für den Server zu schreiben, kann man diese entweder in C++ oder in LUA einbauen!

 

ReTI IDE & Simulator (Technische Informatik)

Da ich aktuell an der Veranstaltung Technische Informatik an der Uni teilnehme war es im letzten Übungsblatt Aufgabe Programm-Code für einen einfachen Prozessor (ReTI oder auch RESA) zu schreiben. Das vorgeschlagene Tool dafür (Neumi) kann entsprechenden Programm-Code zwar super visualisieren, ist allerdings beim „coden“ nicht sehr hilfreich da es kein vernünftiges Feedback bei Fehlern gibt und es auch keine gute Eingabemaske für den Code gibt. Deshalb hab ich mich hingesetzt und eine kleine HTML-JavaScript-IDE mit einer ReTI-Simulation in PHP zusammengebaut. Das ganze ist unter reti.agrafix.net zu finden! Ich habe auch eine kleine Dokumentation dazu geschrieben, es sollte sich also alles von selbst erklären.
Ein paar Worte vielleicht noch zur „Simulation“: Die Simulation ist eher ein Debugger, da der Code nicht assembliert sondern direkt interpretiert wird. Das ist notwendig um zielgenaues Feedback bei (Laufzeit-)Fehlern zu geben. Deshalb ist es nicht möglich auf Speicherzellen zuzugreifen, die Anweisungen/Befehle enthalten. (Mit dieser Einschränkung konnte man dennoch alle Aufgabenstellungen bis jetzt lösen 😉 ) Solange man das also nicht benötigt, kann der Simulator den korrekten Endspeicherzustand berechnen.

Zur ReTI IDE & Simulator

Für die HTML-JavaScript-IDE bzw. dessen Code-Editor habe ich übrigens auf das CodeMirror 2 Framework gebaut. Super Framework, leider muss man erstmal ein bisschen Dokumentation lesen bis man damit zu guten Ergebnisen kommt!

 

C++ & Lua WebSocketServer

Hi,

ich möchte eigentlich nur ein kurzes Update zu meinem Projekt C++ & Lua WebSocketServer geben: Das ganze läuft schon recht gut, nur leider ist noch ein kleiner Bug in der Implementierung der HYBI10-En/Dekodierung der den Server ab und zu zum Abstürzen bringt. Das ist natürlich schlecht und muss deshalb erst noch behoben werden!

Just a small update on the C++ & Lua WebSocketServer: Due to a bug in my hybi10-implementation the release will be delayed until I fix that problem – stay tuned!

-agrafix

 

Keep your scope – jQuery.proxy

Wer kennt das nicht, man arbeitet mit jQuery und einem click-Event, und „verliert“ dadurch bei Objekten das „this“. Kleines Beispiel:

function MyObject() {
	this.hello = "Hello";
}

MyObject.prototype.clickHandler = function(event) {
	alert(this.hello);
};

var obj = new MyObject();

$('#clickMe').click(obj.clickHandler);

Wenn man jetzt auf ein gedachtes Element mit der ID „clickMe“ klickt, dann erscheint eine Fehlermeldung in JavaScript bzw. eine Alert-Box mit dem Inhalt „undefined“ – obwohl this.hello eigentlich doch „Hello“ sein sollte? Bei den jQuery Event-Handlern „verliert“ die Funktion ihr aktuelles Scope und das this zeigt dann auf den Sender des Events, in diesem Fall auf das Element mit der ID „clickMe“. Das ist zwar oft gewünscht, da man dieses nun zB mit $(this).text(‚Ich wurde geklicked‘); weiterverarbeiten kann, ist aber in meinem Beispiel oben sehr ungünstig. Deshalb bietet die jQuery-Library eine Funktion jQuery.proxy, welche eine Proxy-Funktion generiert mit der wir das Scope einer Funktion selbst beeinflussen können. Ersetzen wir Zeile 11 also mit folgendem:

$('#clickMe').click($.proxy(obj.clickHandler, obj));

Nun zeigt das this wärend unserem clickHandler-Aufruf wieder auf das eigentliche Objekt und eine Alert-Box mit dem Inhalt „Hello“ erscheint. Möchte man nun trotzdem auf das auslösende Element für das Event zugreifen, schafft

var calledBy = event.target;
// beispiel
$(calledBy).text("Link geklickt!");

abhilfe! Natürlich kann man auch seine eigene Proxy-Funktion mit direct-calling bauen, was aber den Code schon deutlich unübersichtlicher macht, allerdings viel mehr Flexibilität bietet (wieder Bezug auf 1. Beispielcode):

$('#clickMe').click((function(o) {
	return function(event) {
		o.clickHandler(event);
	};
})(obj));

Es lohnt sich also immer auch mal unter „Misc“ bzw. „Utilities“ in der Dokumentation einer Library nachsehen 😉

 

Manager’s Life Update

Gerade habe ich ein neues Manager’s Life Update eingespielt. Es enthält das versprochene Casino mit dem Poker Multiplayer!

Das Poker-Spiel ist komplett in HTML5+Canvas, JavaScript und PHP realisiert und sollte auf jedem HTML5-Endgerät funktionieren.

Außerdem habe ich noch viele kleinere Bugs behoben und ein paar Änderungen an der Sprite-Engine vorgenommen.

Viel Spaß beim Pokern! 😉

 

Poker Abstraktion

Wie bereits angekündigt wird die nächste Version von Manager’s Life ein Casino in die Spielwelt integrieren. Dabei darf natürlich kein Multiplayer-Poker fehlen!

Poker mit PHP umzusetzen ist eigentlich eine relativ einfache Sache – der schwierigere Teil ist das erkennen der Poker-Hände beim Showdown (Flush, Straight, Pair, …). Natürlich kann man einfach alle möglichen Hände hardcoden und dann einfach eine nach der anderen Abarbeiten (siehe zB phppokerengine), das ist aber nicht besonderst elegant, performant oder erweiterbar. Außerdem ist es sehr unübersichtlich und fehleranfällig. Deshalb habe ich mir gedacht, man könnte alle möglichen Poker-Hände mit einer Abstraktions-Sprache definieren und diese dann mit entsprechendem Parser zu behandeln.

Zuerst zu meiner Sprache, die ich wie folgt definiert habe:

  • Gleiche Zahlen stellen gleiche Karten-Werte dar
  • Gleiche Buchstaben stellen gleiche Farben dar
  • Ein Fragezeichen steht für eine beliebige Karte
  • Ein > sagt, dass die nächste Karte direkt auf die vorherige folgen muss.
  • Ein [ sagt, dass das nächste Zeichen kein Symbol, sondern kein expliziter Kartenwert ist
  • {x} nach einem Symbol gibt an, wie oft es wiederholt wird

Um das ganze zu konkretisieren habe ich nun alle Poker-Hände in meine Sprache übersetzt:

// pair (zB ♥A - ♦A)
1{2}

// two pair (zB ♣A - ♦A - ♠3 - ♣3)
1{2}2{2}

// three of a kind (zB ♣A - ♦A - ♠A)
1{3}

// straight (zB ♣K - ♦Q - ♠J - ♣10 - ♣9)
?>?>?>?>?

// flush (zB ♣2 - ♣Q - ♣9 - ♣10 - ♣9)
a{5}

// full house (zB ♣K - ♦K - ♠Q - ♣Q - ♣Q)
1{3}2{2}

// four of a kind (zB ♣K - ♦K - ♠K - ♣K)
1{4}

// straight flush (zB ♣K - ♣Q - ♣J - ♣10 - ♣9)
a>a>a>a>a

// royal flush (zB ♣A - ♣K - ♣Q - ♣J - ♣10)
[a>a>a>a>a

Das Parsen und Verarbeiten ist zwar etwas trickreicher, aber machbar. Ich habe einen Quick-n-Dirty PHP-Prototypen gebaut – er funktioniert, ist allerdings nicht besonderst sauber geschrieben. Im Vergleich zu der Methode „alle-Möglichkeiten-hardcoren“ entsteht übrigens noch ein weiterer Vorteil: Meine Klasse ist 7kB groß, die oben verlinkte 0,5 MB…

Die Verwendung der Klasse erfolgt so:

a>a>a>a');

$hand = array(
  array('color' => 'spades', 'card' => 'k'),
  array('color' => 'spades', 'card' => 'q'),
  array('color' => 'spades', 'card' => 'j'),
  array('color' => 'spades', 'card' => '10'),
  array('color' => 'spades', 'card' => '9')
);

if ($parser->check($hand)) {
   echo "Deine Hand enthält ein Straight Flush!
n"; } else { echo "Kein Straight Flush vorhanden.
n"; } ?>

Die Ausgabe sollte „Deine Hand enthält ein Straight Flush!“ sein.

Die Klasse findet sich dort: PokerParser.class.php

 

RedBean PHP – Top oder Flop?

Eine Datenbank-Abstraktion mit ORM kann sehr praktisch sein und viel Arbeit sparen, vorallem wenn sie so funktioniert wie RedBean-PHP.
Nun, wie funktioniert RedBean-PHP? Eigentlich relativ simpel. Anderst als bei sehr bekannten PHP-ORM-Libraries wie Doctrine oder Propel muss sich der Programmierer theoretisch gar nicht um das Layout der Datenbank kümmern. Er muss also keine Felder oder Relationen vor dem eigentlichen Programmieren definieren, sondern kann sie einfach verwenden, und RedBean-PHP kümmert sich um den Rest. Klingt ziemlich cool, oder? Hier mal ein Beispiel:

// RedBean-PHP besteht aus einer einzigen Datei
require "rb.php";

// Zur Datenbank verbinden:
// derzeit wird SQLite, MySQL und PostgreSQL unterstützt
R::setup('mysql:host=localhost;dbname=redbeantest','root','');

// ein haus erstellen
$house = R::dispense('house');
$house->color = 'blue';
$house->size = 34;
R::store($house); // in der db speichern

// einen besitzer erstellen
$owner = R::dispense('owner');
$owner->name = 'Alex';
R::store($owner); // in der db speichern

// das Haus gehört dem Besitzer
// Many-to-Many Relation, da jeder Besitzer 
// viele Häuser haben kann und ein Haus 
// mehrere Besitzer
R::associate($owner, $house);

// eine stadt erstellen
$city = R::dispense('city');
$city->name = 'Zauberstadt';

// eine One-to-Many Relation, da 
// jedes Haus nur zu einer Stadt 
// gehört
$city->ownHouse[] = $house; 

R::store($city); // speichern

// jetzt laden wir mal ein paar sachen aus der DB
// einen besitzer mit dem name Alex finden
$person = R::findOne('owner', ' name = ?', array('Alex'));

// seine häuser finden
$houses = R::related($person, 'house');
var_dump($houses); // häuser dumpen

// die häuser der stadt ausgeben
foreach ($city->ownHouse as $key => $house) {
   echo "Haus Nummer $key ist ".$house->color."n";
}

Das ganze funktioniert, sobald es eine Datenbank „redbeantest“ gibt. Es müssen keine Tabellen in der Datenbank erstellt werden, noch Forgein-Keys angelegt werden. (Weitere Beispiele und die Dokumentation sind auf der Webseite.)

Alles wunderbar, allerdings kommt hier schon ein erstes Problem auf! RedBean PHP stellt nämlich alle Forgein-Keys und dessen Relations auf ON UPDATE SET NULL, ON DELETE SET NULL. Dieses Verhalten ist aber nicht immer gewünscht, das heißt hier muss man aufjedenfall noch nach RedBean-PHP aufräumen bevor die Software in Produktion gehen kann. Ein weiteres Problem ist, dass RedBean-PHP den Typ eines Datenbankfelds nur dann optimal wählt, wenn man es mit genügend Test-Daten füttert. Ansonsten ist auch hier ein nachträgliches Aufräumen nötig. Hier kann man sich also die Frage stellen – möchte ich lieber später meine Datenbank aufräumen müssen, oder sie direkt von Anfang an korrekt designen?

Ein zweites Problem von RedBean-PHP ist Performance. Das Framework macht, vorallem bei Relations, einfach viel zu viele Datenbank-Abfragen, die bei vielen Zugriffen zu hoher Datenbanklast führen können (Abhilfe schafft hier ein Caching System, allerdings ist das nur hilfreich, wenn keine aktuellen Daten benötigt werden). Hier endet man dann also damit einige R::find und R::related durch eigene Queries zu ersetzen. (was übrigens auch prima funktioniert, aber dann nicht mehr im Sinne von ORM ist…)

Abgesehen davon ist es aber eine super Sache, man kommt aufjedenfall schnell zu Ergebnissen. Ich würde es für alle kleineren PHP-Projekte empfehlen! Für größere Projekte würde ich davon abraten, da man einfach zu schnell den Überblick verliert und man mit dem „Aufräumen“ schnell mal etwas übersieht was dann zu kritischen Fehlern führen kann.

 

HTML5 WebSocket

HTML5 unterstützt jetzt das Aufbauen von „WebSocket“-Verbindungen. Eine WebSocket Verbindung basiert ähnlich wie HTTP auf dem TCP Protokoll, baut aber eine „dauerhafte“ Verbindung auf. Die Vorteile gegenüber HTTP ist, dass eine Aktion des Servers keine vorhergehende Anfrage des Clients mehr erfordert, wodurch der Server neue Informationen fasst in Echtzeit ausliefern kann. Dadurch kann man eine Menge Traffic und viele unnötige HTTP-Requests sparen, die etwa ein AJAX-JavaScript sonst schicken würde um neue Informationen zu erhalten.
Technisch gesehen baut bei einer WebSocket-Verbindung der Browser mit dem Server eine Verbindung auf, die nach einen Handshake dann bestehen bleibt und über die dann Binär- und Textdaten geschickt werden können.

Leider hat das Protokoll im Handshake einige Sicherheitsprobleme (gehabt?) und sich deshalb ständig verändert. Dieser Artikel bezieht sich also auf die Aktuellste Version (17). Diese wird derzeit von der aktuellsten Chrome Version und anderen Webkit Browsern unterstützt, in Firefox und Opera ist WebSocket wegen der Sicherheitsprobleme derzeit deaktiviert.

Um HTML5 WebSocket zu verwenden ist zunächst ein WebSocket-Server nötig. Ich habe hier nach einer PHP-Variante gesucht und habe dann nach einer Weile folgenden gefunden: php-websocket. Diesen sollte man sich laden und dann kann man ihn per

php -q php-websocket/server/server.php

starten. Jetzt ist ein WebSocket-Server auf „localhost:8000“ zu erreichen. Dort sind derzeit zwei Module geladen, einmal das Modul „echo“ und einmal das Modul „chat“. (Ich werde mich hier nicht mit der Entwicklung eines WebSocket-Server weiter befassen) Das „echo“ Modul nimmt einfach Anfragen entgegen und schickt diese dann an alle Verbundenen Sockets weiter.

Nun kümmern wir uns um die HTML5-Seite von WebSocket, dem User-Frontend. Dafür bauen wir uns erstmal eine einfache HTML5 Seite:





HTML5 WebSocket Test





// hier wird dann das WebSocket-Script reinkommen, siehe unten




Log

Chatbox

Say something

Für die einfache Verwendung des WebSockets habe ich mir eine kleine Wrapper-Library (websocketlib.js) programmiert:

/**
 * Small wrapper library for HTML5 WebSocket
 * 
 * @author Alexander Thiemann 
 * @version 1.0
 * 
 * @param string WebSocket host, eg. ws://localhost:8010/echo
 * @param function Function to be called when socket is connected
 * @param function Function to be called when socket recieves data, takes 1 argument message
 * @param function Function to be called for logging purposes, takes 1 argument log-message
 * 
 * @see http://blog.agrafix.net/2011/11/html5-websocket/
 */
var WebSocketLib = {
	init: function(host, onConnect, onRecv, onLog) {
		this.socket = new WebSocket(host);
		
		this.onLog = onLog;
		this.onRecv = onRecv;
		this.onConnect = onConnect;
		
		this.onLog('Initializing Socket. State: ' + this.socket.readyState);
		
		this.socket.onopen = this._onOpen;
		this.socket.onmessage = this._onMessage;
		this.socket.onclose = this._onClose;
	},
	
	send: function(message) {
		WebSocketLib.socket.send(message);
	},
	
	_onClose: function() {
		WebSocketLib.onLog('Socket Closed.');
	},
	
	_onMessage: function(msg) {
		WebSocketLib.onLog('Recieved new Message from Server: ' + msg.data);
		WebSocketLib.onRecv(msg.data);	
	},
	
	_onOpen: function() {
		WebSocketLib.onLog('Socket is Ready. State: ' + WebSocketLib.socket.readyState);
		
		if (WebSocketLib.socket.readyState == 1) {
			WebSocketLib.onConnect();
		}	
	}
}

Ich denke dort ist alles relativ selbsterklärend. Das WebSocket ist wirklich sehr leicht zu verwenden! Um diese Wrapper-Library zu verwenden, bauen wir in obiges HTML-Grundgerüst bei „// hier wird dann das WebSocket-Script reinkommen, siehe unten“ folgendes ein:

$(document).ready(function() {
	WebSocketLib.init('ws://localhost:8010/echo', 
	function() {
		sockReady();
	},
	function (t) {
		$('#chatbox').append($("

").text(t)); }, function (t) { $('#logbox').append($("

").text(t)); }); function sockReady() { $('#chat').submit(function(e) { e.preventDefault(); var input = $('input[name="message"]'); WebSocketLib.send(input.val()); input.val(""); }); } });

Zuerst warten wir, bis unser HTML-Dokument im Browser aufgebaut ist. Dann geben wir in Zeile 2 an, wo unser WebSocket-Server läuft. Die Funktion in Zeile 4 wird aufgerufen, sobald das WebSocket verbunden ist. Die Funktion Zeile 6-8 wird aufgerufen, wenn eine Nachricht zurückgegeben wird. Die Funktion Zeile 9-11 wird bei Log-Ereignissen (Hauptsächlich zum Debuggen) aufgerufen. Mit WebSocketLib.send(„Nachricht“) in Zeile 19 schicken wir eine Nachricht an den Server.

Wenn nun alles vorbereitet ist, der WebSocket-Server gestartet ist (siehe oben), können wir einem Browser, der WebSocket unterstützt (siehe oben), unsere HTML-Seite aufrufen. Die Log-Ausgabe im HTML-Fenster sieht wie folgt aus:

Initializing Socket. State: 0
Socket is Ready. State: 1

Im Consolenfenster des PHP-Server sehen wir:

2011-11-25 11:26:47 [info] [client 127.0.0.1:30601] Connected
2011-11-25 11:26:47 [info] [client 127.0.0.1:30601] Performing handshake
2011-11-25 11:26:47 [info] [client 127.0.0.1:30601] Handshake sent

Die Verbindung ist nun also aufgebaut. Jetzt sollten wir den „echo“-Server testen. Wir tippen etwas in das Textfeld und schicken es ab. Der Log sagt:

Recieved new Message from Server: test

Und unter „Chat“ steht jetzt „test“. Hat soweit also funktioniert! Jetzt können wir ein neues Browserfenster aufmachen, wieder den Verbindungsvorgang abwarten und dann dort eine Nachricht verschicken. Diese erscheint dann in allen verbundenen Client-Fenstern.

Eigentlich ziemlich einfach zu verwenden, und es öffnet viele neue Möglichkeiten vorallem in Kombination mit Canvas für HTML5-Multiplayer-Games… hoffen wir also, dass es bald von allen Browser voll unterstützt wird! 🙂