Was sind WebSockets? (Chat und Multiplayer-Spiel) 5533

In diesem Beitrag geht es um WebSockets, also einer bidirektionalen dauerhaften Verbindung zwischen Server und Clients. Dies wollen wir uns am Beispiel eines einfachen online Chats ansehen.

Normalerweise kommunizieren Clients, also zum Beispiel euer Browser, mit dem Server, indem du eine Aktion auf der Webseite ausführst. Das kann das Öffnen eines Links oder das Klicken auf einen Button sein. Das ist eine normale HTTP (Hypertext Transfer Protocol) Verbindung:

normale HTTP Verbindung

Der Client sendet eine Anfrage (Request) an den Server und dieser gibt eine Antwort (Response) zurück. Es geht also immer vom Client aus. Wenn der Client keine Anfrage sendet, bekommt dieser auch nichts vom Server zurück. Bei einem online Chat ist das natürlich problematisch, da ein Nutzer nicht automatisch die neuen Nachrichten vom Server bekommt.

Bei einer WebSocket-Verbindung startet der Verbindungsaufbau auch mit einer Anfrage des Clients, also zum Beispiel der Aufruf einer Webseite. Danach wird aber eine dauerhafte Verbindung zwischen Client und Server aufgebaut, die so lange besteht, bis der Client die Webseite verlässt.

Websocket-Verbindung

Mit dieser dauerhaft aufgebauten Verbindung kann der Server sowie der Client andauernd Informationen austauschen. Wenn ein Nutzer eine neue Nachricht im Chat schreibt, sendet der Server die neue Nachricht an alle Teilnehmer des Chats.

Mehrere Clients mit WebSocket-Verbindung zum Server.

1. Einen Chat mit WebSockets erstellen

Wir wollen einen einfachen online Chat erstellen, in dem jeder Besucher der Webseite automatisch Teilnehmer des Chats ist und Nachrichten schreiben kann.

Einfacher Chat mit WebSockets.

Folgende Schritte sind notwendig, um diesen Chat zu erstellen:

  1. Webserver mit WebSockets einrichten.
  2. Website für Chat erstellen.

1) Webserver mit WebSockets einrichten

Wir starten mit dem Einrichten eines Webservers. Dafür benötigen wir einen Server, der die Webseite des Chats zur Verfügung stellt und mit der sich die Nutzer verbinden können. Das kann ein echter Server oder ein lokal installierter Server sein. Ich nutze in folgendem Beispiel einen einfachen VServer, dieser kostet zwischen 2 und 5 Euro pro Monat und hat meistens eine Vertragslaufzeit von 12 Monaten (VServer bei netcup, 1&1, …).

Nach der Anmeldung auf dem Server musst du zuerst Node.js und den JavaScript-Paketmanager NPM installieren. Node.js ist eine plattformübergreifende JavaScript-Laufzeitumgebung, die JavaScript-Code außerhalb eines Webbrowsers ausführen kann. Damit können wir einfach einen Webserver aufsetzen und weitere benötigte Module installieren.

Auf meinem VServer habe ich Linux Ubuntu installiert und kann Node.js und den Paketmanager NPM folgendermaßen als Admin (root) installieren:

apt install nodejs npm

Danach lege ich aus Sicherheitsgründen einen neuen Nutzer an, der den Webserver erstellt und mit dem ich mich am Server anmelde.

adduser richi

Jetzt wechseln wir zum neu erstellten Nutzer und erstellen einen Ordner für unseren Webserver:

su richi

mkdir chat

Im neu erstellente Ordner initialisieren wir ein neues NPM-Packet und installieren das JavaScript Express Framework. Mit diesem und Node.js können wir dann super einfach einen Webserver erstellen.

npm init -f

npm install --save express

Um die oben beschriebene bidirektionale dauerhafte Websocket-Verbindung mit den Clients herstellen zu können, installieren wir noch das Framework socket.io, welches die Websocket-Funktionen sehr einfach nutzbar macht.

npm install --save socket.io

Jetzt sind alle nötigen Pakete installiert und wir können den Code für den Webserver erstellen. Dies geschieht in der Datei /chat/server.js

//server.js
var express = require('express');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);

app.use(express.static(__dirname + '/public'));
app.get('/', function (req, res) {
	res.sendFile(__dirname + '/index.html');
});

io.on('connection', function (socket) {
	console.log('Server: a user connected');
	//new message received?
	socket.on('messageSent', function (userId, message) {
		//send new message to all clients
		io.emit('newMessage', userId, message);
	});
	socket.on('disconnect', function () {
		console.log('Server: user disconnected');
		socket.disconnect();
  	});	
});


server.listen(8081, function () {
	console.log(`Listening on ${server.address().port}`);
	console.log('IP: 45.136.31.152:8081 ');
});

Hier wird mit Hilfe on express und Node.js ein Webserver erstellt, der Anfragen in den Ordner „/chat/public“ und dort an „index.html“ weiterleitet.

Für unseren Chat ist der mittlere Teil des Codes interessant:

io.on('connection', function (socket) {
	console.log('Server: a user connected');
	//new message received?
	socket.on('messageSent', function (userId, message) {
		//send new message to all clients
		io.emit('newMessage', userId, message);
	});
	socket.on('disconnect', function () {
		console.log('Server: user disconnected');
		socket.disconnect();
  	});	
});

Das ist der Part in dem wir das Paket socket.io, also die WebSockets, nutzen, um neue Nachrichten von Nutzern entgegenzunehmen und an alle anderen Nutzer zu senden. Die Funktionen „io.on()“ bzw. „socket.on()“ sind vom installierten Paket socket.io und überprüfen andauernd, ob eine Nachricht vom eingetragenen Typ am Server von den Clients oder anders herum ankommt.

socket.on('messageSent', function (userId, message) {
	//send new message to all clients
	io.emit('newMessage', userId, message);
});

Wird mit „socket.emit()“ eine Nachricht vom Typ „messageSent“ vom Client an den Server gesendet, wird diese Funktion ausgeführt und die empfangene Nachricht an alle Teilnehmer des Chats mittels „io.emit()“ weitergeleitet. Mit der emit()-Funktion kann man allen Teilnehmer und auch dem Server Informationen zusenden. Diese können dann mit der on()-Funktion die Informationen aufnehmen und entsprechend reagieren.

Das ist die grundlegende Technik, wie mit Hilfe von WebSockets und socket.io kommuniziert wird. Ein Teilnehmer ob Server oder Client kann zu jeder Zeit mit einem anderen Teilnehmer kommunizieren, da eine dauerhafte Verbindung zwischen den Geräten besteht.

Jetzt ist der Server fertig und wir erstellen die Webseite, die der Nutzer sieht, wenn er unseren Webserver aufruft.

2) Website für den Chat erstellen

Jetzt benötigen wir eine Webseite, die die Nutzer des Chats angezeigt bekommen wenn sie sich mit dem erstellten Webserver verbinden.

Zuerst erstellen wir neue „index.html“ Datei im Ordner „chat/public/„. Wenn ein Nutzer dann die IP-Adresse des Servers mit dem angegeben Port aufruft, wird diese HTML-Seite angezeigt.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
	</head>
	<body>
	
		<h1>Chat</h1>
		<div>
			<textarea id="txtChat"rows="4" cols="50" readonly></textarea>
		</div>
		<div>
			<input id="inputChat" placeholder="message" />
			<button id="btnSend">send</button>
		</div>
		<script src="/socket.io/socket.io.js"></script>
		<script src="js/chat.js"></script>
	</body>
</html>

Hier wird ein Textfeld für die Chatnachrichten und ein Eingabefeld mit Button zum Senden einer Nachricht angezeigt. Des Weiteren werden JavaScript-Dateien geladen. Die von socket.io, damit man die WebSocket-Technologie über das Framework socket.io nutzen kann und die für unseren Chat nötige chat.js Datei.


//chat.js
this.socket = io();
var self = this;
this.users = [];

//update textarea when you get new message from server
this.socket.on('newMessage', function (userId, message) {
	document.getElementById("txtChat").value += userId + ": " + message + "\n";
});

//send new message to server
document.getElementById("btnSend").addEventListener("click", function(){
	self.socket.emit('messageSent', self.socket.id, document.getElementById("inputChat").value);
	document.getElementById("inputChat").value = "";
});

In der chat.js Datei wird eine neu eingegebene Nachricht mit „socket.emit(‚messageSent‘, …)“ an den Server geschickt. Mit „socket.on(’newMessage‘, …)“ wird andauernd überprüft, ob eine neue Nachricht vom Server geschickt wurde, wenn ja, dann wird diese Nachricht im Textfeld des Chats angezeigt.

Jetzt kannst du den erstellen Server im Ordner „/chat/“ starten und dich dann mit einem Browser verbinden.

node server.js

2. Einfaches Multiplayerspiel mit WebSockets erstellen

Jetzt wollen wir mit Hilfe von WebSockets ein einfaches Multiplayerspiel erstellen:

Einfaches Multiplayerspiel mit WebSockets.

Ein Spieler verbindet sich mit unserem Server http://45.136.31.152:8081/ und bekommt automatisch eine Spielfigur zugewiesen. Die Spielfigur kann mit den Pfeiltasten gesteuert werden. Jeder Mitspieler kann die Bewegung der anderen Spieler sehen. Es ist also eher ein Prototyp als ein fertiges Spiel, doch kannst du mit dieser Grundlage weitere Spielelemente hinzufügen.

Folgende Schritte werden benötigt, um das Spiel zu erstellen:

  1. Webserver fürs Multiplayerspiel erstellen.
  2. Webseite der Clients erstellen.

1) Webserver fürs Multiplayerspiel erstellen

Der Server des Multiplayerspiels wird unter „/multiplayer/server.js“ erstellt und hat folgenden Code:

//server.js
var express = require('express');
var app = express();
var server = require('http').Server(app);
var socketIo = require('socket.io')(server);

var players = {};
var sprites = ["knight","orc","clown","human","woman"];

app.use(express.static(__dirname + '/public'));
app.get('/', function (req, res) {
	res.sendFile(__dirname + '/index.html');
});

socketIo.on('connection', function (socket) {
	console.log('Server: a user connected');
	// create a new player and add it to our players object
	players[socket.id] = {
	  	x: Math.floor(Math.random() * 100) + 50,
	  	y: Math.floor(Math.random() * 100) + 50,
	  	playerId: socket.id,
		sprite: 0,
	};
	players[socket.id].sprite = getAvailableSpriteId();
	//emit "currentPlayers": send the players object to the new player
	socket.emit('currentPlayers', players);
	//emit "newPlayer": update all other players of the new player
	socket.broadcast.emit('newPlayer', players[socket.id]);
	socket.on('disconnect', function () {
		console.log('Server: user disconnected');
		// remove this player from our players object
		sprites.push(players[socket.id].sprite);
		delete players[socket.id];
		socket.disconnect();
		//emit "myDisconnect":
		socketIo.emit('myDisconnect', socket.id);
  	});
	// when a player moves, update the player data
	socket.on('playerMovement', function (movementData) {
		players[socket.id].x = movementData.x;
		players[socket.id].y = movementData.y;
		//emit "playerMoved": to all players about the player that moved
		socket.broadcast.emit('playerMoved', players[socket.id]);
	});	
});

function getAvailableSpriteId(){
	if(sprites.length > 0){
		let tempSprite = sprites[0];
		sprites.shift();
		return tempSprite;
	}else{
		return "human";
	}
}

server.listen(8081, function () {
	console.log(`Listening on ${server.address().port}`);
	console.log('IP: 45.136.31.152:8081 ');
});

2) Webseite für Spieler erstellen

Jetzt benötigen wir eine Webseite, die die Spieler angezeigt bekommen wenn sie sich mit dem erstellten Webserver verbinden.

Zuerst erstellen wir neue „index.html“ Datei im Ordner „multiplayer/public/„. Wenn ein Nutzer dann die IP-Adresse des Servers mit dem angegeben Port aufruft, wird diese HTML-Seite angezeigt.

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
	</head>
	<body>
		<script src="/socket.io/socket.io.js"></script>
		<script src="//cdn.jsdelivr.net/npm/phaser@3.55.2/dist/phaser.min.js"></script>
		<script src="js/game.js"></script>
	</body>
</html>

Diese HTML-Seite lädt nur das vorher installierte Paket socket.io und das Spieleframework phaser.io. Phaser.io nutzen wir, um einfach ein HTML-Spiel erstellen zu können. Des Weiteren wird eine JavaScript Datei für unserer HTML-Seite geladen „game.js„. Diese Datei müssen wir als nächstes im Ordner „multiplayer/public/js/“ erstellen:

//game.js
var config = {
	type: Phaser.AUTO,
	parent: 'phaser-example',
	width: 200,
	height: 200,
	zoom: 4,
	pixelArt: true,	
	scene: {
		preload: preload,
		create: create,
		update: update
	} 
};

var game = new Phaser.Game(config);

function preload() {
	this.load.image('knight', 'assets/knight.png');	
	this.load.image('orc', 'assets/orc.png');
	this.load.image('clown', 'assets/clown.png');
	this.load.image('human', 'assets/human.png');
	this.load.image('woman', 'assets/woman.png');
}

function create() {
	var self = this;
	this.socket = io();
	this.otherPlayers = [];
	//when receiving "currentPlayers" message:
	this.socket.on('currentPlayers', function (players) {
		Object.keys(players).forEach(function (id) {
			if (players[id].playerId === self.socket.id) {
				addPlayer(self, players[id]);
			} else {
				addOtherPlayers(self, players[id]);
			}
		});
	});
	//when receiving "newPlayer" message:
	this.socket.on('newPlayer', function (playerInfo) {		
		addOtherPlayers(self, playerInfo);
	});
	//when receiving "myDisconnect" message:
	this.socket.on('myDisconnect', function (playerId) {
		self.otherPlayers.forEach(function (otherPlayer) {
			if (playerId === otherPlayer.playerId) {
				otherPlayer.destroy();
			}
		});
	});
	//when receiving "playerMoved" message:	
	this.socket.on('playerMoved', function (playerInfo) {
		console.log("Client: a user moved");
		self.otherPlayers.forEach(function (otherPlayer) {
			if (playerInfo.playerId === otherPlayer.playerId) {
				otherPlayer.setPosition(playerInfo.x, playerInfo.y);
			}
		});
	});
	//bind keyboard
	this.cursors = this.input.keyboard.createCursorKeys();
}

function update() {
	if (this.knight) {
		if (this.input.keyboard.checkDown(this.cursors.left, 250)){
		  this.knight.x -= 16;
		} else if (this.input.keyboard.checkDown(this.cursors.right, 250)){
			this.knight.x += 16;
		} else if (this.input.keyboard.checkDown(this.cursors.down, 250)){
			this.knight.y += 16;
		} else if (this.input.keyboard.checkDown(this.cursors.up, 250)){
			this.knight.y -= 16;
		}	 
		//emit player movement
		var x = this.knight.x;
		var y = this.knight.y;
		if (this.knight.oldPosition && (x !== this.knight.oldPosition.x || y !== this.knight.oldPosition.y )) {
			this.socket.emit('playerMovement', { x: this.knight.x, y: this.knight.y });
		}
		//save old position data
		this.knight.oldPosition = {
			x: this.knight.x,
			y: this.knight.y,
		};
	}
}

function addPlayer(self, playerInfo) {
	
	self.knight = self.add.sprite(playerInfo.x, playerInfo.y, playerInfo.sprite).setOrigin(0.5, 0.5);
}

function addOtherPlayers(self, playerInfo) {
	const otherPlayer = self.add.sprite(playerInfo.x, playerInfo.y, playerInfo.sprite).setOrigin(0.5, 0.5);	
	otherPlayer.playerId = playerInfo.playerId;
	self.otherPlayers.push(otherPlayer);
}

Zuerst wird eine config-Variable gefüllt, die dann einer neuen Phaser.io Instanz übergeben wird. Die darauffolgenden drei Methoden preload(), create() und update() sind Phaser.io spezifisch.

  • In preload() werden die Grafiken für unser Spiel geladen.
  • Die Funktion create() wird jedes Mal aufgerufen, wenn ein Spieler die Webseite aufruft. Hier werden alle Spieler erstellt, die von unserem Server an den Client übermittelt werden. Es wird auch überwacht, ob ein Spieler sich bewegt oder das Spiel verlässt.
  • Die Funktion update() wird andauernd, also mehrmals in der Sekunde aufgerufen. Hier wird geprüft ob der Spieler eine Pfeiltaste drück, um dann seine Spielfigur zu bewegen.

Zuletzt müssen wir noch die Spielergrafiken in den Ordner „multiplayer/public/assets/“ hochladen:

Grafiken sind von Kenney.nl

3) Webserver starten und spielen

Nun haben wir den Webserver und die Webseite für die Spieler erstellt. Damit die Spieler die Webseite erreichen können, um das Spiel zu spielen, muss der Webserver gestartet werden. Dazu gehst du in den Ordner „multiplayer/“ und gibst folgenden Befehl ein:

node server.js

Jetzt siehst du auf deinem Server, dass der Webserver gestartet wurde. Besucht ein Spieler die Webseite, wird dies auch auf deinem Server angezeigt. Öffne die Webseite in mehreren Tabs und bewege einen der Spieler mit den Pfeiltasten.

Einfaches Multiplayerspiel mit WebSockets.

Jetzt hast du einen online Chat und sogar ein einfaches Multiplayerspiel erstellt und etwas über WebSockets und der bidirektionalen Kommunikation erfahren. Versuche den Chat oder das Spiel anzupassen. Du könntest zum Beispiel jedem Nutzer des Chats einen Zufallsnamen geben, anstatt der kryptischen ID, die den Nutzern von socket.io gegeben wird.

Links:

unsere-schule.org

×

Was sind WebSockets? (Chat und Multiplayer-Spiel)

Code: 5533