Robo Cat, Multiplayer Game

       
Company
   Academic Project
    Engine
   SDL from Multiplayer Game Programming
Platform
   Windows & Linux
    Skills
   Amazon EC2, C++, Node.js
Role
   Server Engineer
    Responsibilities
   HTTP user service, latency handling, network security

Features

  • HTTP user service (data tier)
  • Latency handling (presentation and logic tiers)
  • Data and game connection security

HTTP User Service

  • Amazon EC2 is used to consistently host the user service.
  • MobaXterm is used to easily access EC2 from my Windows PC.
  • PM2 is used for a resilient behavior of the user service.
  • Postman is used to automatically test the user service API.
  • Redis is used to manage session data which is a temporary type.
  • MongoDB is used to preserve user data.
1) Ecosystem file of PM2.      2) Unit tests through Postman.

Latency Handling

  • Amazon EC2 is used to consistently host the game sever.
  • C++ REST SDK is used to use HTTP protocol in the C++ client.

If latency problem is not hendled on the client side, users can experence lagging response. One way to hide this problem is temporarily creating a dumy data in the client. The dumy data will be immediately deleted as soon as the real data is arrived from the game server. See the below example where a cat is deploying a bomb by the user’s input.

See the response time when the cat deploys a bomb after a input number is appeared in "Pressed Key".    1) No latency hendling.    2) Responsive reaction.

The above simple trick is what I added to the game engine, and other techniques such as dead reckoning and data compression were already implemented by the engine author.

Data and Game Connection Security

  • Nginx is used to secure SSL keys and certificates and to upstream the user service.
  • OpenSSL is used to decode encrypted data in the C++ server.

Authentication is kept checking from the user service, and SHA-256 encryption is used between the three layers. I will briefly show how it is progressing to secure the user service and game connection.

  1. After a client requests login to the user service. The user service creates a unique session and a unique token, then notify it to the client. The two unique data will be used to verify on every http request. 10s of lifespan is assigned to the session as soon as it created.

     // Encode the given message with SHA-256 hash algorithm.
     BuildToken:function(message) {
       let hash = crypto.createHash('sha256');
       hash.update(message);
       let token = hash.digest('base64');
       return token;
     },
    
     LoginUser:function(req, res) {
       ...
       // Creates authentication data.
       var session = base64url(username);
       var token = this.BuildToken(
        (Math.random() * date.getTime());
          
       // Store the session to Redis with 10s of expiration.
       // Store the token to MongoDB.
       ...
    
       // Respone to the client.
       res.json({
       "session": session,
       "token": token
       });
     }
    
  2. If the user is verify by successful login, the client requests the address of the game server to the user service.

     void NetworkManagerClient::UpdateConnect() {
       ...
       // This given data from the user service will be used
       // for verification.
       postData[L"session"] = mUserData.session;
       postData[L"token"] = mUserData.token;
    
       // Send the connection request to the user service.
       client.request(
         web::http::methods::POST,
         U("/api/v1/connect"), postData)
       ...
     }
    
  3. The user service gives the server address and a unique connect token to the client. This token is encrypted with SHA-256 and a secret key which is only shared with the game server.
     async ConnectToGameServer(req, res) {
       // Check authentication by the given session and token.
       ...
    
       // The game server address that client will connect.
       var server = process.env.GAME_SERVER;
       // This token will be used when the client tries to
       // connect to game server. The formula is to make sure
       // that the connect token is unique and disposable.
       var tokenConnect = this.BuildToken(
         model.username + model.avatar + SHARED_SECRET);
    
       // Respone to the client.
       res.json({
       ...
       "server": server,
       "token": tokenConnect
       });
     }
    
  4. After the client sends the given user data and the given connect token to the game server. The game server decrypts the connect token using the shared secret key. If the decryped token is the same data which is a combination of name, avatar, and the secret key, the game server regards the user as a secured user.

     void NetworkManagerServer::HandlePacketFromNewClient(...) {
       ...
       // Concatenate the given values to build the message.
       string message = name + avatar + mSharedSecret;
       string digest = digestBuilder.BuildDigest(message);
    
       // Verify that this login request passes through
       // the authentic user service.
       if (token.compare(digest) != 0) {
        LOG("Token verification has been failed: %s", name);
        return; // Deny the connection.
       }
    
       // The connection token is verified!
       // Connect the client to this server.
       ...
     }
    

Sorce Code

References

  • Multiplayer Game Programming: Architecting Networked Games