{"id":28654,"date":"2026-02-28T17:24:14","date_gmt":"2026-02-28T16:24:14","guid":{"rendered":"https:\/\/blog.mi.hdm-stuttgart.de\/?p=28654"},"modified":"2026-03-01T13:36:16","modified_gmt":"2026-03-01T12:36:16","slug":"multiplayer-arena-dynamische-game-sessions-mit-docker-fastapi","status":"publish","type":"post","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/28\/multiplayer-arena-dynamische-game-sessions-mit-docker-fastapi\/","title":{"rendered":"Multiplayer-Arena: Dynamische Game-Sessions mit Docker &amp; FastAPI"},"content":{"rendered":"\n<p><strong>Game:<\/strong> https:\/\/46.101.127.20.sslip.io\/ (online bis 31. M\u00e4rz 2026) (<strong>beachte Readme im Git!<\/strong>)<\/p>\n\n\n\n<p><strong>Git Repo<\/strong>: https:\/\/github.com\/ek101-collab\/ArenaGame-with-ContainerSessions<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Einleitung und Motivation<\/h2>\n\n\n\n<p>Im Rahmen der Vorlesung <strong>&#8220;System Engineering und Management&#8221;<\/strong> bestand die Kernaufgabe darin, ein Projekt zu konzipieren und umzusetzen, das moderne Web- und Cloud-Technologien nutzt. Zu Beginn der Projektphase lag mein Fokus auf einer klassischen Wetter-App. Das Ziel war es, den grundlegenden Umgang mit Serverarchitekturen, REST-APIs und Daten-Parsing zu verstehen. Doch w\u00e4hrend der ersten Prototypen wurde mir klar, dass ich diese Gelegenheit nutzen wollte, um tiefer in ein Feld einzutauchen, das meine wahre Leidenschaft und mein Karriereziel darstellt: die <strong>Spieleentwicklung<\/strong>.<\/p>\n\n\n\n<p>Besonders die Architektur von Multiplayerspielen hat mich schon immer fasziniert. Die Herausforderung, hunderte von Datenpaketen pro Sekunde konsistent \u00fcber ein Netzwerk zu verteilen, ist eine der komplexesten Aufgaben im System Engineering. So entstand die Idee, eine vereinfachte Version eines \u201eArena Fighters\u201c (\u00e4hnlich wie <em>Super Smash Bros.<\/em>) zu entwickeln.<\/p>\n\n\n\n<p>Bei der Umsetzung von <strong>&#8220;ArenaGame&#8221;<\/strong> lag die Priorit\u00e4t nicht auf einem massiven Umfang an Inhalten. Stattdessen konzentrierte ich mich auf die pr\u00e4zise Synchronisation fundamentaler Mechaniken: Laufen, Schlagen und Dashen. Diese Aktionen m\u00fcssen \u00fcber das Netzwerk so abgeglichen werden, dass sich das Spiel f\u00fcr alle Kontrahenten fair und fl\u00fcssig anf\u00fchlt. Da ich zuvor fast ausschlie\u00dflich an lokalen Anwendungen gearbeitet hatte und Netzwerk-Kommunikation f\u00fcr mich Neuland war, war mein prim\u00e4res Ziel die Implementierung einer <strong>soliden, entkoppelten und skalierbaren Serverarchitektur<\/strong>.<\/p>\n\n\n\n<p>In den folgenden Kapiteln stelle ich die einzelnen Komponenten des Projekts im Detail vor. Ich erl\u00e4utere die gew\u00e4hlten technischen Ans\u00e4tze und beschreibe, wie ich die Architektur aufgebaut habe, um die Anforderungen an eine stabile Spielsession zu erf\u00fcllen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Spielprinzip<\/h2>\n\n\n\n<p>Zu Beginn werden alle teilnehmenden Spieler zuf\u00e4llig in der Arena positioniert. Ab diesem Moment k\u00f6nnen sich die Spieler frei bewegen und andere Spieler angreifen. Zus\u00e4tzlich steht ihnen eine Dash-Mechanik zur Verf\u00fcgung, mit der sie Angriffen ausweichen oder vor Gegnern fl\u00fcchten k\u00f6nnen.<\/p>\n\n\n\n<p>Ziel des Spiels ist es, alle gegnerischen Spieler gegen die W\u00e4nde der Arena zu schlagen. Um zu verhindern, dass sich alle Spieler dauerhaft in der Mitte der Arena aufhalten und sich durch Treffer nur minimal bewegen, habe ich eine Knockback-Mechanik integriert, die f\u00fcr mehr Dynamik im Spielverlauf sorgt.<\/p>\n\n\n\n<p>Diese Mechanik funktioniert so, dass ein Spieler bei jedem Treffer einen bestimmten Prozentsatz an Knockback aufbaut. Je h\u00f6her dieser Prozentsatz ist, desto st\u00e4rker f\u00e4llt der R\u00fccksto\u00df aus, der bei einem weiteren Treffer ausgel\u00f6st wird. Dadurch wird nicht nur das Spielgeschehen beschleunigt, sondern auch verhindert, dass eine Runde zu lange dauert oder im Extremfall gar kein Ende findet.<\/p>\n\n\n\n<p>Jeder erfolgreiche Treffer erh\u00f6ht den Knockback-Wert um 20 %, welcher f\u00fcr alle Spieler sichtbar \u00fcber dem jeweiligen Charakter angezeigt wird. Der maximal erreichbare Wert liegt bei 110 %. Diese Grenze wurde bewusst gew\u00e4hlt, da das Spiel den Namen \u201e110 %\u201c tragen sollte. Erreicht ein Spieler diesen Wert, wird bei einem Treffer ein extrem starker R\u00fccksto\u00df ausgel\u00f6st, wodurch es deutlich schwieriger wird, sich innerhalb der Arena zu halten.<\/p>\n\n\n\n<p>Sobald alle Spieler bis auf einen die Arena-Wand oder -Grenze ber\u00fchrt haben und dadurch ausgeschieden sind, gewinnt der verbleibende Spieler. Der Gewinner wird anschlie\u00dfend auf dem Bildschirm angezeigt, bevor alle Spieler wieder ins Hauptmen\u00fc zur\u00fcckgef\u00fchrt werden.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Architektur<\/h2>\n\n\n\n<p class=\"has-medium-font-size\">Wie ich bereits zu Beginn erw\u00e4hnt habe, war es mein Ziel, eine Serverarchitektur zu entwickeln, die verschiedene Aufgaben klar voneinander trennt. Aus diesem Grund habe ich eine mikroservice\u00e4hnliche Struktur umgesetzt, bei der einzelne Komponenten jeweils spezifische Aufgaben \u00fcbernehmen:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Spiel (Frontend &#8211; Darstellung)<\/strong><\/li>\n\n\n\n<li><strong>Matchmaker (Backend &#8211; Orchestrierung)<\/strong><\/li>\n\n\n\n<li><strong>Game Server (Backend &#8211; Datensynchronisierung)<\/strong><\/li>\n<\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/architecture-1.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"718\" data-attachment-id=\"28656\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/28\/multiplayer-arena-dynamische-game-sessions-mit-docker-fastapi\/architecture-11\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/architecture-1.png\" data-orig-size=\"1173,823\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"architecture\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/architecture-1-1024x718.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/architecture-1-1024x718.png\" alt=\"\" class=\"wp-image-28656\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/architecture-1-1024x718.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/architecture-1-300x210.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/architecture-1-768x539.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/architecture-1.png 1173w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"has-text-align-center\"><em>Bild 1 \u2013 Architekturdiagramm<\/em><\/p>\n\n\n\n<p>Ruft ein Spieler das Frontend \u00fcber die entsprechende URL auf, hat er die M\u00f6glichkeit, entweder selbst ein Spiel zu hosten oder einem bestehenden Spiel beizutreten. Zur Vereinfachung ist im oberen Diagramm ausschlie\u00dflich die Host-Variante dargestellt, zudem erfolgt die Darstellung nur in eine Richtung.<\/p>\n\n\n\n<p>M\u00f6chte ein Spieler ein Spiel hosten, sendet das Frontend eine Anfrage an den Matchmaker. Dieser startet daraufhin einen neuen Game Server und \u00fcbermittelt dessen URL an das Frontend. Anschlie\u00dfend verbindet sich das Frontend direkt mit dem zugewiesenen Game Server.<\/p>\n\n\n\n<p>Im Diagramm sind mehrere Spiel- und Game-Server-Bl\u00f6cke dargestellt. Diese visualisieren, dass bei jedem Klick auf \u201eHost\u201c ein neuer Game Server vom Matchmaker erstellt und genau einem Spieler zugewiesen wird. W\u00e4hrend dieser Erstellungsphase erh\u00e4lt das Frontend zus\u00e4tzlich einen generierten Code, der f\u00fcr das Beitreten weiterer Spieler verwendet wird.<\/p>\n\n\n\n<p>Mithilfe dieses Codes k\u00f6nnen andere Spieler einem bereits gestarteten Game Server beitreten und am Spiel des Hosts teilnehmen.<\/p>\n\n\n\n<p>Mit diesem Ansatz verfolgte ich drei zentrale Ziele. Erstens sollte die <strong>Skalierbarkeit<\/strong> der einzelnen Komponenten gew\u00e4hrleistet werden, sodass sie unabh\u00e4ngig voneinander erweitert oder vervielf\u00e4ltigt werden k\u00f6nnen. Zweitens war mir die <strong>Unabh\u00e4ngigkeit<\/strong> der Komponenten besonders wichtig. F\u00e4llt beispielsweise der Matchmaker aus, soll sich dies weder auf das Frontend noch auf die Game-Server auswirken.<\/p>\n\n\n\n<p>Daraus ergibt sich auch das dritte Ziel, ein <strong>vereinfachtes Debugging<\/strong>. Durch die klare Entkopplung der einzelnen Dienste konnte ich w\u00e4hrend der Entwicklung gezielt \u00fcberpr\u00fcfen, ob die Kommunikation zwischen den jeweiligen Komponenten korrekt funktioniert. So lie\u00df sich beispielsweise analysieren, ob das Frontend erfolgreich eine Verbindung zum Matchmaker aufbaut oder ob der Matchmaker ordnungsgem\u00e4\u00df mit den Game-Servern kommuniziert.<\/p>\n\n\n\n<p>Dies stellt jedoch lediglich die grundlegende Idee der Architektur dar und dient dazu, ein grobes Gesamtbild zu vermitteln. Um ein tiefergehendes Verst\u00e4ndnis dieser Architektur zu erlangen, ist es notwendig, n\u00e4her auf die technische Umsetzung einzugehen, welche in den folgenden Kapiteln erl\u00e4utert wird.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Frontend (Spiel)<\/h2>\n\n\n\n<p>Bevor ich mit der Implementierung der Serverarchitektur begann, besch\u00e4ftigte ich mich zun\u00e4chst mit der grundlegenden Konzeption des Spiels. Anfangs hatte ich die Idee, das Spiel mithilfe eines Frameworks wie Node.js oder React Native umzusetzen. Allerdings wurde schnell deutlich, dass dieser Ansatz mit einem erheblichen Mehraufwand verbunden gewesen w\u00e4re.<\/p>\n\n\n\n<p>Dieser zus\u00e4tzliche Aufwand h\u00e4tte insbesondere daraus resultiert, dass zentrale Systeme wie die Darstellung der Arena und der Spieler, ein Animationssystem, ein Game-Loop-Tick sowie eine Physik-Engine vollst\u00e4ndig selbst h\u00e4tten implementiert werden m\u00fcssen. Da ich diesen Aufwand als unverh\u00e4ltnism\u00e4\u00dfig einsch\u00e4tzte, entschied ich mich f\u00fcr den Einsatz einer Game-Engine, die diese grundlegenden Systeme bereits von Haus aus mitbringt.<\/p>\n\n\n\n<p>Als Game-Engine w\u00e4hlte ich Godot, da ich mit dieser bereits aus fr\u00fcheren Projekten vertraut war und sie zudem einen Web-Export unterst\u00fctzt. In Godot begann ich zun\u00e4chst mit der Implementierung der Spielfunktionen des Spielers und testete diese lokal. Dabei wurde unter anderem die Knockback-Mechanik \u00fcberpr\u00fcft, indem zwei Spieler instanziiert wurden, von denen einer manuell gesteuert wurde.<\/p>\n\n\n\n<p>Da Godot \u00fcber eine eigene Skriptsprache namens GDScript verf\u00fcgt, welche in ihrer Syntax stark an Python angelehnt ist, konnte die Implementierung mithilfe geeigneter Quellen vergleichsweise z\u00fcgig umgesetzt werden.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/character.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"558\" data-attachment-id=\"28659\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/28\/multiplayer-arena-dynamische-game-sessions-mit-docker-fastapi\/character\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/character.png\" data-orig-size=\"2559,1394\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"character\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/character-1024x558.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/character-1024x558.png\" alt=\"\" class=\"wp-image-28659\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/character-1024x558.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/character-300x163.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/character-768x418.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/character-1536x837.png 1536w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/character-2048x1116.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"has-text-align-center\"><em>Bild 2 \u2013 <\/em>Charakter<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/arena.png\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"557\" data-attachment-id=\"28660\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/28\/multiplayer-arena-dynamische-game-sessions-mit-docker-fastapi\/arena\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/arena.png\" data-orig-size=\"2559,1392\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"arena\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/arena-1024x557.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/arena-1024x557.png\" alt=\"\" class=\"wp-image-28660\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/arena-1024x557.png 1024w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/arena-300x163.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/arena-768x418.png 768w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/arena-1536x836.png 1536w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/arena-2048x1114.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/a><\/figure>\n\n\n\n<p class=\"has-text-align-center\"><em>Bild 3 \u2013 <\/em>Arena<\/p>\n\n\n\n<p>Nachdem das grundlegende Ger\u00fcst der Spiellogik in der Godot-Engine stand, begann ich mit der Umsetzung der Netzwerk-Anbindung. Daf\u00fcr habe ich einen zentralen Network-Manager erstellt, der als globales Singleton (Autoload in Godot) eingebunden ist. Diese Klasse ist von \u00fcberall im Spiel erreichbar, egal ob man sich gerade im Hauptmen\u00fc oder bereits in der Arena befindet.<\/p>\n\n\n\n<p>Da die Server- und Netzwerkstruktur recht komplex ist, war es mir wichtig, die gesamte Netzwerk-Kommunikation an einer einzigen Stelle zu b\u00fcndeln. Auf diese Weise bleibt die eigentliche Spiellogik \u00fcbersichtlich und muss sich nicht mit Netzwerkdetails besch\u00e4ftigen. Der Network-Manager \u00fcbernimmt diese Aufgaben vollst\u00e4ndig. Da diese Klasse sehr umfangreich ist, beschr\u00e4nke ich mich im Folgenden auf die wichtigsten Schritte im Verbindungsablauf.<\/p>\n\n\n\n<p>Godot stellt intern eine eigene Klasse namens <strong>WebSocketPeer<\/strong> zur Verf\u00fcgung, die f\u00fcr die Nutzung von WebSockets gedacht ist. WebSockets sind allgemein eine Verbindungsart zwischen einem Client und einem Server, die dauerhaft bestehen bleibt und nicht wie klassische HTTP-Anfragen nach dem Prinzip \u201eRequest und Response\u201c funktioniert. Dadurch entsteht eine deutlich geringere Latenz, was f\u00fcr mein Spiel besonders wichtig ist, da die Eingaben der Spieler in jedem Frame aktualisiert werden m\u00fcssen. Aus diesem Grund war mir von Anfang an klar, dass ich diese Klasse verwenden werde.<\/p>\n\n\n\n<p>Beim Start des Spiels existiert zun\u00e4chst noch keine aktive WebSocket-Verbindung, auch wenn diese bereits im Hintergrund in der _process()-Funktion \u00fcberpr\u00fcft wird. Die <strong>_process()<\/strong>-Funktion stellt in Godot den sogenannten Tick-Loop der Engine dar und wird beispielsweise etwa 60 Mal pro Sekunde aufgerufen.<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-python\" data-line=\"\">func _process(_delta):\n\tws.poll()\n\tvar state = ws.get_ready_state()\n\n\tif state == WebSocketPeer.STATE_OPEN:\n\t\tif not connected:\n\t\t\tconnected = true\n\t\t\tprint(&quot;WebSocket ge\u00f6ffnet und bereit!&quot;)\n\t\t\tconnected_to_server.emit() \n\t\t\n\t\twhile ws.get_available_packet_count() &amp;gt; 0:\n\t\t\tvar packet = ws.get_packet()\n\t\t\tvar data = JSON.parse_string(packet.get_string_from_utf8())\n\t\t\tif data:\n\t\t\t\t_handle_internal_data(data)\n\t\t\t\tmessage_received.emit(data)\n\n\telif state == WebSocketPeer.STATE_CLOSED or state == WebSocketPeer.STATE_CLOSING:\n\t\tif connected:\n\t\t\tconnected = false\n\t\t\tprint(&quot;WebSocket geschlossen\/getrennt.&quot;)\n\n<\/code><\/pre>\n<\/div>\n\n\n\n<p>Um eine Verbindung zum WebSocket herzustellen, stehen zwei Optionen zur Verf\u00fcgung. Entweder erstellt der Spieler ein neues Spiel oder tritt einem bereits bestehenden Spiel bei. F\u00fcr diese beiden Funktionen habe ich eine entsprechende Benutzeroberfl\u00e4che umgesetzt, die wie folgt aussieht:<\/p>\n\n\n\n<figure class=\"wp-block-image aligncenter size-full is-resized\"><a href=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu.png\"><img loading=\"lazy\" decoding=\"async\" width=\"834\" height=\"832\" data-attachment-id=\"28665\" data-permalink=\"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2026\/02\/28\/multiplayer-arena-dynamische-game-sessions-mit-docker-fastapi\/mainmenu\/\" data-orig-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu.png\" data-orig-size=\"834,832\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"mainmenu\" data-image-description=\"\" data-image-caption=\"\" data-large-file=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu.png\" src=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu.png\" alt=\"\" class=\"wp-image-28665\" style=\"width:833px;height:auto\" srcset=\"https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu.png 834w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu-300x300.png 300w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu-150x150.png 150w, https:\/\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2026\/02\/mainmenu-768x766.png 768w\" sizes=\"auto, (max-width: 834px) 100vw, 834px\" \/><\/a><\/figure>\n\n\n\n<p class=\"has-text-align-center\"><em>Bild 4 \u2013 <\/em>Main Menu<\/p>\n\n\n\n<p>Wenn der Spieler auf <strong>Host<\/strong> klickt, sendet der Network Manager eine normale HTTP-Anfrage an den Matchmaker, die \u00fcber Godots eigene <strong>HTTPRequest<\/strong>-Klasse realisiert wird. Wird die Anfrage erfolgreich beantwortet und liefert der Matchmaker eine g\u00fcltige Antwort, ruft Godot die Funktion <strong>connect_ws()<\/strong> auf, die eine WebSocket-Verbindung direkt zwischen dem Spieler (Host) und dem Game Server herstellt. Ab diesem Zeitpunkt \u00fcberpr\u00fcft der <strong>WebSocketPeer<\/strong> in der <code class=\"\" data-line=\"\">_process()<\/code>-Funktion kontinuierlich \u00fcber seine <code class=\"\" data-line=\"\">poll()<\/code>-Methode, ob neue Datenpakete eingetroffen sind, und verarbeitet diese entsprechend.<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-python\" data-line=\"\">func request_new_session():\n\tvar http = HTTPRequest.new()\n\tadd_child(http)\n\thttp.request_completed.connect(func(_result, response_code, _headers, body):\n\t\tif response_code != 200:\n\t\t\tprint(&quot;Matchmaker Server Fehler: &quot;, response_code)\n\t\t\treturn\n\t\t\t\n\t\tvar response_text = body.get_string_from_utf8()\n\t\tprint(&quot;MATCHMAKER ANTWORT: &quot;, response_text)\n\t\tvar json = JSON.parse_string(response_text)\n\t\t\n\t\tif json and json.has(&quot;ip&quot;) and json.has(&quot;port&quot;):\n\t\t\t_connect_ws(json.ip, int(json.port))\n\t\telse:\n\t\t\tprint(&quot;Matchmaker Fehler: Ung\u00fcltiges JSON erhalten&quot;)\n\t\thttp.queue_free()\n\t)\n\thttp.request(MATCHMAKER_URL + &quot;\/create_session&quot;, [], HTTPClient.METHOD_POST)\n<\/code><\/pre>\n<\/div>\n\n\n\n<p>Die <strong>Join-Funktion<\/strong> funktioniert \u00e4hnlich wie die <code class=\"\" data-line=\"\">request_new_session()<\/code>-Methode, mit dem Unterschied, dass bei der HTTP-Anfrage zus\u00e4tzlich ein Beitrittscode mitgesendet wird. <\/p>\n\n\n\n<p>Darauf folgt dann der Aufbau der Websocketverbindung:<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-python\" data-line=\"\">func _connect_ws(ip: String, port: int):\n\tws = WebSocketPeer.new()\n\tconnected = false \n\t\n\tvar domain = &quot;46.101.127.20.sslip.io&quot;\n\tvar url = &quot;&quot;\n\t\n\tif OS.has_feature(&quot;web&quot;):\n\t\turl = &quot;wss:\/\/&quot; + domain + &quot;\/game\/&quot; + str(port) + &quot;\/ws&quot;\n\telse:\n\t\turl = &quot;ws:\/\/&quot; + ip + &quot;:&quot; + str(port) + &quot;\/ws&quot;\n\t\t\t\n\tprint(&quot;Verbinde sicher zu: &quot;, url)\n\tvar err = ws.connect_to_url(url)\n\tif err != OK:\n\t\tprint(&quot;Fehler beim Verbindungsaufbau: &quot;, err)\n<\/code><\/pre>\n<\/div>\n\n\n\n<p>Durch diesen Ansatz wird eine aktive WebSocket-Verbindung aufgebaut, \u00fcber die anschlie\u00dfend wichtige Daten zwischen Client und Server ausgetauscht werden k\u00f6nnen. Der Network Manager \u00fcbernimmt dabei jedoch noch eine weitere Aufgabe, n\u00e4mlich die Bereitstellung der <strong>send_json<\/strong>-Funktion. Auf den genauen Code dieser Funktion gehe ich hier nicht n\u00e4her ein, sondern erl\u00e4utere lediglich ihren Zweck.<\/p>\n\n\n\n<p>Wie bereits zuvor erw\u00e4hnt, handelt es sich beim Network Manager um eine globale Klasse, auf die alle anderen Klassen in Godot zugreifen k\u00f6nnen. Genau daf\u00fcr ist diese Methode gedacht. Dadurch k\u00f6nnen beispielsweise Spielerklassen kontinuierlich ihren aktuellen Zustand, wie Position, Aktionen oder Treffer, an den Game Server senden (als Dictionary), woraufhin die anderen Spieler entsprechend reagieren.<\/p>\n\n\n\n<p>Neben dem Spieler greifen aber auch weitere Klassen, wie etwa der GameHandler oder die Lobby, auf Serverereignisse zu. Diese sind f\u00fcr das grundlegende Verst\u00e4ndnis jedoch weniger relevant. Wichtig ist an dieser Stelle vor allem zu zeigen, dass nach dem Aufbau der WebSocket-Verbindung der Austausch von Informationen zwischen Godot und dem Game Server vergleichsweise einfach umgesetzt werden kann.<\/p>\n\n\n\n<p>Jetzt haben wir uns angesehen, wie Godot eine Verbindung zum Matchmaker und dar\u00fcber anschlie\u00dfend zum Game Server aufbaut, was vergleichsweise einfach umzusetzen ist. Diese Einfachheit entsteht jedoch nicht von selbst, sondern setzt eine saubere Umsetzung im Hintergrund voraus. Damit kommen wir nun zum Kern der gesamten Serverarchitektur, dem <strong>Backend<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Backend (Game Server)<\/h2>\n\n\n\n<p>Bevor wir uns den Matchmaker ansehen, der die Verbindungen zwischen Server und Client verwaltet, betrachten wir zun\u00e4chst die Architektur des Game Servers selbst. Diese Reihenfolge halte ich f\u00fcr sinnvoller, da wir bisher die Kommunikation nur in eine Richtung gesehen haben, n\u00e4mlich von Godot zum Game Server, jedoch noch nicht, woher die Antworten kommen und wie die Zust\u00e4nde der Spieler gesetzt werden. Au\u00dferdem ist der Matchmaker in gewisser Form vom Game Server abh\u00e4ngig, da dieser das grundlegende Fundament darstellt, das ben\u00f6tigt wird, um \u00fcberhaupt einen Game Server starten zu k\u00f6nnen.<\/p>\n\n\n\n<p>Der Game-Server hat zun\u00e4chst nur eine einzige Aufgabe, die spielrelevanten Informationen zwischen den Spielern zu synchronisieren und zu aktualisieren. Im Grunde ist der Server der Vermittler des eigentlichen Spiels, der st\u00e4ndig Nachrichten empf\u00e4ngt, sie kurz checkt und dann an die richtigen Leute verteilt.<\/p>\n\n\n\n<p>F\u00fcr die Umsetzung des Game Servers habe ich<strong> FastAPI <\/strong>verwendet. Dieses Web-Framework f\u00fcr Python diente mir als Grundlage f\u00fcr die gesamte Serverlogik. Dort habe ich festgelegt, wie eingehende Daten, zum Beispiel Spielerpositionen oder Zustands\u00e4nderungen, verarbeitet werden. Dabei ist wichtig zu verstehen, dass FastAPI selbst nicht der eigentliche Server ist, der von au\u00dfen angesprochen wird, sondern lediglich die Logik bereitstellt. F\u00fcr mich war es eine gute Basis, auf der ich mein Spiel Schritt f\u00fcr Schritt aufbauen konnte.<\/p>\n\n\n\n<p>Damit diese Logik auch tats\u00e4chlich als <strong>Webservice<\/strong> im Netzwerk erreichbar ist, brauchte ich zus\u00e4tzlich ein <strong>Server-Interface<\/strong>. Daf\u00fcr habe ich <strong>Uvicorn<\/strong> eingesetzt. Uvicorn fungiert als asynchroner <strong>ASGI-Server<\/strong> (Asynchronous Server Gateway Interface). Seine Hauptaufgabe besteht darin, die eingehenden Verbindungen zu verwalten und die Datenpakete an die FastAPI-Anwendung weiterzureichen. Man kann sich Uvicorn dabei als die Schicht vorstellen, die zwischen dem Netzwerk und meiner Anwendung vermittelt.<\/p>\n\n\n\n<p>Zum Starten des Servers habe ich dann folgende Zeile verwendet:<br><strong>uvicorn.run(app, host=&#8221;0.0.0.0&#8243;, port=8000)<\/strong><\/p>\n\n\n\n<p>Der erste Parameter ist die Anwendung selbst, die IP-Adresse sorgt daf\u00fcr, dass der Server von \u00fcberall erreichbar ist, und der Port legt fest, \u00fcber welches \u201eTor\u201c kommuniziert wird. Da die Kommunikation im Spiel \u00fcber WebSockets l\u00e4uft, war die asynchrone Arbeitsweise von Uvicorn f\u00fcr mich besonders wichtig. So konnte der Server mehrere dauerhafte Verbindungen der Spieler gleichzeitig handhaben, ohne dass alles blockiert wird.<\/p>\n\n\n\n<p>So viel zum Grundger\u00fcst, nun geht es etwas mehr in die eigentliche Logik des Servers. Die FastAPI-Anwendung verwaltet zun\u00e4chst ein zentrales Objekt namens<strong> game_state<\/strong>. Darin wird gespeichert, welche Spieler aktuell verbunden sind, wer der Host ist und ob das Match \u00fcberhaupt schon l\u00e4uft.<\/p>\n\n\n\n<p>Das Herzst\u00fcck der Kommunikation ist der <strong>WebSocket-Endpoint<\/strong>, der \u00fcber <strong>@app.websocket(&#8220;\/ws&#8221;)<\/strong> definiert ist. Sobald ein Client eine Verbindung aufbaut, startet eine asynchrone <strong>while True<\/strong>-Schleife. Diese h\u00e4lt die Verbindung dauerhaft offen und wartet kontinuierlich auf eingehende JSON-Pakete.<\/p>\n\n\n\n<p>Die komplette Steuerung des Servers findet innerhalb dieses Endpoints statt. Mithilfe einer Reihe von<strong> if<\/strong>-Abfragen wird der <strong>type<\/strong> der eingehenden Nachricht ausgewertet und je nach Inhalt der passende Logikblock ausgef\u00fchrt:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Host und Join:<\/strong> Bei der Verbindung werden die Spieler registriert. Jeder Spieler erh\u00e4lt eine eindeutige <strong>player_id<\/strong>, die mit Hilfe der Bibliothek <strong>UUID<\/strong> generiert wird, und der Name wird gespeichert. Der erste Spieler wird automatisch als Host festgelegt.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>start_game:<\/strong> Dieser Block kann nur vom Host ausgel\u00f6st werden. Der Server setzt das Spiel auf \u201eaktiv\u201c, generiert zuf\u00e4llige Startpositionen f\u00fcr alle Spieler und signalisiert allen Clients den Spielbeginn.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>player_state:<\/strong> Dieser Block wird am h\u00e4ufigsten genutzt, um Positionen und Bewegungen der Spieler zu synchronisieren. Um Bandbreite zu sparen und Ruckler beim Absender zu vermeiden, habe ich eine <strong>broadcast_except_self<\/strong>-Funktion eingesetzt, die die Daten an alle Spieler au\u00dfer dem Sender verteilt.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>hit:<\/strong> Meldet ein Client einen Treffer, berechnet der Server den neuen \u201eKnockback\u201c-Wert und informiert alle Teilnehmer \u00fcber die Aktualisierung. Die Berechnung erfolgt bewusst auf dem Server, da beim ersten Prototypen, bei dem der Client die Berechnung \u00fcbernommen hat, extreme Bugs und Fehlerwerte auftraten. Durch die Server-Berechnung ist die Konsistenz gew\u00e4hrleistet, und professionell betrachtet, sogar ein Schutz gegen Cheating.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>player_died:<\/strong> Hier pr\u00fcft der Server, ob nach dem Ausscheiden eines Spielers noch gen\u00fcgend Teilnehmer aktiv sind. Ist nur noch ein Spieler \u00fcbrig, wird das Spiel beendet und der Sieger bekannt gegeben.<\/li>\n<\/ul>\n\n\n\n<p>Neben diesen Aufgaben \u00fcbernimmt der Server noch eine weitere wichtige Funktion, das <strong>Ressourcenmanagement<\/strong>. Die Details dazu werden zwar sp\u00e4ter beim<strong> Deployment<\/strong> noch einmal relevant, aber es lohnt sich, die grundlegende Logik schon hier zu erkl\u00e4ren.<\/p>\n\n\n\n<p>Da jeder Server nur f\u00fcr ein einzelnes Spiel zust\u00e4ndig ist, ist es wichtig, dass er nach Spielende wieder heruntergefahren oder abgeschaltet wird. Daf\u00fcr habe ich mir die folgenden Sicherheitsmechanismen \u00fcberlegt:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>shutdown_sequence<\/strong>: Nach Spielende oder beim Verbindungsabbruch des Hosts wird der Matchmaker \u00fcber den Endpunkt <strong>\/session_done<\/strong> benachrichtigt. Nach einer kurzen Verz\u00f6gerung beendet sich der Prozess \u00fcber <strong>os._exit(0)<\/strong>. <\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>idle_timeout_check<\/strong>: Um ungenutzte Sessions zu vermeiden, die erstellt, aber nie gestartet wurden, gibt es einen <strong>5-Minuten-Timer<\/strong>. Erfolgt innerhalb dieser Zeit kein Spielstart, f\u00e4hrt der Server selbstst\u00e4ndig herunter.<\/li>\n<\/ul>\n\n\n\n<p>Ich habe mir fr\u00fch Gedanken \u00fcber diese Art von Sicherheitsmechanismen gemacht, da in der realen Welt <strong>Effizienz<\/strong> und <strong>Kosteneinsparung<\/strong> eine gro\u00dfe Rolle spielen. Wenn Server weiterlaufen, obwohl sie nicht mehr ben\u00f6tigt werden, k\u00f6nnen schnell hohe Kosten entstehen, und der Speicher der Systeme w\u00fcrde sich unn\u00f6tig f\u00fcllen, was im schlimmsten Fall zu Abst\u00fcrzen f\u00fchren kann. Aus diesem Grund habe ich diese Mechanismen direkt in den Game Server integriert.<\/p>\n\n\n\n<p>Eine weitere Sache, die hier wichtig ist, ist <strong>CORS (Cross-Origin Resource Sharing)<\/strong>. In der Web-Entwicklung sorgt CORS quasi daf\u00fcr, dass ein Server Anfragen von anderen Webseiten oder Domains akzeptieren darf, man kann es sich als eine Art \u201eSicherheitsfreigabe\u201c vorstellen.<\/p>\n\n\n\n<p>Ich erw\u00e4hne das hier, weil ich selbst Probleme hatte, als ich meine FastAPI-Implementierung getestet habe. Beim Versuch, mich vom Frontend mit dem Game Server zu verbinden, bekam ich eine Fehlermeldung, dass die Verbindung nicht aufgebaut werden darf. Nach einigem Nachforschen und Ausprobieren stellte sich heraus, dass ich CORS in meiner Anwendung aktivieren musste. Ohne diese Einstellung verweigert der Browser aus Sicherheitsgr\u00fcnden die Verbindung zum Server.<\/p>\n\n\n\n<p>Um CORS in FastAPI zu aktivieren, habe ich folgendes Middleware-Setup hinzugef\u00fcgt:<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-python\" data-line=\"\">app.add_middleware(\n    CORSMiddleware,\n    allow_origins=[&quot;*&quot;],\n    allow_credentials=True,\n    allow_methods=[&quot;*&quot;],\n    allow_headers=[&quot;*&quot;],\n)\n<\/code><\/pre>\n<\/div>\n\n\n\n<p>Mit <strong>allow_origins=[&#8220;*&#8221;]<\/strong> erlaubt man dem Browser, die Daten vom Game Server abzufragen, egal von welcher Domain die Anfrage kommt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Backend (Matchmaker)<\/h2>\n\n\n\n<p>Wie zuvor erw\u00e4hnt, fungiert der Matchmaker als Vermittler und Orchestrierer zwischen Client und Game-Server. Seine Aufgabe ist es, auf Anfrage eines Spielers eine neue Game-Server-Instanz zu starten oder bestehende Sessions zu vermitteln. Der Matchmaker wurde, genau wie der Game-Server, mit <strong>FastAPI<\/strong> umgesetzt. Auch hier kommt eine <strong>CORS-Middleware<\/strong> zum Einsatz, allerdings mit dem Unterschied, dass der Zugriff explizit auf die offizielle Domain des Frontends beschr\u00e4nkt wird.<\/p>\n\n\n\n<p>Ein wichtiger Punkt in der Architektur ist das <strong>Port-Management<\/strong>. Jedem neuen Game-Server wird ein eigener Port zugewiesen, damit die Datenpakete der Spieler beim Versenden eindeutig zugeordnet werden k\u00f6nnen. Da eine IP-Adresse jeden Port zur selben Zeit nur einmal vergeben kann, dient der Port als eindeutige Kennung f\u00fcr die jeweilige Spiel-Session. W\u00fcrden zwei Spiele gleichzeitig denselben Port nutzen, g\u00e4be es Adresskonflikte und der zweite Server k\u00f6nnte gar nicht erst starten.<\/p>\n\n\n\n<p>Um dies abzusichern, habe ich die Funktion <strong>get_next_free_port<\/strong> eingebaut, die daf\u00fcr sorgt, dass immer nur freie Ports aus einem festgelegten Bereich vergeben werden. Zus\u00e4tzlich pr\u00fcft eine weitere Funktion, <strong>is_port_open<\/strong>, mittels einer Socket-Verbindung, ob ein neu gestarteter Server tats\u00e4chlich bereit ist, bevor die Verbindungsdaten an den Client weitergegeben werden. Dabei ist zu beachten, dass sich diese Socket-Verbindung von der WebSocket-Verbindung unterscheidet. Sie dient lediglich als eine Art \u201eAnklopfen\u201c. Sobald der Game Server antwortet, wird die Verbindung direkt wieder geschlossen. Der Grund, warum ich <strong>is_port_open<\/strong> implementiert habe, wird im sp\u00e4teren Kapitel <strong>Probleme<\/strong> <strong>&amp; Herausforderungen<\/strong> noch wichtig.<\/p>\n\n\n\n<p>Um die Systemressourcen zu schonen, ist der Portbereich auf 30.000 bis 30.100 begrenzt, was maximal 100 gleichzeitig laufende Game Server erlaubt.<\/p>\n\n\n\n<p>Neben dem Starten von Servern fungiert der Matchmaker auch als <strong>zentrales Verzeichnis<\/strong>. Er speichert jeden aktiven Game-Code zusammen mit der IP-Adresse und dem Port in einer Liste (<strong>active_sessions<\/strong>). Wenn ein weiterer Spieler einer Runde beitreten m\u00f6chte, muss er lediglich den Spiel-Code angeben. Der Matchmaker gleicht diesen Code mit der Liste ab und liefert die passenden Verbindungsdaten zur\u00fcck.<\/p>\n\n\n\n<p>Im Gegensatz zum Game Server l\u00e4uft die Kommunikation beim Matchmaker aber nicht \u00fcber WebSockets, sondern \u00fcber <strong>HTTP-Anfragen<\/strong>. Der Lebenszyklus einer Spiel-Session wird dabei \u00fcber drei zentrale Endpunkte gesteuert:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Erstellung (\/create_session)<\/strong>: Wenn ein Spieler ein Spiel hostet, generiert der Matchmaker einen eindeutigen vierstelligen Game-Code und sucht einen freien Port. Anschlie\u00dfend startet er eine neue Instanz des Game-Servers.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Beitritt (\/join_session)<\/strong>: Spieler k\u00f6nnen \u00fcber den Game-Code die zugeh\u00f6rigen Verbindungsdaten (IP und Port) aus dem internen Speicher des Matchmakers abrufen.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Abschluss (\/session_done)<\/strong>: Sobald eine Session beendet ist, sendet der Game-Server ein Signal an diesen Endpunkt. Der Matchmaker entfernt die Session aus seiner Liste, wodurch der Port sofort f\u00fcr neue Instanzen freigegeben wird.<\/li>\n<\/ul>\n\n\n\n<p>Im Grunde ist das die Architektur des Matchmakers, relativ simpel gehalten. Ich m\u00f6chte au\u00dferdem erw\u00e4hnen, dass ich bei beiden Backends (<strong>Game Server<\/strong> und <strong>Matchmaker<\/strong>) keinen ausf\u00fchrlichen Code eingef\u00fcgt habe, da dies den Rahmen dieses Blogs sprengen w\u00fcrde. Mir geht es hier vor allem darum, die <strong>Funktionsweise meines Systems<\/strong> zu erkl\u00e4ren. Deshalb findet man in diesen beiden Kapiteln auch keine Codesnippets, abgesehen von der CORS-Implementierung.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Deployment (IaaS)<\/h2>\n\n\n\n<p>Als ich fertig mit der grundlegenden Codebasis war und das Spiel lokal auf meinem Rechner, von der Anfrage zum Matchmaker bis hin zur eigentlichen Verbindung mit dem Game-Server, stabil lief, war meine n\u00e4chste Aufgabe, diese Architektur in einer <strong>Cloud<\/strong> zu hosten. Lokal w\u00e4re das Spiel zwar im eigenen Heimnetzwerk nutzbar gewesen, aber das entsprach zum einen nicht meiner Vorstellung eines echten Multiplayers. Zum anderen wollte ich die Technologien und Ans\u00e4tze, die in der Vorlesung angesprochen wurden, selbst ausprobieren und das Projekt in eine realit\u00e4tsnahe Umgebung einbetten. <\/p>\n\n\n\n<p>Viele moderne Systeme und Services basieren heute auf Cloud-L\u00f6sungen, zum Beispiel bei Amazon oder Netflix. Der Hauptgrund daf\u00fcr ist zum einen die <strong>globale Erreichbarkeit<\/strong> und zum anderen die <strong>flexible Skalierbarkeit<\/strong>. In meinem Fall bedeutet das, dass die Infrastruktur theoretisch mitwachsen kann, wenn die Last steigt, etwa dann, wenn viele Spieler gleichzeitig neue Sessions erstellen. <\/p>\n\n\n\n<p>F\u00fcr das Deployment habe ich mich f\u00fcr ein <strong>Droplet<\/strong> beim Cloud-Anbieter <strong>DigitalOcean<\/strong> entschieden. Dabei handelt es sich um ein klassisches <strong>Infrastructure-as-a-Service-Modell (IaaS)<\/strong>. Im Gegensatz zu fertigen Software-L\u00f6sungen bekommt man hier die volle Kontrolle \u00fcber das Betriebssystem. Zum einen habe ich mich f\u00fcr diesen Ansatz entschieden, weil ich lernen wollte, wie man einen \u201eechten\u201c Server konfiguriert und ein eigenes Projekt darauf zum Laufen bringt. Zum anderen war dieses Modell auch notwendig, da ich die Docker-Engine selbst verwalten muss, um Game-Server-Instanzen dynamisch zu starten und die Ports im gew\u00fcnschten Bereich gezielt freizugeben.<\/p>\n\n\n\n<p>Damit kommen wir auch direkt zum n\u00e4chsten wichtigen Baustein f\u00fcr das Deployment, <strong>Docker<\/strong>. Docker ist ein Werkzeug, mit dem sich Anwendungen in isolierten Umgebungen, sogenannten <strong>Containern<\/strong>, verpacken und ausf\u00fchren lassen. Der Grund f\u00fcr die Nutzung von Docker war \u00e4hnlich wie beim Droplet. Einerseits wollte ich den Umgang mit Containern besser lernen, da ich zuvor nur wenig Erfahrung damit hatte. Andererseits war Docker f\u00fcr den Cloud-Betrieb sehr sinnvoll.<\/p>\n\n\n\n<p>Ich h\u00e4tte das gesamte Spiel theoretisch auch direkt auf dem Betriebssystem des Droplets installieren k\u00f6nnen. Allerdings stellt sich dann die Frage, was passiert, wenn ich den Cloud-Anbieter wechseln m\u00f6chte. Das Droplet bei DigitalOcean nutzt Linux als Betriebssystem, w\u00fcrde ich sp\u00e4ter auf ein Windows-System wechseln, m\u00fcssten eventuell Teile der Anwendung angepasst werden, da jedes Betriebssystem eigene technische Unterschiede hat. Durch das Verpacken von Frontend und den Backends in Container habe ich dieses Problem umgangen und die Anwendung deutlich unabh\u00e4ngiger vom darunterliegenden System gemacht.<\/p>\n\n\n\n<p>Dadurch ergeben sich mehrere Vorteile:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Isolation und Konsistenz:<\/strong> Zum Beispiel ein Game-Server-Container enth\u00e4lt alles, was er zum Laufen braucht (Python-Laufzeitumgebung, Bibliotheken, Code). Dadurch ist garantiert, dass das Spiel in der Cloud exakt so funktioniert wie auf meinem lokalen Rechner, unabh\u00e4ngig von der Konfiguration des Droplets. <\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Dynamische Instanziierung:<\/strong> Da der Game-Server als Docker-Image vorliegt, kann der Matchmaker bei Bedarf sekundenschnell eine neue, saubere Instanz starten. Sobald eine Runde vorbei ist, wird der Container einfach gel\u00f6scht. Das h\u00e4lt das System sauber und gibt Ressourcen sowie Ports sofort wieder frei.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Sicherheit und Port-Mapping:<\/strong> Durch Docker kann ich gezielt steuern, welche Ports nach au\u00dfen hin ge\u00f6ffnet werden. W\u00e4hrend der Matchmaker fest auf einem Port kommuniziert, weise ich den Game-Server-Containern beim Start dynamisch Ports aus meinem Bereich (<strong>30.000 bis 30.100<\/strong>) zu. Dies geschieht \u00fcber das sogenannte Port-Mapping, bei dem ein Port des Containers auf einen Port des Droplets gespiegelt wird.<\/li>\n<\/ul>\n\n\n\n<p>Durch die Kombination aus einem IaaS-Droplet und der Docker-Engine habe ich mir im Grunde eine eigene kleine \u201eGame-Server-Cloud\u201c aufgebaut, die flexibel auf Spieleranfragen reagieren kann, ohne dass ich f\u00fcr jede neue Runde manuell eingreifen muss. Man k\u00f6nnte sagen, dass ich damit eine Art reduziertes, selbstgebautes <strong>Kubernetes<\/strong> erstellt habe, einen eigenen <strong>Orchestrator<\/strong>, der speziell auf meine Anforderungen zugeschnitten ist.<\/p>\n\n\n\n<p>Kubernetes ist ein extrem m\u00e4chtiges, aber auch hochkomplexes Werkzeug mit einer, meiner Meinung nach, sehr steilen Lernkurve. F\u00fcr mein Projekt w\u00e4re der Einsatz von Kubernetes \u00fcberdimensioniert gewesen (\u201eOverkill\u201c). Mit meinem Ansatz habe ich daher zwei Fliegen mit einer Klappe geschlagen. Ich konnte die grundlegende Funktionsweise von Kubernetes durch den Eigenbau verstehen und gleichzeitig eine leichtgewichtige L\u00f6sung schaffen, die exakt zu den Bed\u00fcrfnissen meines Spiels passt. <\/p>\n\n\n\n<p>Bevor wir auf die technischen Details der Container eingehen und darauf, wie sie aufgebaut sind, m\u00f6chte ich noch einmal auf einen Punkt zur\u00fcckkommen, den ich bereits im Kapitel <strong>Backend (Game Server)<\/strong> angesprochen habe, <strong>das Ressourcenmanagement<\/strong>. Dort habe ich die Sicherheitsmechanismen beschrieben, mit denen fertige oder ungenutzte Spiel-Sessions sauber beendet werden. In diesem Zusammenhang hatte ich auch den Aufruf <strong>os.exit(0)<\/strong> erw\u00e4hnt.<\/p>\n\n\n\n<p>Das Besondere bei Containern ist, dass mit diesem Befehl nicht nur der Prozess endet, sondern gleich der gesamte Container beendet und entfernt wird. Das bedeutet, dass nicht der Matchmaker die Game-Server-Container aktiv herunterf\u00e4hrt, sondern dass sich die Container selbst beenden, sobald eine Spiel-Session nicht mehr gebraucht wird. Dadurch hat der Matchmaker nur die Aufgabe, neue Game Server zu starten, muss sich aber nicht darum k\u00fcmmern, wie lange eine Session l\u00e4uft oder wann sie beendet wird. Das reduziert die Komplexit\u00e4t und die Last, da keine zus\u00e4tzlichen \u00dcberpr\u00fcfungen notwendig sind.<\/p>\n\n\n\n<p>Genau dieser Ansatz hat mich w\u00e4hrend der Entwicklung besonders fasziniert. Jeder gestartete Game-Server-Container ist komplett unabh\u00e4ngig von allen anderen. Es wird keine zentrale Liste oder ein Array gef\u00fchrt, das den Zustand aller Game Server \u00fcberwacht, sondern jeder Container entscheidet selbst, wann er sich beendet. Bevor er herunterf\u00e4hrt, informiert er lediglich den Matchmaker eigenst\u00e4ndig dar\u00fcber, dass die Session beendet wurde, sodass die zuvor belegten Ports wieder freigegeben werden k\u00f6nnen.<\/p>\n\n\n\n<p>Doch kommen wir nun zu der technischen Umsetzung und Zusammensetzung der unterschiedlichen Container. Grunds\u00e4tzlich legt man die Regeln zum Erstellen eines Containers in einer sogenannten <strong>Dockerfile<\/strong> fest. In so einer Dockerfile steht zum Beispiel, welches Basis-Image verwendet werden soll (etwa ein schlankes Python-System), welche Dateien kopiert werden m\u00fcssen und welche Befehle beim Start des Containers ausgef\u00fchrt werden. F\u00fcr mein Projekt habe ich drei spezifische Dockerfiles entwickelt, die jeweils eine eigene S\u00e4ule meiner Architektur bilden. <\/p>\n\n\n\n<p>Das Frontend basiert auf einem <strong>Caddy-Image<\/strong>. Caddy ist ein moderner, sehr leichtgewichtiger Webserver, der sich perfekt dazu eignet, die exportierten Spieldateien auszuliefern. Ein wichtiger Punkt in diesem Dockerfile ist die Konfiguration der<strong> HTTP-Header<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Cross-Origin-Opener-Policy: same-origin<\/strong><\/li>\n\n\n\n<li><strong>Cross-Origin-Embedder-Policy: require-corp<\/strong><\/li>\n<\/ul>\n\n\n\n<p>Diese Sicherheits-Header musste ich selbst mitliefern, da der Browser die Ausf\u00fchrung des Spiels ansonsten aus Sicherheitsgr\u00fcnden blockiert h\u00e4tte. Durch meine Recherche bin ich darauf gesto\u00dfen, dass dies damit zusammenh\u00e4ngt, dass Web-Games, abh\u00e4ngig von der verwendeten Engine, h\u00e4ufig auf <strong>SharedArrayBuffer<\/strong> und <strong>Multithreading<\/strong> setzen.<\/p>\n\n\n\n<p>Beim Matchmaker habe ich ein <strong>Python 3.10-slim<\/strong> Image verwendet. Das Besondere dabei, damit der Matchmaker selbst Container starten kann, installiere ich im Dockerfile zus\u00e4tzlich die <strong>docker.io<\/strong> Bibliothek. Sie ist haupts\u00e4chlich daf\u00fcr da, dass der Container f\u00fcr den Matchmaker \u00fcberhaupt die notwendigen Werkzeuge besitzt, um mit der Docker-Engine des Droplets zu kommunizieren. Diese Engine hatte ich ja schon zu Beginn direkt auf dem Droplet installiert, um dort \u00fcberhaupt Docker nutzen zu k\u00f6nnen.<\/p>\n\n\n\n<p>Doch mit dem Dockerfile allein ist es noch nicht getan. Da das Frontend und der Matchmaker immer zusammen laufen m\u00fcssen, habe ich eine <strong>Docker Compose Datei<\/strong> erstellt. Man kann sich diese Datei wie eine Art Gruppenanweisung vorstellen. Statt jeden Container m\u00fchsam einzeln mit langen Befehlen zu starten, schreibt man alles einmal in diese Datei und startet das gesamte System gleichzeitig.<\/p>\n\n\n\n<p>In dieser Docker Compose Datei konnte ich dann auch sogenannte <strong>Volumes<\/strong> festlegen. Normalerweise nutzt man Volumes bei Docker, um Daten (z.B Bilder oder Datenbanken) dauerhaft zu speichern, damit sie nicht gel\u00f6scht werden, wenn man den Container stoppt.<\/p>\n\n\n\n<p>In meinem Fall habe ich das Volume aber f\u00fcr einen technischen Zweck genutzt. Ich habe den Docker-Socket des Droplets (<strong>\/var\/run\/docker.sock<\/strong>) direkt in den Matchmaker-Container gemountet. Man kann sich das wie ein Verbindungskabel vorstellen.<\/p>\n\n\n\n<p>Ohne dieses Volume w\u00e4re der Matchmaker in seinem Container komplett isoliert. Er w\u00fcsste gar nicht, dass er auf einem Server mit einer Docker-Engine l\u00e4uft. Durch das Volume &#8220;spiegle&#8221; ich den Zugriff auf die Docker-Steuerung des Droplets direkt in den Container hinein. Zusammen mit der installierten <strong>docker.io<\/strong> Bibliothek kann mein Matchmaker-Code jetzt so tun, als w\u00e4re er direkt auf dem Droplet installiert. Er kann also einfach &#8220;nach drau\u00dfen&#8221; greifen und der Docker-Engine sagen, dass sie einen neuen Game-Server-Container starten soll.<\/p>\n\n\n\n<p>Ein weiterer Vorteil der Compose-Datei war die \u00dcbersicht beim <strong>Networking<\/strong>. Ich konnte genau festlegen, dass das Frontend auf Port <strong>80<\/strong> f\u00fcr alle Spieler erreichbar ist, w\u00e4hrend der Matchmaker auf Port <strong>8001<\/strong> auf Anfragen wartet. Die eigentlichen Game-Server stehen nicht in der Compose-Datei, weil sie ja erst erstellt werden, wenn wirklich jemand spielen m\u00f6chte.<\/p>\n\n\n\n<p>Dass der Container nach dem Spiel sofort verschwindet (nach <strong>os.exit(0)<\/strong>), liegt an der aktivierten Funktion <strong>auto_remove=True<\/strong>. Beim Programmieren des Matchmakers habe ich festgelegt, dass jeder neue Game Server mit diesem Flag gestartet wird.<\/p>\n\n\n\n<p>Normalerweise bleiben Container nach dem Beenden im Status \u201eExited\u201c auf dem System liegen und verbrauchen weiterhin Speicherplatz, bis man sie manuell l\u00f6scht. Durch <strong><strong>auto_remove=True<\/strong><\/strong> erkennt die Docker-Engine auf dem Droplet aber sofort, wenn der Game-Server-Prozess fertig ist, und r\u00e4umt den kompletten Container automatisch weg. Das war f\u00fcr mich die einfachste L\u00f6sung, um sicherzustellen, dass das Droplet nicht irgendwann voll l\u00e4uft:<\/p>\n\n\n\n<div class=\"wp-block-jetpack-markdown\"><pre><code class=\"language-python\" data-line=\"\">container = client.containers.run(\n           &quot;mein-game-server&quot;,\n           detach=True,\n           ports={&#039;8000\/tcp&#039;: (&#039;0.0.0.0&#039;, assigned_port)},\n           environment={&quot;GAME_SESSION_CODE&quot;: code},\n           auto_remove=True\n       )\n<\/code><\/pre>\n<\/div>\n\n\n\n<p>Zuletzt haben wir das Dockerfile f\u00fcr den Game-Server. Man kann sich dieses Dockerfile als die \u201eBlaupause\u201c vorstellen, die der Matchmaker jedes Mal nutzt, wenn er einen neuen Container startet. Da diese Container erst in dem Moment entstehen, in dem ein Spieler ein Spiel anfragt, war es mir wichtig, dass das Image m\u00f6glichst klein ist und schnell startet. Ich habe mich hier ebenfalls f\u00fcr ein <strong>Python-Slim-Image<\/strong> entschieden. In diesem Dockerfile werden lediglich die n\u00f6tigen Bibliotheken f\u00fcr die Spiellogik (wie FastAPI und Uvicorn f\u00fcr die WebSockets) installiert und anschlie\u00dfend der eigentliche Programmcode des Spiels hineinkopiert.<\/p>\n\n\n\n<p>Besonders wichtig war f\u00fcr mich hier das <strong>Port-Konzept<\/strong> innerhalb des Dockerfiles. Im Dockerfile ist fest hinterlegt, dass der Server intern immer auf Port 8000 lauscht. Das macht die Programmierung einfach, da ich im Spielcode keine variablen Ports verwalten muss. Da die internen Container-Ports sowieso isoliert voneinander sind und keinen Einfluss auf andere Container haben, konnte ich das so beibehalten. Die eigentliche Portzuweisung wird, wie bereits behandelt, durch den Matchmaker vorgenommen. Dieser mappt einen freien externen Port (aus meinem Bereich von 30.000 bis 30.100) immer auf dieselbe interne Portnummer des Containers (8000).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">HTTP vs HTTPS<\/h2>\n\n\n\n<p>Am Anfang war mein System nur \u00fcber die reine IP-Adresse meines Droplets erreichbar (46.101.127.20). Das funktionierte zwar, lief aber ausschlie\u00dflich \u00fcber <strong>HTTP<\/strong>. Sobald ich jedoch versuchte, die Browser-Funktionen f\u00fcr das Spiel zu nutzen, stie\u00df ich auf zwei gro\u00dfe Probleme:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Zertifikats-Problem:<\/strong> F\u00fcr eine reine IP-Adresse bekommt man kein kostenloses SSL-Zertifikat von Anbietern wie Let&#8217;s Encrypt. Ohne Zertifikat gibt es <strong>kein HTTPS<\/strong>.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Sicherheits-Blockade:<\/strong> Moderne Browser blockieren heute viele Funktionen, wie zum Beispiel den <strong>SharedArrayBuffer<\/strong>, der f\u00fcr die Spiel-Engine wichtig ist, oder zeigen deutliche Warnmeldungen an, wenn eine Seite nicht \u00fcber HTTPS ausgeliefert wird. In meinem Fall kam es sogar vor, dass der Browser das Frontend komplett blockierte, da es nur \u00fcber HTTP erreichbar war.<\/li>\n<\/ul>\n\n\n\n<p>Um zun\u00e4chst ein SSL-Zertifikat zu bekommen, br\u00e4uchte man eigentlich eine eigene Domain. Da Domains aber meistens Geld kosten und ich das vermeiden wollte, habe ich einen Dienst namens <strong>sslip.io<\/strong> genutzt. Dieser Dienst macht etwas Einfaches, aber Geniales. Er wandelt meine IP-Adresse in einen Hostnamen um. Aus <strong>46.101.127.20<\/strong> wurde so <strong>46.101.127.20.sslip.io<\/strong>.<\/p>\n\n\n\n<p>Der Vorteil ist, dass dies jetzt wie eine \u201eechte\u201c Domain aussieht und mein <strong>Caddy-Webserver<\/strong> endlich ein offizielles SSL-Zertifikat anfordern konnte. Das Coole an Caddy ist, dass es diesen Prozess komplett automatisch erledigt. Es stellt selbstst\u00e4ndig eine Verbindung zu einer Zertifizierungsstelle wie <strong>Let&#8217;s Encrypt<\/strong> oder <strong>ZeroSSL<\/strong> her und besorgt sich ein legitimes SSL-Zertifikat. Somit war das erste Problem gel\u00f6st und mein Frontend lief endlich \u00fcber <strong>HTTPS<\/strong>.<\/p>\n\n\n\n<p>Nachdem mein Frontend \u00fcber HTTPS erreichbar war, tauchte das n\u00e4chste Problem auf. Das Frontend versuchte nun, eine Verbindung zum Matchmaker und zu den Game Servern aufzubauen. Diese liefen allerdings noch auf \u201eunsicheren\u201c Ports wie zum Beispiel 8001 oder 30005 \u00fcber HTTP.<\/p>\n\n\n\n<p>Der Browser blockierte dies sofort wegen sogenanntem <strong>Mixed Content<\/strong>. Wenn eine Hauptseite \u00fcber HTTPS geladen wird, d\u00fcrfen dar\u00fcber keine unsicheren HTTP- oder WS-Verbindungen aufgebaut werden. Dadurch konnte mein Spiel keine Verbindung zum Backend herstellen. Wie bereits beschrieben, l\u00e4uft die Kommunikation zwischen Client und Game Server \u00fcber WebSockets. Auch hier gibt es eine sichere und eine unsichere Variante, n\u00e4mlich ws (Websocket) und<strong> wss (Websocket Secure)<\/strong>. Sobald das Frontend \u00fcber HTTPS l\u00e4uft, m\u00fcssen alle WebSocket-Verbindungen ebenfalls \u00fcber wss erfolgen. Versuchte mein Spiel also, sich \u00fcber ws mit einem Game Server zu verbinden, blockierte der Browser diese Verbindung sofort und verlangte zwingend eine sichere wss-Verbindung.<\/p>\n\n\n\n<p>Das eigentliche Problem war jedoch, dass meine Game Server selbst nichts von SSL-Zertifikaten wussten. Sie kommunizieren intern ausschlie\u00dflich \u00fcber normales ws. Ich musste also einen Weg finden, die Verschl\u00fcsselung nach au\u00dfen hin bereitzustellen, ohne jeden einzelnen Game Server umbauen zu m\u00fcssen.<\/p>\n\n\n\n<p>Um diesen Konflikt zu l\u00f6sen, habe ich den gesamten Datenverkehr \u00fcber Caddy geleitet. Anstatt dass der Spiel-Client im Browser direkt eine Verbindung zum Port des Game Servers aufbaut, schickt er seine Anfrage an die sichere Adresse meines <strong>Reverse Proxys<\/strong>, also an Caddy. Erst relativ sp\u00e4t habe ich festgestellt, dass sich Caddy nicht nur als Webserver, sondern auch sehr gut als Reverse Proxy nutzen l\u00e4sst.<\/p>\n\n\n\n<p>W\u00e4hrend ein normaler Proxy meist daf\u00fcr gedacht ist, aus einem internen Netzwerk nach au\u00dfen zu kommunizieren, funktioniert ein Reverse Proxy genau umgekehrt. Er nimmt Anfragen aus dem \u00f6ffentlichen Internet entgegen und leitet sie sicher an interne Dienste weiter. In meinem Fall sind das die Game Server Container.<\/p>\n\n\n\n<p>Konkret sendet das Frontend eine Anfrage in der Form <strong>wss:\/\/46.101.127.20.sslip.io\/game\/30005<\/strong> an Caddy. Damit signalisiert der Client, dass er sich mit dem Game Server auf Port 30005 verbinden m\u00f6chte. Caddy \u00fcbernimmt hier die Rolle eines \u00dcbersetzers. Er nimmt die verschl\u00fcsselte wss-Anfrage entgegen, pr\u00fcft sie mit dem vorhandenen SSL-Zertifikat und entschl\u00fcsselt die Datenpakete.<\/p>\n\n\n\n<p>Anschlie\u00dfend leitet Caddy diese Daten intern als <strong>normales ws-Protokoll<\/strong> an den entsprechenden Game Server weiter. Der R\u00fcckweg funktioniert nach dem gleichen Prinzip. Sendet der Game Server Daten zur\u00fcck, gehen diese zun\u00e4chst unverschl\u00fcsselt an Caddy, werden dort sofort wieder verschl\u00fcsselt und anschlie\u00dfend sicher als <strong>wss-Paket<\/strong> an den Browser zur\u00fcckgesendet.<\/p>\n\n\n\n<p>Diese Umstellung war f\u00fcr die Funktion des Projekts absolut entscheidend, da moderne Browser ohne wss-Verbindungen das Spiel schlichtweg nicht starten w\u00fcrden. Zwar g\u00e4be es theoretisch die M\u00f6glichkeit, im Browser <strong>unsichere Inhalte<\/strong> manuell zu erlauben, f\u00fcr einen realistischen Einsatz w\u00e4re das jedoch v\u00f6llig ungeeignet. Durch den Einsatz von Caddy konnte ich die Sicherheitsanforderungen der Browser erf\u00fcllen, die Kommunikation verschl\u00fcsseln und gleichzeitig den Code der Game Server sehr einfach halten. Da Caddy die komplette SSL-Verarbeitung \u00fcbernimmt, muss sich der Game Server selbst nicht um Verschl\u00fcsselung k\u00fcmmern, was die Komplexit\u00e4t und Fehleranf\u00e4lligkeit deutlich reduziert.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">CI\/CD-Pipeline &amp; Tests<\/h2>\n\n\n\n<p>Eine weitere Sache, die ich kurz ansprechen m\u00f6chte, ist meine kleine CI\/CD-Pipeline, die ich f\u00fcr dieses Projekt eingerichtet habe. Sie sorgt daf\u00fcr, dass \u00c4nderungen am Code nach einem Push automatisch auf meinem Droplet bereitgestellt werden. Dabei verbindet sich GitHub w\u00e4hrend der Pipeline per <strong>SSH<\/strong> mit dem Droplet und f\u00fchrt dort die notwendigen Befehle aus, um die Anwendung zu aktualisieren und die ben\u00f6tigten Docker-Container zu bauen oder neu zu starten.<\/p>\n\n\n\n<p>Damit dieser Zugriff sicher und zuverl\u00e4ssig funktioniert, habe ich ein <strong>SSH-Schl\u00fcsselpaar<\/strong> erstellt, bestehend aus einem <strong>privaten <\/strong>und einem <strong>\u00f6ffentlichen<\/strong> Schl\u00fcssel. <strong>Der \u00f6ffentliche Schl\u00fcssel wurde auf dem Droplet<\/strong> hinterlegt, w\u00e4hrend ich den <strong>privaten Schl\u00fcssel in den GitHub Secrets<\/strong> gespeichert habe. Dadurch ist sichergestellt, dass sich ausschlie\u00dflich meine Pipeline mit dem Server verbinden kann und es nicht zu Zugriffsverweigerungen oder interaktiven Passwortabfragen kommt.<\/p>\n\n\n\n<p>Bevor das eigentliche Deployment ausgef\u00fchrt wird, war es mir wichtig, dass bestimmte <strong>Tests<\/strong> automatisch laufen. Dadurch wollte ich sicherstellen, dass keine fehlerhaften \u00c4nderungen auf dem Droplet landen und dort Probleme verursachen. F\u00fcr den <strong>Matchmaker<\/strong> und den <strong>Game Server<\/strong> habe ich deshalb einfache, aber zentrale Tests geschrieben. Dabei \u00fcberpr\u00fcfe ich unter anderem die <strong>korrekte Vergabe der Ports<\/strong>, <strong>den erfolgreichen Aufbau einer WebSocket-Verbindung<\/strong> sowie das Verhalten des Matchmakers, <strong>wenn keine aktive Spiel-Session existiert<\/strong>.<\/p>\n\n\n\n<p>F\u00fcr die Umsetzung der Pipeline habe ich <strong>GitHub Actions<\/strong> verwendet. Dazu habe ich eine <strong>deploy.yml<\/strong>-Datei erstellt, in der die einzelnen Schritte und Regeln der Pipeline definiert sind. Diese habe ich anschlie\u00dfend direkt in GitHub eingebunden, sodass die Pipeline automatisch bei bestimmten Events, wie einem Push auf den Main-Branch, ausgef\u00fchrt wird.<\/p>\n\n\n\n<p>Die Implementierung der CI\/CD-Pipeline erfolgte relativ sp\u00e4t, kurz vor der Abgabe des Projekts. Der Grund daf\u00fcr war, dass ich zun\u00e4chst wichtigere Priorit\u00e4ten hatte, wie eine stabile Spiellogik, eine funktionierende Serverkommunikation und ein sauber konfiguriertes Droplet. Trotzdem habe ich mich bewusst dazu entschieden, die Pipeline noch zu integrieren.<\/p>\n\n\n\n<p>Zum einen plane ich, das Projekt in Zukunft weiterzuf\u00fchren, und wollte vermeiden, bei jeder \u00c4nderung manuell das Repository auf dem Droplet aktualisieren oder die Docker-Container neu bauen und starten zu m\u00fcssen. Zum anderen war es f\u00fcr mich auch eine gute \u00dcbung, da ich diesen Schritt bei fr\u00fcheren privaten Projekten oft ausgelassen habe. Durch diese Pipeline ist der Entwicklungsprozess nun deutlich strukturierter, sicherer und langfristig wartbarer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Probleme &amp; Herausforderungen<\/h2>\n\n\n\n<p>W\u00e4hrend der Entwicklung meines Projekts bin ich auf zahlreiche Probleme gesto\u00dfen, die ich hier unm\u00f6glich alle im Detail beschreiben kann. Dennoch m\u00f6chte ich auf einige zentrale Herausforderungen eingehen, die mich besonders viel Zeit und Nerven gekostet haben. Zun\u00e4chst beginne ich mit den technischen Problemen, da diese den gr\u00f6\u00dften Einfluss auf den Entwicklungsprozess hatten.<\/p>\n\n\n\n<p>Im Kapitel <strong>Backend<\/strong> <strong>(Matchmaker) <\/strong>hatte ich bereits die Funktion <strong>is_port_free<\/strong> erw\u00e4hnt. Diese Funktion entstand nicht zuf\u00e4llig, sondern aus einem sehr konkreten Problem heraus. Nachdem mein erster Prototyp lokal auf meinem eigenen Rechner stabil lief, war ich \u00fcberzeugt, dass das System bereit f\u00fcr eine Pr\u00e4sentation w\u00e4hrend der Vorlesung sei. Am Tag der Pr\u00e4sentation wollte ich das Spiel unterwegs im Zug noch einmal testen, diesmal in einem anderen Netzwerk. Genau dort trat pl\u00f6tzlich ein unerwartetes Verhalten auf. Jedes Mal, wenn ich ein Spiel hosten wollte, erhielt ich im Browser eine Fehlermeldung. In seltenen F\u00e4llen funktionierte der Verbindungsaufbau, in den meisten F\u00e4llen jedoch nicht. Das war besonders verwirrend, da das System zu Hause zuverl\u00e4ssig lief.<\/p>\n\n\n\n<p>An der HdM schilderte ich dieses Problem dem Dozenten, der mir zwei entscheidende Fragen stellte. Erstens, ob der Game Server \u00fcberhaupt gestartet werde, und zweitens, ob die Verbindungsanfrage des Clients den Game Server tats\u00e4chlich erreiche. Der ersten Frage konnte ich relativ schnell nachgehen, da ich im Droplet sehen konnte, dass der Matchmaker den Game-Server-Container trotz Fehlermeldung im Browser korrekt startete. Damit r\u00fcckte die zweite Frage in den Fokus.<\/p>\n\n\n\n<p>Beim genaueren Debugging schaute ich mir schlie\u00dflich die Logdateien des Game Servers an und stellte fest, dass der Client sich nie mit diesem verbunden hatte. Nach l\u00e4ngerer Analyse wurde mir klar, dass zwar der Container selbst gestartet wurde, der darin laufende <strong>Uvicorn-Webserver<\/strong> jedoch zu diesem Zeitpunkt noch nicht bereit war. Der Matchmaker gab dem Client also bereits die Verbindungsdaten zur\u00fcck, obwohl der eigentliche Server, der die FastAPI-Anwendung bereitstellt, <strong>noch nicht auf Anfragen reagieren konnte<\/strong>. Genau aus diesem Grund implementierte ich die Funktion <strong>is_port_free<\/strong>. Sie f\u00fchrt dieses bereits beschriebene \u201eAnklopfen\u201c durch und stellt sicher, dass der Matchmaker erst dann antwortet, wenn der Uvicorn-Server tats\u00e4chlich erreichbar ist.<\/p>\n\n\n\n<p>Ein weiteres gro\u00dfes Problem war die Kommunikation \u00fcber HTTP und HTTPS. Mir war es sehr wichtig, eine saubere und sichere Verbindung zu haben, ohne dass Spieler in den Browsereinstellungen <strong>unsichere Inhalte<\/strong> aktivieren m\u00fcssen. Solche Warnungen k\u00f6nnten abschreckend wirken oder sogar den Eindruck einer unseri\u00f6sen oder potenziell gef\u00e4hrlichen Webseite vermitteln. Ich probierte zahlreiche Ans\u00e4tze aus, auch jenseits von Caddy, stie\u00df jedoch immer wieder auf dieselben Blockaden seitens des Browsers. Erst relativ sp\u00e4t entschied ich mich, zus\u00e4tzlich eine <strong>KI zu Hilfe<\/strong> zu nehmen, die mir schlie\u00dflich verst\u00e4ndlich erkl\u00e4rte, wie ich meine bestehende Architektur mithilfe von <strong>Caddy als Reverse Proxy<\/strong> korrekt absichern konnte. Erst dadurch gelang es mir, die WebSocket-Kommunikation zuverl\u00e4ssig \u00fcber <strong>wss <\/strong>bereitzustellen.<\/p>\n\n\n\n<p>Ein weiteres technisches Problem betraf die Wahl der Programmiersprache. Python eignet sich nur bedingt f\u00fcr Game Server, da Spiele kontinuierlich Daten senden. In meinem Fall geschah dies etwa <strong>60 Mal pro Sekunde pro Client<\/strong>. Mit wenigen Spielern funktionierte das noch problemlos, doch als ich an der HdM mit mehr als f\u00fcnf Spielern gleichzeitig testete, traten deutliche Probleme auf. Die Spielfiguren begannen zu <strong>springen<\/strong> oder sich scheinbar zu <strong>teleportieren<\/strong>. Der Grund daf\u00fcr liegt in der Natur von Python als interpretierter Sprache. F\u00fcr performante Echtzeit-Server w\u00e4ren kompilierten Sprachen wie C++ oder Go deutlich besser geeignet. Ich hatte mich dennoch f\u00fcr Python entschieden, da ich darin bereits Erfahrung hatte und der Fokus des Projekts nicht prim\u00e4r auf maximaler Performance lag.<\/p>\n\n\n\n<p>Neben diesen Punkten gab es weitere technische Schwierigkeiten. So f\u00fcllte sich der Speicher des Droplets relativ schnell, sobald viele Sessions gleichzeitig liefen, was letztlich sogar zu <strong>Abst\u00fcrzen<\/strong> f\u00fchrte. Bei der Wahl des Cloud-Anbieters war es mir wichtig, die Kosten so gering wie m\u00f6glich zu halten, da ich das Spiel nur f\u00fcr einen begrenzten Zeitraum hosten wollte. Da ich Probleme hatte, das GitHub Student Pack zu beanspruchen, konnte ich keine kostenlosen Cloud-Guthaben nutzen, die bei manchen Anbietern bis zu 200 Euro betragen. Schlie\u00dflich entschied ich mich f\u00fcr <strong>DigitalOcean<\/strong> und w\u00e4hlte bewusst das <strong>kleinste verf\u00fcgbare Droplet<\/strong>, um die laufenden Kosten m\u00f6glichst niedrig zu halten.<\/p>\n\n\n\n<p>Neben den technischen Herausforderungen gab es auch pers\u00f6nliche Schwierigkeiten. Da ich zuvor kaum Erfahrung mit komplexen Netzwerkarchitekturen hatte, bestand ein gro\u00dfer Teil der Entwicklungszeit aus <strong>Ausprobieren<\/strong>, <strong>Verwerfen<\/strong> und <strong>Neuaufbauen<\/strong>. Das f\u00fchrte h\u00e4ufig zu <strong>Frustration<\/strong>, insbesondere wenn Dinge trotz gro\u00dfer M\u00fche nicht funktionierten. Hinzu kam ein <strong>erheblicher Zeitdruck<\/strong>, da ich parallel mehrere anspruchsvolle F\u00e4cher belegte. Da ich zu diesem Zeitpunkt bereits relativ weit mit dem Projekt fortgeschritten war, kam es f\u00fcr mich nicht mehr infrage, es einfach zu verwerfen und mit einem neuen, leichteren Projekt von vorne zu beginnen.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Was habe ich gelernt?<\/h2>\n\n\n\n<p>W\u00e4hrend der Entwicklung hatte ich eine schwierige und teilweise sehr anstrengende Zeit, dennoch hat sich dieser Aufwand f\u00fcr mich absolut gelohnt. Ich konnte zahlreiche neue Konzepte kennenlernen, die ich zuvor weder verwendet noch wirklich gekannt hatte. Viele Inhalte aus der Vorlesung empfand ich als ideale Grundlage, um sie direkt in mein Projekt zu integrieren und praktisch anzuwenden.<\/p>\n\n\n\n<p>Dabei habe ich nicht nur eine cloudbasierte Anwendung entwickelt, sondern auch ein v\u00f6llig neues Verst\u00e4ndnis f\u00fcr den Aufbau von <strong>Netzwerkarchitekturen<\/strong> gewonnen. Konzepte wie <strong>Microservices<\/strong>, deren <strong>eigenst\u00e4ndiger Aufbau<\/strong> sowie <strong>deren Bereitstellung <\/strong>waren f\u00fcr mich extrem lehrreich und zugleich motivierend. Besonders spannend war es, diese nicht nur theoretisch zu verstehen, sondern selbst umzusetzen.<\/p>\n\n\n\n<p>Zum ersten Mal hatte ich au\u00dferdem intensiven Kontakt mit <strong>echtem Deployment<\/strong> auf einem Server. Ich habe gelernt, worauf man dabei achten muss, wie ein solches System verwaltet wird und wie Fehler in einer produktionsnahen Umgebung analysiert und behoben werden k\u00f6nnen. Auch mit <strong>Docker<\/strong> hatte ich erstmals sehr intensiv zu tun und verstehe nun deutlich besser, welchen Zweck Container erf\u00fcllen und welchen Mehrwert sie in modernen Architekturen bieten.<\/p>\n\n\n\n<p>Ein weiterer wichtiger Lernaspekt war das Thema <strong>Ressourcenmanagement<\/strong>. Ich habe gelernt, wie entscheidend es ist, ungenutzte Ressourcen konsequent zu beenden, da sonst unn\u00f6tige Kosten entstehen k\u00f6nnen, etwa wenn <strong>mehrere ungenutzte Game-Server<\/strong> parallel laufen. Da ich den Server selbst finanziert habe, wurde mir die Bedeutung dieses Konzepts besonders deutlich. <strong>Diese Erfahrung h\u00e4tte ich rein theoretisch so nicht machen k\u00f6nnen<\/strong>.<\/p>\n\n\n\n<p>Durch das Projekt habe ich au\u00dferdem gelernt, Technologien <strong>bewusst auszuw\u00e4hlen<\/strong>, <strong>sinnvoll voneinander zu trennen<\/strong> und <strong>kritisch zu hinterfragen<\/strong>, ob sie dem Projekt wirklich nutzen oder eher zus\u00e4tzliche Komplexit\u00e4t erzeugen. Auch wenn meine Netzwerkarchitektur sicher nicht perfekt ist und an einigen Stellen Optimierungspotenzial besitzt, habe ich eigenst\u00e4ndig eine funktionierende Architektur entworfen, die f\u00fcr mein zuk\u00fcnftiges Verst\u00e4ndnis, insbesondere im Bereich der Spieleentwicklung, sehr wertvoll sein wird.<\/p>\n\n\n\n<p>Ich habe nun ein deutlich besseres Verst\u00e4ndnis daf\u00fcr, wie zum Beispiel Multiplayer-Spiele auf einer grundlegenden Ebene funktionieren. Mir ist bewusst geworden, dass in professionellen Systemen weitere Microservices wie Authentifizierung oder Datenbanken integriert werden, die f\u00fcr mein Projekt zwar nicht notwendig waren, deren Funktionsweise ich nun jedoch besser nachvollziehen kann.<\/p>\n\n\n\n<p>Durch diese Vorlesung und die Umsetzung des Projekts f\u00fchle ich mich insgesamt deutlich sicherer im Bereich Softwareentwicklung, und insbesondere im Hinblick auf <strong>Deployment<\/strong> und <strong>Bereitstellung<\/strong> von Anwendungen.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Game: https:\/\/46.101.127.20.sslip.io\/ (online bis 31. M\u00e4rz 2026) (beachte Readme im Git!) Git Repo: https:\/\/github.com\/ek101-collab\/ArenaGame-with-ContainerSessions Einleitung und Motivation Im Rahmen der Vorlesung &#8220;System Engineering und Management&#8221; bestand die Kernaufgabe darin, ein Projekt zu konzipieren und umzusetzen, das moderne Web- und Cloud-Technologien nutzt. Zu Beginn der Projektphase lag mein Fokus auf einer klassischen Wetter-App. Das Ziel war [&hellip;]<\/p>\n","protected":false},"author":1320,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[1],"tags":[],"ppma_author":[1216],"class_list":["post-28654","post","type-post","status-publish","format-standard","hentry","category-allgemein"],"aioseo_notices":[],"jetpack_featured_media_url":"","jetpack-related-posts":[{"id":23679,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2022\/08\/31\/jobsuche-portal\/","url_meta":{"origin":28654,"position":0},"title":"Jobsuche Portal","author":"ag164","date":"31. August 2022","format":false,"excerpt":"SS22 - Dev4Cloud Projekt - von Robin H\u00e4rle und Anton Gerdts Ideenfindung \u00a0\u00a0\u00a0 Zu Beginn der Ideenfindungsphase f\u00fcr unser Projekt sahen wir uns die verschiedenen Apis auf Bund.dev an, um uns von der Thematik der verf\u00fcgbaren Daten inspirieren zu lassen. Wir entschieden uns ohne lange abzuw\u00e4gen daf\u00fcr ein Jobsuche-Portal mit\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":27563,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2025\/02\/28\/die-technische-entwicklung-einer-open-source-losung-zur-bildoptimierung\/","url_meta":{"origin":28654,"position":1},"title":"Die technische Entwicklung einer Open-Source-L\u00f6sung zur Bildoptimierung","author":"Lennart Gastler","date":"28. February 2025","format":false,"excerpt":"Im Rahmen meines Systems Engineering Projektes habe ich die shuto-api entwickelt \u2013 eine in Go geschriebene Open-Source-Bildoptimierungsl\u00f6sung. Mein Ziel war es, eine flexible, self-hostable und erweiterbare API zu erstellen, welche ohne viele Probleme in bereits bestehende Systeme integriert werden kann. Der Service erm\u00f6glicht es, Bilder zu komprimieren, zu skalieren sowie\u2026","rel":"","context":"In &quot;System Designs&quot;","block_context":{"text":"System Designs","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/system-designs\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/image-17.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/image-17.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/image-17.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/image-17.png?resize=700%2C400&ssl=1 2x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2025\/02\/image-17.png?resize=1050%2C600&ssl=1 3x"},"classes":[]},{"id":20593,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2021\/09\/25\/herzlichen-gluckwunsch-sie-haben-gewonnen\/","url_meta":{"origin":28654,"position":2},"title":"HERZLICHEN GL\u00dcCKWUNSCH &#8211; Sie haben gewonnen!","author":"Eric Prytulla","date":"25. September 2021","format":false,"excerpt":"\u00dcber Social Engineering und wie man sich sch\u00fctzen kann. Jeder kennt E-Mails mit Titeln wie diesem. Eine wildfremde Person verspricht Gewinne in Millionenh\u00f6he. Und alles, was daf\u00fcr ben\u00f6tigt wird, sind ein paar pers\u00f6nliche Daten. Ein Traum vieler Menschen wird wahr und man will dem Titel glauben. Doch was passiert, wenn\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/09\/Spam.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":24536,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2023\/03\/19\/von-studierenden-fur-studierende-grundlagentutorium-unterstutzung-empowerment\/","url_meta":{"origin":28654,"position":3},"title":"Von Studierenden f\u00fcr Studierende: Grundlagentutorium &#8211; Unterst\u00fctzung &amp; Empowerment","author":"Tamara Hezel","date":"19. March 2023","format":false,"excerpt":"Kim Bastiaanse, Tamara Hezel Einleitung Die Idee zu unserem Projekt entstand aus einem Gespr\u00e4ch mit einer Studentin des 3. Semesters. Aufgrund von Wissensl\u00fccken und Unsicherheiten hatte sie \u00fcberlegt, ihr Studium abzubrechen. Wir f\u00fchlten uns direkt in unsere ersten Semester im Studiengang Mobile Medien zur\u00fcckversetzt und \u00fcberlegten, was uns damals geholfen\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/08\/gMqnGsMdK7UF6sYIWMq7XDYYPl2O4KeKk7VGGfIir9JTXBQmmmb42idNXZAuCf71_KaTrmKinBIjQ2I0_xrUYEUeByfVcjRUg1umCfJC8Fv2ftIohCIrfkI4udGuMoBvVKMms7Oj9549xtAkH4dLQ_c.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/08\/gMqnGsMdK7UF6sYIWMq7XDYYPl2O4KeKk7VGGfIir9JTXBQmmmb42idNXZAuCf71_KaTrmKinBIjQ2I0_xrUYEUeByfVcjRUg1umCfJC8Fv2ftIohCIrfkI4udGuMoBvVKMms7Oj9549xtAkH4dLQ_c.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/08\/gMqnGsMdK7UF6sYIWMq7XDYYPl2O4KeKk7VGGfIir9JTXBQmmmb42idNXZAuCf71_KaTrmKinBIjQ2I0_xrUYEUeByfVcjRUg1umCfJC8Fv2ftIohCIrfkI4udGuMoBvVKMms7Oj9549xtAkH4dLQ_c.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]},{"id":25893,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2023\/09\/15\/entwickeln-einer-edge-anwendung-mit-cloudflare\/","url_meta":{"origin":28654,"position":4},"title":"Entwickeln einer Edge-Anwendung mit Cloudflare","author":"Jens Schlegel","date":"15. September 2023","format":false,"excerpt":"Einleitung Englisch spielt eine gro\u00dfe Rolle in meinem Beruf und Alltag, doch immer noch passieren mir Grammatikfehler. Um meine Englischkenntnisse zu verbessern, habe ich eine kleine Webseite entwickelt, auf der das Schreiben von englischen S\u00e4tzen ge\u00fcbt werden kann. Dem Nutzer wird ein Satz pr\u00e4sentiert, der dann in die festgelegte Sprache\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/09\/SCR-20230817-rdek_1694731446844_0.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/09\/SCR-20230817-rdek_1694731446844_0.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/09\/SCR-20230817-rdek_1694731446844_0.png?resize=525%2C300&ssl=1 1.5x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2023\/09\/SCR-20230817-rdek_1694731446844_0.png?resize=700%2C400&ssl=1 2x"},"classes":[]},{"id":13181,"url":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/2021\/03\/24\/verkehrserkennung-mit-neuronalen-netzen\/","url_meta":{"origin":28654,"position":5},"title":"Verkehrserkennung mit Neuronalen Netzen","author":"Lukas Joraschek","date":"24. March 2021","format":false,"excerpt":"Einleitung Hast du beim Lernen auch schon einmal gelangweilt aus dem Fenster geschaut und die vorbeifahrenden Autos gez\u00e4hlt? Auf wie viele Autos bist du dabei genau gekommen und war diese Zahl vielleicht auch vom Wochentag oder der Uhrzeit abh\u00e4ngig? In unserem Projekt haben wir versucht diese Frage zu beantworten.Daf\u00fcr haben\u2026","rel":"","context":"In &quot;Allgemein&quot;","block_context":{"text":"Allgemein","link":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/category\/allgemein\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/03\/nach_training1-4-150x150.png?resize=350%2C200&ssl=1","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/03\/nach_training1-4-150x150.png?resize=350%2C200&ssl=1 1x, https:\/\/i0.wp.com\/blog.mi.hdm-stuttgart.de\/wp-content\/uploads\/2021\/03\/nach_training1-4-150x150.png?resize=525%2C300&ssl=1 1.5x"},"classes":[]}],"jetpack_sharing_enabled":true,"authors":[{"term_id":1216,"user_id":1320,"is_guest":0,"slug":"emre_kalkan","display_name":"Emre Kalkan","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/5dbd9b12714f2d7762f34ed68b09499e0375a1a0717fc6fbb7942694e2435ad6?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/28654","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/users\/1320"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/comments?post=28654"}],"version-history":[{"count":24,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/28654\/revisions"}],"predecessor-version":[{"id":28847,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/posts\/28654\/revisions\/28847"}],"wp:attachment":[{"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/media?parent=28654"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/categories?post=28654"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/tags?post=28654"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/blog.mi.hdm-stuttgart.de\/index.php\/wp-json\/wp\/v2\/ppma_author?post=28654"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}