Recommendations For a Server Game

Started by
11 comments, last by Josheir 8 months ago

Hello, everyone, I have noticed a similar post, but I am going to go with it. lately, I have been using JavaScript, CSS, and React. I have decided to dedicate a significant amount of my time writing games with C++ again. What I want to do is write a war game like Hasbro's board game Risk, that creates a server for multiple players. I am thinking that the server should use a library. I want to keep it as simple as possible, and I want to use C++. The game would consist of a flat map with markers on it, however using 3D later for another project might be possible. Any advice, or tips for this opportunity?

Excitedly,
Josheir

Advertisement

Requirements for what you describe seem pretty minimal.

With simple turn-based games like Risk you need very little.

Assuming a turn based game, players are logged in and have a session ID, and there is nothing else going on, each turn is something that could be done with a simple web request. One possible way to build it is a simple web server using your choice of systems, a simple database of the current board states, and validation rules to ensure each move is legal. Much like commonly-available servers for Chess or Go, basically any minimal implementation is adequate for playing.

That's quite different from something like a fast-paced shooter with 100 simultaneous active connections, continuously moving players, vehicles, validating weapon cooldowns and ammo/inventory counts and calculating damages for fifty different types of weapons, and such.

Given your background in React and web development, my suggestion would be simple http responses and maybe http long polling if you're looking to get updates live. Or if you want to implement it in C++, use one of the many web server libraries to handle that work for you.

Some additional color on this:

  • Write your C++ web server to handle HTTP. Dealing with all the library versioning nightmares for HTTPS isn't worth it. Stick a nginx or something in front of it, to terminate the HTTPS connection and forward to the server. (If you actually release this as a public service, you'll want that anyway, for operability and scalability.)
  • There are TONS of C++ libraries for HTTP, none of them “standard.” libhttpserver is probably one of the more battle tested, though.
enum Bool { True, False, FileNotFound };

hplus0603, what is the purpose of nginx, is it a server I use? Why would I want to use libhttpserver and nginx?

nginx has several uses, but in this case, it will be used for three things:

  1. HTTPS termination, so you don't have to worry about that in the C++ code, and can route based on payload data (like URL path)
  2. A hardened external interface, so security problems you'll accidentally introduce in your C++ code, become harder to exploit.
  3. A load balancing reverse proxy and router.

Number 3 is the most interesting – if you serve the “sign up” web page on the same domain as the “game API,” then you perhaps want URLs that start with /api/… to go to your C++ back-end, but other URLs to go to some node-based React server, and maybe a URL that starts with /static/… will just serve files straight from disk.

For each back-end, you can configure one or more servers. This lets you run multiple copies of the same server, and load balance between them. Also, if one crashes, the next one can still take the requests. This avoids being woken up in the middle of the night because of rare bugs. (You will need to build your game to store all important state in some network-accessible location, like a database or memcache, rather than in-process, though.)

Finally, when you deploy new versions of software, the load balancer part of it will let you spin up new copies of the C++ code before you turn off the old ones, send new requests to the new copy, and then shut off the old copies once they're done serving whatever they're serving.

It's very typical to also have some kind of hardware reverse proxy in front of this, like an AWS NLB or ALB. (NLB is cheaper and faster and scales better; ALB can do some of the same things that Nginx can do.)

So, your typical architecture looks like:

client <- internet -> NLB -> Nginx* -> Backend*

Where * means you have more than one, for reduncancy.

If you use Kubernetes as a host, the kubernetes “ingress controller” is actually nginx under the covers, so in that case, you get it “for free” or at least in a way that you manage it the same as all your other kubernetes services.

Meanwhile, if it's just you and your friends, a single container that runs both the nginx process and the C++ process hosted in some cheap container host (digital ocean or lightsail or whatnot) is probably good enough – but keeping the nginx there still delivers all the other benefits above. And it doesn't HAVE to be nginx; people have used Apache or HAproxy or Varnish or Envoy or a bunch of other tools. Or, as suggested above, the Amazon ALB service.

enum Bool { True, False, FileNotFound };

While true, I doubt Josheir is looking to build anything at that scale.

I get the feeling he's looking for a small, single server for a few games, not a scalable system more typical of a commercial environment.

In a commercial environment you'll absolutely want all those elements so you can scale, deploy servers intelligently, and otherwise manage all the server instances and message routing that is needed for hosting many thousand games at once.

For a hobby environment where you're only going to handle one or two games at once, or maybe even twenty games at once, just running a simple standalone server on a single machine somewhere is adequate.

just running a simple standalone server on a single machine somewhere is adequate

I'm going to disagree here. I have some services that I run on my home file server, just for me, exposed through a port forward. Nobody knows about them. I still use nginx as a proxy. I do this for three reasons:

  1. Security updates and hardening. This code has been around for a very long time, and is unlikely to have buffer overflows and the like. And if there's a problem, security updates are available right away.
  2. TLS termination. There are plugins that make letsencrypt.org an automatic affair – it refreshes the certificate every three months, and I don't need to worry about it. And my main code can just talk regular HTTP, no need to worry about the dumpster fire that's the openSSL API, which always bleeds through to any library that's using it.
  3. Serving static assets (my web page assets, etc) is built-in and efficient.

So, even if I don't need blue/green deployments, or redundancy, or load scaling, or fancy routing, nginx (or another reverse proxy) is totally worth it, even for small hobby projects. (Nginx even has plugins to add “google auth” and other oauth2 sign-in, so you can easily get protected sign-in without having to code it yourself. But, again – the above three are the base to build on.)

enum Bool { True, False, FileNotFound };

@hplus0603 Okay, what would you say is the easiest way to learn this, and if at work would others set this up?

@josheir That depends on what “this” means :-)

nginx

There are some pretty easy quick starts for nginx. On the latest service I spun up, I made a myservice.conf file and dropped it into /etc/nginx/sites-enabled:

server {                                                                                                                                                              
        listen 3001 default_server;                                                                                                                                   
        listen [::]:3001 default_server;                                                                                                                              
                                                                                                                                                                      
        # make these really drastic, because the Python process is serial.                                                                                            
        client_body_timeout 10;                                                                                                                                       
        client_header_timeout 10;                                                                                                                                     
        keepalive_timeout 10;                                                                                                                                         
        lingering_timeout 10;                                                                                                                                         
        send_timeout 10;                                                                                                                                              
        proxy_connect_timeout 10;                                                                                                                                     
        proxy_read_timeout 10;                                                                                                                                        
        proxy_send_timeout 10;                                                                                                                                        
                                                                                                                                                                      
        root /var/www/html;                                                                                                                                           
                                                                                                                                                                      
        index index.html;                                                                                                                                             
                                                                                                                                                                      
        server_name _;                                                                                                                                                
                                                                                                                                                                      
        location / {                                                                                                                                                  
                proxy_pass http://127.0.0.1:3000;                                                                                                                     
        }                                                                                                                                                             
}                                                                                                                                                                     

This says “nginx listens on port 3001 and forwards incoming connections to a local process listening on port 3000.” (This hosts a serial Python process that talks to a GPU to do some machine learning snake oil – hence the complaint about Python in the comment above.)

Docker

There are some somewhat deeper tutorials for docker, if you haven't gotten into that. On the one hand, it's how essentially all services are deployed these days, but on the other hand, it's a bit different from how “ship binaries on the hardware/vm” used to work ten years ago.

You need to install two pieces: The docker build-time on your development and CI machines, and the docker runtime on your actual servers. Docker uses a Dockerfile (similar to a makefile) to control how to package up the software in question. It can look something like:

# Simple Dockerfile to copy bin/myservice into a self-contained container
FROM ubuntu:latest
RUN apt-get update
RUN apt install -y -V ca-certificates lsb-release wget # add other dependencies maybe
COPY bin/myservice myservice
CMD ["myservice", "--port=8000", "--log-level=3", "--database=my.database.com:5432", "--datadir=/var/data"]
EXPOSE 8000

You build this with something like docker build -t myservice .

If you don't use static linking, you need to also install the dependencies in the Dockerfile. Each invocation is as if you came to a brand new, uninstalled system. Caching makes the docker build process acceptable over time. (But all the cool kids are using static linking to not have to worry about shared libraries anymore.)

You then run it locally with something like:

docker run -it --rm -n myservice -p 8000:4321 -v /home/me/mydata:/var/data myservice

Or, if you want to start it in the background:

docker run -d -n myservice -p 8000:4321 -v /home/me/mydata:/var/data myservice

This says “what the inside of the container thinks is port 8000 should be port 4321 on the host, and what the inside of the container thinks is path /var/data, should be path /home/me/mydata on the host.”

A lot to wrap your head around! The CPU runs microcode to implement an instruction set, the instruction set runs a hypervisor, the hypervisor runs a VM, the VM runs a kernel, the kernel runs a container host, the container runs a service. (If you're lucky, the service is written in .NET or Java and has its own second VM, that in turn runs the service …) As a developer, 90% of the time, you can ignore the layers you're not concerned about, but in live use cases, it helps a lot to have an idea of what's going on all through the stack. Both for security, performance, and lack of surprises :-)

http library

Each http library comes with its own “quick start” instructions, of varying quality depending on the library. I've personally used https://github.com/yhirose/cpp-httplib​ and it's alright. It has reasonable getting-started documentation; nothing much to say.

running at work

“At work” depends on where you work and how it's set up! Modern software deployment organizations have an “infrastructure” team, which provides some kind of host and service routing fabric, and you then build your own containers and provide manifests to the infrastructure team. Their job is to keep the fabric/hardware up; your role is to build good containers that do what they're supposed to. If you're alone, you'll probably use a simple container service like AWS Elastic Container Service, or Amazon Lightsail, or Google Container Engine. Once you want “more than one” you might go to a hosted Kubernetes service like AWS EKS or Gcloud GKE. Those take the job of the “infrastructure team” and you deploy to them typically by describing all the bits you need (database containers, cache containers, service containers, …) in yaml files, and have the yaml files point at some docker repository where it gets the container images.

Even if your “at work” is just providing bare hosts or VMs, they will likely have some reverse proxy that needs to be configured for your service to be discovered, but how that works, depends entirely on who or what the implementation is. It's useful to understand the bits and pieces that all have to come together to make it work, though; it gives you some concepts and words to look for to keep it all together. No matter whether you use F5 bigiron or envoy or nginx or perlbal, they all end up having to do the same thing, because that's how the internet works :-)

basic server skills

The modern internet is built on a strata of tons of underlying technologies. It helps to know how each of them work, at least a little bit. Anything from “the HTTP protocol" to “kernel resource virtualization” will absolutely affect how your service runs. Unfortunately, I don't know of great materials for “the whole stack” – it's either newbie stuff that glosses over too many details, or it's for-experts deep dives that are aimed at people who are going to do a lot of work at each layer. But a few days of reading web pages, watching a few youtube videos on 1.75x speed, and, most importantly, playing around by yourself, usually helps!

enum Bool { True, False, FileNotFound };

Just very basically, how does the C++ program the server. Is the server provided with the software I write?

This topic is closed to new replies.

Advertisement