The idea of this project was to create a simple chat application that would grow
over time. As a result, there would be more and more clients that want to chat
with each other, what might lead to problems in the server that have to be
fixed. Which exact problems will occur, we were going to see along the project.
In the center is a simple chat server that broadcasts incoming messages to all
clients. In order to notify the clients about new messages, the connection
should be static and bidirectional. Therefore, we based the communication on the
Furthermore, wanted to see how the server behaves with the rising load.
Therefore, we had the plan of performing several load tests to display the
weak points and improvements, as the system enhances.
Audio in the form of podcasts has become increasingly popular as a digital medium in recent years. In the U.S., one in two people have already consumed podcasts and 32% are monthly listeners. In Germany, one in five listens to a podcast at least once a month. In addition to the podcast phenomenon, which has existed and established itself in the market for some time, a new medium is now blossoming – social audio. Unlike podcasts, which are a one-way broadcast medium (they are recorded to be listened to in the future), this is real-time audio. Listeners can actively participate in the dialogue.
Social audio apps like Clubhouse seem to benefit from several factors. Headphones and earbuds have become ubiquitous; Bluetooth-based offerings, like Apple’s Airpods, have built-in microphones, making communication easy and intuitive. The podcast boom also appears to be a driving factor. Audio-based content has an enormous reach, and users are now accustomed to audio formats. Last but not least, digital voice communication has become commonplace, driven by voice messaging and phone calls via messenger platforms like Whatsapp, among others.
The Corona pandemic also seems to have played its part, as people began to experience Zoom Fatigue after only a short time. On the one hand, text messages simply don’t convey the emotions and nuances that human character requires – especially during isolation. On the other hand, Zoom calls and video calls are simply too exhausting, too demanding for us humans in the long run. For example, we mainly look at our own faces to make sure that the vegetables from lunch are not still stuck between our teeth. In general, this can be summarized under the camera effect. Many people are blocked and do not dare to speak if they feel they are being watched.
Currently, there are more than 40 companies or apps that are active in the field of “social audio”. The largest include Clubhouse, Twitter Spaces, Facebook Live Audio, Spotify Greenroom and Reddit Talk. In June 2020, Discord announced a new tagline, “Your place to talk,” in an attempt to make the service seem less gamer-centric to capitalize on people’s need to connect during the pandemic. In response to the hype around Clubhouse, a new feature has now also been implemented that further supports this new direction – Discord Stages. Other notable mentions are Fireside, Cappucino and Angle Audio. Interestingly enough Angle Audio switched to a SAaaS (Social Audio as a Service) offering recently. ProductHunt reported a significant increase in new audio products for 2020, explaining that face-to-face encounters are currently becoming more difficult to maintain due to social distancing, but social connections and exchange are needed more than ever.
Since audio as a medium is still a niche market in organizations, we at InspireNow wanted to find out whether this hype, this success in terms of audio-based content, can also find resonance in the business and professional environment. We perceive great potential, especially in terms of knowledge transfer.
That is why we have decided to delve deeper into the topic by means of an innovation project. The overall goal of this innovation project was to evaluate whether Enterprise Social Audio works, i.e. whether the added value and appeal of social audio can also be used in the context of companies, institutions and organizations. This was evaluated in the context of our existing SaaS solution InspireNow.
If you're looking for a step-by-step guide on how to include and implement
social audio in your own mobile app, I'm afraid this blog post is not for you.
Rather I'm going to give a birdseye view over the topic itself and depict the
procedure of analyzing requirements for enterprise context.
I'll describe some challenges encountered during development and
provide insights into security, GDPR and compliance concerns.
In the first step, a feasibility study was carried out on the basis of a proof of concept in order to obtain initial certainty for the technical viability.
Da das Projekt keinerlei Sicherheitsaspekte abdeckt, ist es aufgrund einer sehr hohen IT-Security Gefahr mit möglicherweise schweren Folgen nicht für die Nutzung außerhalb des eigenen Heimnetzwerkes ausgelegt.
Aus aktuellem Anlass in der Entwicklung von Smart Home Technologien existiert ein stetig wachsendes Interesse, auch in der eigenen Wohnung aktuelle Bequemlichkeiten wie Staubsaugerroboter, Sprachassistenten oder Kontrollmechanismen, die einem den Alltag erleichtern zu implementieren. So wächst auch das Interesse wie man diese Funktionalitäten in anderen Lebensbereichen nutzen kann. Daraus resultierte die Frage, inwiefern man eine sehr routine-basierte und alltägliche Aufgabe wie das Bewässern des Gartens oder der eigenen Innenpflanzen automatisieren kann. Dies würde dem Nutzer nicht nur Zeit und Aufwand im Alltag sparen, sondern auch die Möglichkeit eröffnen, nicht vor Ort sein zu müssen und ganz unbesorgt auch für längere Zeit unterwegs sein zu können, ohne Nachbarn bitten zu müssen sich um seine Pflanzen zu kümmern. Obwohl es bereits SmartHome Lösungen gibt und diese sich auch stetig weiterentwickeln und verbessern, sind sie meistens sehr kostspielig und noch nicht für die breite Masse verwendbar. Deshalb wäre ein Konzept wie man sich auch schon mit geringeren Kosten eine SmartHome Bewässerungsanlage im eigenen Heim installieren könnte noch für viele Menschen interessant.
Are you searching for country-specific, up-to-date numbers and rates for the global pandemic caused by COVID-19? Well, then I got some bad news for you. You won’t find any in this blog post… not directly anyway. If you are looking for in-depth information about public APIs, location-based data visualization or cloud-based Node.js web applications on the other hand, I might just be the right guy to help you out.
After reading this post you will not only have detailed information about the previously mentioned topics, but you will also learn about the challenges and problems I had to face working on this project and what to look out for, when starting a web application from scratch all by yourself.
This project is the result of the examination that is part of the lecture “Software Development for Cloud Computing”. The focus of this lecture is to learn about modern cloud computing technologies and cloud services like AWS, Microsoft Azure and IBM Cloud, how to create and develop software using these technologies as well as creating a cloud-based project from scratch.
At first, I wasn’t quite sure what I was going to work on, but I really wanted to work on a web application that uses location-based data in the form of a map to visualize something else. This “something” was yet to be determined when I started brainstorming for my project, so I started to do some research. I then bumped into this collection of free and public APIs which was perfect for my undertaking and I was almost set on creating a weather app, when I found a free API that would provide me with global data all around the Coronavirus.
Now that I knew what I was going to visualize I came up with a personal scope for this project. I decided to create a web application that would deliver COVID-19data for a specific country by either hovering or clicking on this country, as well as a search function, so that the user could jump to a country of choice by entering the name of a city or a country. Since I had only very limited knowledge about web applications and cloud computing as such (I have worked bits and pieces with Microsoft Azure during my 6-months internship at Daimler before, but never really worked with Node.js or a map library) I did some research first, but I was very confident that I could reach this goal.
3. More Research
Now that I determined what I was planning on doing, I had to figure out which tools and cloud technologies I was going to use. Since I already had a little experience with Microsoft Azure it seemed obvious to settle with Azure and the Azure Maps Service for my project. But there were a couple problems with that:
Problem 1: In order to create a private Azure Account, even an education account, one has to provide a credit card, which I do not own.
Problem 2: There is no map material in Azure Maps for the regions China and South Korea. Now that isn’t technically a k.o. criteria, but I would prefer to use a service that supports all regions to avoid limitations.
Problem 3: Again, this isn’t a huge problem, but I would rather learn something new and not go with something I already had prior experience in.
So I decided to go with AWS, Amazon’s Cloud Service instead. Even though in retrospect the documentation for AWS is not as good as for Microsoft Azure (at least in my personal opinion), AWS offers a wide range of services and on top of that you can create a free education account with 100$ worth of credits. Unfortunately AWS does not have a location data service from what I could figure out, so I had to decide on an external service.
4. But how do I get access to data from the internet?
As I mentioned earlier, I stumbled upon a public Web API called covid19api, which offers all sorts of corona-related, up-to-date data for free. In the abstract I promised in-depth information about public APIs, so I might as well lose a couple words about the functionality of Application Programming Interfaces while we’re at it. An API is a software-to-software interface, not a user-to-software interface.
A good metaphor to understand the concept of APIs would be to think of it as a waiter in a restaurant. The waiter(API) takes an order (HTTP-Request) from a guest(user) and delivers it to the kitchen(backend-system) where the order is acknowledged and prepared. When everything is ready the waiter(API) serves the food(HTTP-Response) to the guest(user). Some companies publish their APIs to the public, so that programmers can use their service to create different products. While some companies provide their APIs for free, others do so against a subscription fee. In the case of the COVID-19-API there is a free tier as well as a 10$, 30$ and 100$ subscription option. By subscribing, the user has access to additional data routes and no request-rate limit, the latter led me to subscribing, because I require several requests per second with my application.
Let’s take a step back and focus more on which solution I came up with for my project. The architecture of my web application is pretty straight forward. Clients can access a frontend via their browser. If a client hovers over, clicks on or searches for a country, a HTTP-Request is sent to the backend server which then evaluates that request and sends another HTTP-Request to either the COVID-19-API or the Mapbox-Search-API depending on what the client requested. Upon receiving a HTTP-response from either one of the APIs backend systems, my backend server evaluates the data for the respective user request and sends it back to the frontend where it is then visualized. I will go a little more in-depth about these topics later on, but first I want to explain why having a separate frontend and backend makes sense.
Pros for having a separate front and backend:
It’s far easier to distinguish between a frontend or backend issue, in case of a bug
Possibility to upgrade either one without touching the other as they run on different instances (modularity)
Allows use of different languages for front- and backend without problem
Two developers could work on each end individually without causing deployment conflicts, etc.
Adds security, because the server is not tightly bound to the client
Adds level of abstraction to the application
Cons for having a seperate front and backend:
Have to pay two cloud instances instead of just one
Independent testing, building and deployment strategies required
Can’t use templating languages anymore, instead the backend is basically an API for the frontend
The frontend of my application consists of a static HTML-website that is hosted on an AWS EC2 Linux instance. The EC2 instance gets its data from an S3 bucket that is also hosted in AWS and contains up-to-date code for the website. The implementation of Mapbox is very straight forward. All you have to do is implement the Mapbox CDN(Content Delivery Network) into the head and include the above shown code with a valid access token into the body of your HTML. The “style” tag allows the user to select from different map styles, such as streets, satellite, etc. Users can create custom map styles, tilesets and datasets using Mapbox Studio. The big benefit of this is that the user does not have to store and load the data manually from the server. Instead a user can simply upload a style/tileset/dataset to Mapbox Studio and access it from the HTML by creating a new data source with the respective url for the style/tileset/dataset.
In my case I created a custom tileset from a GeoJSON file of every country in the world. You can find geographical GeoJSON data for free online, I personally found this handy web tool that lets the user create a fairly accurate GeoJSON from countries of choice. But I encountered a problem by doing so. Even though I had fairly accurate geographical data for each country, the COVID-19-API does not support every single country. By sending a request to the COVID-19-API I got a list of all supported countries with their respective country-slug and ISO2 country code. Since those country codes are unique I wrote a basic algorithm that would craft a custom GeoJSON from all matching country codes of both the GeoJSON and the country JSON response.
Unfortunately not everything was that easy, because for some reason not every Object in the GeoJSON had a valid ISO2 code. So I had to manually go through all countries of both files and figure out which ones are missing, which was a real pain in the backside. Eventually I had a simple GeoJSON with a FeatureCollection containing a name, a unique slug, a ISO2 code and a geometry for each country, which I then uploaded to Mapbox Studio as a custom tileset.
Now that my tileset was uploaded to Mapbox studio, I was able to create a data source and a style layer from it. This allowed me to customize the appearance of the tileset’s polygons to my liking. By using Mapbox’s map.on() function, I could add hover and click events for when the client hovers or clicks over a country and retrieve information from the tileset for this specific country(feature). In my case I get the slug for the country the user has clicked or is currently hovering on and start a HTTP-Request to the backend server with this information and the current and previous date. Hovering will return a basic COVID-19data for a country, while clicking will return premium data.
6.1 COVID-19 Data Request (Frontend)
After receiving a response from my backend in the form of a JSON object, the data is added to an empty <ul> object in the HTML where it is then visible to the client.
6.2 Search Request (Frontend)
The search function works very similar to the previous description on how the COVID-19 data is requested, but instead of sending dates and a country slug from the tileset, we send a query. This query is the text that the client enters into the search bar. Upon starting a search, a fetch POST-request is sent to the backend containing the query in its body. After receiving a response from the backend which contains information about the first point of interest the Mapbox geocoder could find, we jump to the location of the POI, as long as it was a valid query. This “jump” is handled from the Mapbox fitBounds() function which tries to fit a POIs bounding box perfectly on the user’s screen.
The backend consists of a single Node.js express server that is hosted in an Elastic Beanstalk instance on AWS. I also added a CI/CD Code Pipeline from AWS that connects the instance to a GitHub repository so I have continuous updates. Since I decided on separating my frontend and backend, the backend server behaves much more like an API system for my frontend.
7.1 COVID-19 Data Request (Backend)
Whenever a HTTP-Request for one of the corona-related server routes happens, the server passes the request body to a function and executes this function. Upon execution, the backend sends another HTTP-Request to the COVID-19-API with the country slug, the current and previous date as parameters and the API access token as header. This request is being sent using the request-promise npm dependency.
The COVID-19-API’s response contains specific, corona-related data for the requested country. This data has to be evaluated and adapted, to make sure the backend only responds with data that is needed and correctly formatted. This is necessary, because otherwise larger Integer numbers are difficult to read without a dot every 3 numbers. After evaluation the data will then be sent back to the frontend where it is then displayed.
7.2 Search Request (Backend)
If there is a HTTP-Request for a query search, the server simply starts a request to the Mapbox Geocoding API with the request body’s query and the Mapbox access token as parameter. The result will be a list of POIs that fit the query, but for the sake of simplicity the server always sends the very first result back to the frontend.
8. Other Challenges
Another challenge that occured during my work on the project was that I sometimes struggled finding a solution for a problem, because documentation for an API or a service wasn’t clear or simply not existing. Sometimes it would take multiple hours reading up on documentation and community contribution, just to figure out that a single line of code would fix the problem. The biggest issues I probably had with the AWS and COVID-19-API documentation. While I could fix the issues I had with AWS by following YouTube and StackOverflow tutorials, there wasn’t really such a thing for the COVID-19-API. I then joined the official slack server for the API and reached out to the creator and developer who was very supportive and helpful.
But now it is up to you what you do with this information. Are about to close your browser in disappointment after not learning about the latest Coronavirus numbers or are you going to work on your own cloud-based web application tomorrow? No matter how you decide, I hope you learned something from reading this blog post that will help you on your journey to become a cloud developer.
Reading multiple and detailed articles can become a little bit tiring. Listening to the same content, on the other hand, is more comfortable, can be done while driving, and is less straining for the eyes. Therefore I decided to use this lecture to create a service that converts an article to an audio file using a Text-to-Speech service.
The input for the application is quite simple. The user only needs to provide a URL to the article. Then the main application fetches the contents of that URL and cuts out the unwanted markup. Then an audio file needs to be created. I chose the Amazon Polly TTS API and S3 as a file storage solution to try out Amazon Web Services. To reduce multiple creations of the same article and load time, I intended to add a database that checks if there is already an audio file. To interact with this application, I also needed a frontend that has an input field and dynamically renders the elements once the API endpoints send a response.
Getting the content
After the extraction, the text can be sent to the Polly API. At first, I was using the synthesizeSpeech method from the SDK. Aside from the parameters, this method accepts a callback function that can handle the response audio stream. That buffer can be stored in a file on the disk. While looking for a way to upload the audio file to S3 I found that there is a much simpler solution, which also eliminates the 3000 character limit of the synthesizeSpeech method. The Polly SDK also has an option to start a task using the method startSpeechSynthesisTask. This method excepts an additional parameter called ‘OutputS3BucketName’. After the task is completed. The output file is placed into the mentioned S3 bucket. I really enjoyed seeing how this integration of different platform services simplifies the development.
In hindsight, a real consumer application might want to synthesize small snippets and stream them subsequently. That would almost eliminate the wait time, since generating an audio file and loading it can take up a lot of time for impatient users. However, I did not choose this path because I intended to create a cache with my database.
The Response object from the startSpeechSynthesisTask method contains a link to the file, but there are two issues. The first problem is that S3 files are not public by default. You need to complete three different steps to make them publicly available. At first, you need to unblock all public access in the permissions. Then you need to enable public access for ‘list objects’ for everyone. After that, a pocket policy needs to be created. The policy generator luckily makes that quite easy.
Even when public access is enabled, the asset cannot be loaded immediately because the generation takes a couple of seconds. I needed to notify and update the frontend. Eventually, I solved this by starting an interval once the audio is requested. The interval checks if the task has been completed and renders an audio element after it is completed.
The authentification for AWS had to be done using the Cognito service by creating an identity pool.
After the application was running successfully on my local machine, I had to deploy it. I chose the Platform-as-a-Service Platform on the IBM Cloud because I wanted to try out Cloud Foundry and I thought my simple express application was a good use case for this abstraction layer. I could have solved some parts of the app with a cloud function, but I do not need the control level of a virtual machine. Because Cloud Foundry requires a lot less configuration than a VM, it should be easy to deploy. That is what I though. I quickly ran into restrictions anyway. Except for the things I had to figure out due to my lack of knowledge of this platform, I had to spend a lot of time troubleshooting. The biggest issue I faced was because of Puppeteer. At install time, the puppeteer package includes three versions of Chromium for Mac OS, Linux and Windows, which are all 150-250 MB large. The size exceeds the free tier limit and I had to upgrade. After that, I could not get Puppeteer running on the server, because the Ubuntu instance does not include all the debian packages that are necessary for running Chromium. This really set me back. There is no way to install packages via sudo apt-get on PaaS and doing anything manually would eliminate the benefits of the simple deployment. I really thought I had reached the limits of Platform-as-a-Service until I discovered that you can use multiple buildpacks with cloud foundry. Even if they are not included on the IBM Cloud, by adding the Github repo.
This allows you to add an apt.yml file to specify the packages you want to install. Afterward, I was able to run my application.
For tests, I chose to use mocha and chai. Except for a few modifications for the experimental modules I am using, this integration was straightforward. It uncovered a few error cases I was not considering before.
To sum up I can say that I learned a lot during this project, especially because a lot of things were completely new to me. But now I feel more confident to work with those tools and I want to continue to work on this project. I can also recommend using cloud foundry. If you know how to deal with the restrictions and know your true environment conditions, it is pretty flexible and enjoyable to use.
Gaming is fun. Strategy games are fun. Multiplayer is fun. That’s the idea behind this project.
In the past I developed some games with the Unity engine – mainly 2D strategy games – and so I thought it is now time for an awesome 3D multiplayer game; or more like a prototype.
The focus of this blog post is not on the game development though, but rather on the multiplayer part with the help of Cloud Computing.
However where to start? There are many ways one can implement a multiplayer game. I chose a quite simple, yet most of the time very effective and cheap approach: Peer-to-Peer (P2P).
But first, let us dive in the gameplay of Admiral: WW2 (working title).
2 Game Demo
Admiral: WW2 is basically like the classic board game “Battleships”. You’ve got a fleet and the enemy player has got a fleet. Destroy the enemy’s fleet before your own fleet is sunk. The big difference is that Admiral: WW2 is a real-time strategy game. So the gameplay is more like a real-life simulation where you as the admiral can command your ships via direct orders:
Set speed of a ship (stop, slow ahead, full ahead, …)
Set course of a ship
Set the target of the ship (select a ship in the enemy fleet)
Currently there is only one ship class (the German cruiser Admiral Hipper), so the tactical options are limited. Other classes like battleships, destroyers or even aircraft carriers would greatly improve replayability; on the other hand they would need many other game mechanics to be implemented first.
Ships have multiple damage zones:
Hull (decreases the ship’s hitpoints or triggers a water ingress [water level of the ship increases and reduces the hitpoints based on the amount of water in the hull])
Turrets (disables the gun turrets)
Rudder (rudder cannot change direction anymore)
Engine/Propeller (ship cannot accelerate anymore)
If a ship loses all hitpoints the ship will sink and is not controllable.
2.2 The Lobby Menu
Before entering the gameplay action the player needs to connect to another player to play against. This is done via the lobby menu.
Here is the place where games are hosted and all available matches are listed.
On the right hand side is the host panel. To create a game the host must enter a unique name and a port. If the IP & Port combination of the host already exists, hosting is blocked.
After entering valid infos the public IP of the host is obtained via an external service (e.g. icanhazip.com). Then the match is registered on a server and the host waits for incoming connections from other players.
On the left hand side there is the join panel. The player must enter a port before viewing the match list. After clicking “Join”, a Peer-to-Peer connection to the host is established. Currently the game only supports two players, so after both peers (host and player) are connected the game will launch.
More on the connection process later.
3 Multiplayer Communication with Peer2Peer
P2P allows a direct connection between the peers with UDP packets – in this case the game host and player.
So in between no dedicated server handling all the game traffic data is needed, thus reducing hosting costs immensely.
Because most peers are behind a NAT and therefore connection requests between peers are blocked, one can make use of the NAT-Traversal method Hole-Punching.
3.1.1 P2P Connection with Hole-Punching
Given peer A and peer B. A direct connection between A and B is possible if:
A knows the public IP of B
A knows the UDP port B will open
B knows the public IP of A
B knows the UDP port A will open
A and B initiate the connection simultaneously
This works without port-forwarding, because each peer keeps the port open as if they would contact a simple web server and wait for the response.
To exchange the public IPs and ports of each peer a Rendezvous-Server behind no NAT is required.
The Rendezvous-Server needs to be hosted in the public web, so behind no NAT. Both peers now can send simple web requests as if the users would browse the internet.
If peer A tells the server he wants to host a game, the server saves the public IP and port of A.
If B now decides to join A’s game the server informs B of the IP and port of A.
A is informed of B’s public IP and port as well.
After this process A and B can now hole-punch through their NATs and establish a P2P connection to each other.
A Rendezvous-Server can be very cheap, because the workload is quite small.
But there are some cases where Hole-Punching does not succeed (“…we find that about 82% of the NATs tested support hole punching for UDP…”, https://bford.info/pub/net/p2pnat/).
In those cases a Relay-Server is needed.
The Relay-Server is only used as a backup in case P2P fails. It has to be hosted in the public internet, so behind no NAT.
Its only task is the transfer of all game data from one origin peer to all other peers. So the game data just takes a little detour to the Relay-Server before continuing it’s usual way to the peers.
This comes at a price though. Since all of the game traffic is now travelling through this server the workload can be quite tough depending on the amount of information the game needs to exchange. Naturally the ping or RTT (Round Trip Time: the time it takes to send a packet from peer to peer) of a packet is increased resulting in lags. And finally multiple Relay-Servers would be required in each region (Europe, America, Asia, …). Otherwise players far away from the Relay-Server suffer heavy lags. All of these lead to high demands on the server hardware. To be clear: a proper Relay-Server architecture can be expensive in time and money.
Because of that in this project I ignored the worst-case and focused on the default Peer-to-Peer mechanism.
3.1.4 Peer2Peer Conclusion
The big advantage of this method: it’s mainly serverless, so the operation costs of the multiplayer is very low. Because of that, P2P is a very viable multiplayer solution for small projects and indie games. The only thing that is needed is a cheap Rendezvous-Server (of course only if no Relay-Server is used). P2P also does not require to port-forward, which can be a difficult and/or time consuming task depending on the player’s knowledge.
But there are disadvantages:
A home network bandwidth may not be enough to host larger games with much traffic; a server hosted at a server farm has much more bandwidth
The game stops if a P2P host leaves the game
No server authority
every player has a slightly different game state that needs to be synchronized often; a dedicated server has only one state and distributes it to the players; players only send inputs to the server
anti-cheat has to be performed by every peer and not just the server
random is handled better if only the server generates random values, otherwise seeds have to be used
game states may need to be interpolated between peers, which is not the case if only the server owns the game state
A dedicated server would solve these disadvantages but in return the hardware requirements are much higher making this approach more expensive. Also multiple servers would be needed in all regions of the world to reduce ping/RTT.
3.2 Game Connection Process
After starting the game the player sees the multiplayer games lobby. As described previously the player can host or join a game from the list.
3.2.1 Hosting a game
The host needs to input a unique game name and the port he will open for the connection. When the host button is clicked the following procedure is triggered:
Obtain public IP address
Originally this should be handled by the Rendezvous-Server, because it is hosted behind no NAT and can see the public IP of requests, but limitations of the chosen hosting service prevented this approach (more on that later)
Instead I used a web request to free services like icanhazip.com or bot.whatismyipaddress.com as a second backup in case the first service is down; these websites respond with a plain text containing the ipv6 or ipv4 of client/request
The Rendezvous-Server is notified of the new multiplayer game entry and saves the game with public IP and port, both sent to the server by the host
Host sends GET-Request to the server (web server) containing all the information needed /registermpgame?name=GameOne&hostIP=18.104.22.168&hostPort=4141
On success the game is registered and a token is returned to the host; the token is needed for further actions affecting the created multiplayer game
The host now waits for incoming connections from other players/peers
The host sends another GET-Request to the Rendezvous-Server /listenforjoin?token=XYZ123
This is a long-polling request (websocket alternative): the connection is held open by the server until a player joined the multiplayer game
If that is the case the GET-Request is resolved with the public IP and port of the joined player, so that hole-punching is possible
If no player joins until the timeout is reached (I’ve set the timeout to 15 seconds), the request is resolved with http status code 204 No content and no body
In that case the GET-Request has to be sent again and again until a player joins
On player join both peers init a connection and punch through NAT
If successful the game starts
(Otherwise a Relay-Server is needed; explained previously)
The host closes the game with another GET /startorremovempgame?token=XYZ123
3.2.2 Joining a game
The player first needs to input a valid port. After that he is presented with a list of multiplayer games by retrieving the information from the Rendezvous-Server with a GET-Request to the endpoint /mpgameslist. This returns a JSON list with game data objects containing the following infos:
name: multiplayer game name
hostIP: public IP of the host
hostPort: port the host will open for the connection
If the player clicks “Join” on a specific game list item the following process handles the connection with the host:
Obtain public IP address
Originally this should be handled by the Rendezvous-Server, because it is hosted behind no NAT and can see the public IP of requests, but limitations of the chosen hosting service prevented this approach (more on that later)
Instead I used a web request to free services like icanhazip.com or bot.whatismyipaddress.com as a second backup in case the first service is down; these websites respond with a plain text containing the ipv6 or ipv4 of the client/request
Inform the Rendezvous-Server of the join
Send a GET-Request with all the information needed /joinmpgame?name=GameOne&ownIP=22.214.171.124&hostPort=2222
Now the host is informed by the server if the host was listening
The server resolves the request with the public IP and port of the host
Now the player and the host try to establish a P2P connection with hole-punching
If successful the game starts
(Otherwise a Relay-Server is needed; explained previously)
3.3 Game Synchronization
Real-time synchronization of game states is a big challenge. Unlike turn-based games the game does not wait until all infos are received from the other players. The game always goes on with a desirably minimal amount of lag.
Of course the whole game state could be serialized and sent to all players, but this would have to happen very frequently and the package size would be very large. Thus resulting in very high bandwidth demand.
Another approach is to only send user inputs/orders, which yields far less network traffic. I used this lightweight idea, so when the player issues an order the order is immediately transmitted to the other player. There the order is executed as well.
The following game events are synchronized:
GameStart: After the game scene is loaded the game is paused and the peer sends this message to the other player periodically until he receives the same message from the other peer; then the game is started
RandomSeed: Per game a “random seed master” (the host) periodically generates a random seed and distributes that seed to the other player; this seed is then used for all random calculations
All 3 ship orders:
GameSync: All of the previous messages still led to diverging game states, so a complete game serialization and synchronization is scheduled to happen every 30 seconds
Projectile positions, rotations, velocities are synched
The whole ship state is synched
Both game states (the received one and the own one) are interpolated, because I don’t use an authoritative server model and so both game states are “valid”
The following game events should have a positive impact on game sync, but are not implemented yet:
ProjectileFire: Syncs projectiles being fired
Waves: Because the waves have a small impact on the position where projectiles are fired and hit the ship the waves should be in-sync as well
In game development you mostly work with references. So for example a ship has a reference to another ship as the firing target. In code this has the benefit of easy access to the target ship’s properties, fields and methods.
The problem is with networking these references do not work. Every machine has different references although it may represent the same ship. So if we want to transfer the order “Ship1 course 180” we cannot use the local reference value to Ship1.
Ship1 needs an unique ID that is exactly the same on all machines. Now we can send “ShipWithID1234 course 180” and every machine knows which ship to address.
In code this is a bit more tedious, because the received ID has to be resolved to the appropriate ship reference.
The most difficult part is finding unique IDs for all gameobjects.
Ships can obtain an ID easily on game start by the host. Projectiles are a bit more tricky, because they are spawned later on. I solved this by counting the shots fired by a gun turret and combining the gun turret’s ID with the shot number to generate a guaranteed unique ID, provided the gun turret ID is unique. Gun turret IDs are combined as well: Ship ID + gun turret location (sternA, sternB, bowA, bowB, …).
Of course with an authoritative server this gets easier as only the server generates IDs and distributes them to all clients.
Additionally there is an interesting and promising approach to discretize the continuous game time called Lockstep. It is used in prominent real-time strategy games like Age of Empires (https://www.gamasutra.com/view/feature/131503/1500_archers_on_a_288_network_.php). The basic idea is to split up the time in small time chunks, for example 200ms intervals. In this time frame every player can do exactly one action that gets transferred to all the other players. Of course this action can also be “no action”. The action is then executed in the next interval almost simultaneously for all players. This way the real-time game is transformed into a turn-based game. It is important to adjust the interval based on the connection speeds between the players, so that no player lags behind. For the players the small order input delay is usually unnoticed, if the interval is small enough.
An important requirement is that the game is deterministic and orders issued by players have the same outcome on all machines. Sure there are ways to handle random game actions, but because AdmiralWW:2 uses random for many important calculations and my development time frame was limited I unfortunately did not implement this technique.
4 Rendezvous-Server Hosting
Of course there is the option of renting an own dedicated server: very expensive for a simple Rendezvous-Server and maintenance heavy, but powerful and flexible (.NET ok).
There’s the option of a managed server: little maintenance but very, very expensive.
We have VPS (Virtual Private Servers): dedicated servers that are used by many customers and the hardware is distributed among them, cheaper.
Then there are the big players like AWS, Google Cloud Platform, IBM Cloud and Microsoft Azure: they can get very expensive, but in return they offer vast opportunities and flexibility; it is easy to scale and monitor your whole infrastructure and a load-balancer can increase availability and efficiency of your server(s); on the other hand the learning-curve is steeper and setting up a project needs more time.
Also it does have a completely free plan, which grants over 500 hours uptime per month. This is not enough to run the whole month with 30 * 24 = 720 hours, but the application sleeps after 1 hour with no actions and automatically wakes up again if needed. This is perfectly fine for a Rendezvous-Server, because it is not used all the time. The wake up time is not that bad as well (around 4-8 seconds).
Of course Heroku offers scaling so that the performance is massively increased and the app will never sleep, but this comes with a price tag.
In a paid plan Heroku also has a solid monitoring page with events, up- and downtimes, traffic and so on.
Server logs are easily accessible as well.
For setup you just need to create a “Procfile” in your project folder that defines what to execute after the build is completed: web: npm run start will run the npm script called start as a web service. The application is then publicly reachable on your-app-name.herokuapp.com. The NodeJS web server can then listen on the port that is provided by Heroku in the environment variable process.env.PORT.
Deployment is automated: just push to your github master branch (or the branch you specified in Heroku); after that a github webhook triggers the build of your app in Heroku.
But during development I discovered a big disadvantage: Heroku does not support ipv6.
This is a problem, because I wanted to use the Rendezvous-Server as a STUN-Server as well, which can determine and save the public IPs of client requests. But if a client like me only has Dual-Stack lite (unique ipv6 but the ipv4 address is shared among multiple customers) Peer2Peer is not possible with the shared ipv4.
As a workaround the clients obtain their public ipv4 or ipv6 via GET-Request from icanhazip.com or as a backup from bot.whatismyipaddress.com. These websites return a plain text body containing the public IP. After that the peers send their public IP to the Rendezvous-Server as explained previously.
5 Architecture Overview
To realize the web server I used the very popular ExpressJS, which does not need any introduction and should be well-known by this time.
Real-time multiplayer games are tricky. The game states quickly diverge and much effort has to be done to counteract this. Game time differences and lag drastically compound this. But methods such as Lockstep can help to synchronize the time across multiple players.
While developing, try to keep the game as deterministic as possible, so that player actions yield the same result on every machine. Random is usually problematic, but can be handled via a dedicated game server or seeds.
Peer-to-Peer is a simple and great solution for smaller indie multiplayer games, but comes with some disadvantages. For larger projects dedicated/authoritative servers are favourable.
Heroku offers a fast and simple setup for hosting cloud applications and the free plan is great for smaller projects. If demand increases scaling is no problem and the deployment is automated. But be aware of the missing ipv6 support of Heroku.
All in all: Gaming is fun. Strategy games are fun. Multiplayer is fun – for the player and an exciting challenge for developers.
This is part two of our series on how we designed and implemented a scalable, highly-available and fault-tolerant microservice-based Image Editor. This part depicts how we went from a basic Docker Compose setup to running our application on our own »bare-metal« Kubernetes cluster.
This is part one of our series on how we designed and implemented a scalable, highly-available and fault-tolerant microservice-based Image Editor. The series covers the various design choices we made and the difficulties we faced during design and development of our web application. It shows how we set up the scaling infrastructure with Kubernetes and what we learned about designing a distributed system and developing a production-grade Kubernetes cluster running on multiple nodes.
When I was invited to a design thinking workshop of the summer school of Lucerne – University of Applied Sciences and Arts, I made my first experience with the end user interaction part of Industry 4.0. It was an awesome week with a lot of great people and made me interested in the whole Industry 4.0 theme. So when we did projects in the lecture of cloud development I was sure to do a production monitoring project. Continue reading →
The idea for this project occurred to me while I was listening to my sister share her vision for her recently started blog: To create a platform where writers of different ethnicity can publish texts in their native languages and exchange their stories with people from all over the world. Conquering the language barrier and making the texts available at least in the three most prominent languages – German, English and Arabic – requires the involvement of translators who are fluent in at least two of the demanded languages. Anyone who has ever attempted to make a translated text sound natural knows that this is no easy feat and can take up to many hours of finding the perfect balance between literal translation and understandable text.
This is where I saw room for improvement. Nowadays, machine translation tools have reached a decent level of fluency, despite not being able to capture the intricacies of different linguistic styles. Combining them with people who have a basic understanding of the source language can help speed up the process and reduce the effort considerably. Continue reading →