Creating Our WebSocket Server | Creating a Multiplayer Game Server - Part 4

Bradston Henry - Nov 19 '21 - - Dev Community

In this entry of "Creating a Multiplayer Game Server" we will finally be diving into the code and creating the base functionality of our WebSocket server.

In the previous blog in this series, we covered what WebSocket was and the core functionalities that we will be using on our game server. In this blog, we will begin writing our code and seeing how these different WebSocket capabilities actually work.

But Before we start, let's ensure that we have NPM(Node Package Manager) installed on our local machine, otherwise you will not be able to follow along with the steps I will be covering here. NPM installs Node tools along with the package manager so once it's on your machine, you will be good to go. visit this link to download the latest version of NPM.

If you want to check if you have NPM installed on your local machine, open a terminal window and type this command and then press Enter/Return:



npm -l


Enter fullscreen mode Exit fullscreen mode

If NPM is installed on our machine, our terminal should print out a list available NPM commands.

Now that we have the prerequisites out of the way, let's dive into making this happen.

Initializing our Node Project

Create a new folder on your machine called "multiplayer-game-server". This folder will hold all the files we will need to run our server.

Once the folder is created, we will open a terminal window and navigate to that folder via the terminal using the "cd" command:



cd <you-directory-path>/multiplayer-game-server


Enter fullscreen mode Exit fullscreen mode

Once in the folder we will be initializing our Node.js application using the "npm init" command in our terminal:



npm init


Enter fullscreen mode Exit fullscreen mode

This will take you through a series of steps to set up our Node.js application. Press Enter/Return to accept any default values until the process is complete. Note: Ensure that "entry point" option is set to "server.js":

Once complete, there will be a few new files in our "multiplayer-game-server" folder.

Now, we must create the file that will actually house the code for our multiplayer game server. Create a new file called "server.js" in your folder. You can create this file however you would like but ensure that the extension is ".js". To create the file from command line, use this command:



touch server.js


Enter fullscreen mode Exit fullscreen mode

Importing Core Frameworks

Now that our "server.js" file is created, open the file in your favorite text/code editor. Once open, we will add these two lines of code to the top of our file:



var uuid = require('uuid-random');
const WebSocket = require('ws')


Enter fullscreen mode Exit fullscreen mode

These two lines import into our Node.js application two code packages/frameworks we will need; WebSocket (ws) for managing our persistent connections with clients and a Random User ID generator (uuid-random) which we will use for assigning connected clients unique IDs so we can easily track them on our server.

Though we have imported the packages into our code, we first need to actually install them into our application. Navigate back to our terminal and insert this command:



npm install ws uuid-random


Enter fullscreen mode Exit fullscreen mode

As you may have guessed, this command installs the WebSocket and Random User ID Generator packages into our application folder so we can now us them in code.

Adding Server Setup/Initialization Variables

Once that has completed, let's navigate back to our code editor and add these additional lines of code below our package imports:



const wss = new WebSocket.WebSocketServer({port:8080}, ()=> {
    console.log('server started')
})

//Object that stores player data 
var playersData = {
    "type" : "playerData"
}


Enter fullscreen mode Exit fullscreen mode

Our first line of code that begins with "const wss=..." actually creates our WebSocket server that our clients will connect to at our environment port 8080. It is important that we use port 8080 because when we push our server application to Red Hat OpenShift, applications are exposed to port 8080 by default. In order for our application to work on RHOS, our application needs to be started on that port to be accessible.

Our second line "var playersData =..." will be a JSON object we will use to track players/clients that have connected to our server. Though, by default, WebSocket will do this, it is important for us to have our own mechanism to track these users as we will at times need that information to perform custom actions.

Adding WebSocket Methods

Now that we have inserted code to start our WebSocket server and track connected players, let's add WebSocket functions we will need for communicating effectively with connected players/clients. Below our previous code, add these lines of code:



//=====WEBSOCKET FUNCTIONS======

//Websocket function that managages connection with clients
wss.on('connection', function connection(client){

    //Create Unique User ID for player
    client.id = uuid();

    console.log(`Client ${client.id} Connected!`)

    var currentClient = playersData[""+client.id]

    //Send default client data back to client for reference
    client.send(`{"id": "${client.id}"}`)

    //Method retrieves message from client
    client.on('message', (data) => {        

    })

    //Method notifies when client disconnects
    client.on('close', () => {

    })

})

wss.on('listening', () => {
    console.log('listening on 8080')
})


Enter fullscreen mode Exit fullscreen mode

So let's breakdown what all this code does...

Let's first start here:



wss.on('connection', function connection(client){...


Enter fullscreen mode Exit fullscreen mode

This is called our "OnConnection" WebSocket listener method. Essentially what it does is "listens" for a client to connect and then it manages the persistent connection with that client from then on. A thing to note, is that most all other client-server connection methods/functions will be nested inside this OnConnection method. Since this function manages the connection between the server and client at all times, all other functions will leverage the persistent connection this method manages.

Within the "OnConnection" method we have these lines of code:



//Create Unique User ID for player
client.id = uuid();

console.log(`Client ${client.id} Connected!`)

var currentClient = playersData[""+client.id]

//Send default client data back to client for reference
client.send(`{"id": "${client.id}"`)


Enter fullscreen mode Exit fullscreen mode

Essentially these lines of code set up the initial connection with our player/client and give the player a unique identity. First we create and the assign a unique ID to our our player (which we set in our playerData JSON). Once the ID is set, we send back to the player the ID that the server has assigned to them for future reference. This chunk of code is useful because it allows us the opportunity to set up unique IDs for our players so that in the future we can have custom control over how we manage and track individual players on the server.

Once we create a unique ID for our client/player, we must now set the ability to receive data/information from the player:



client.on('message', (data) => {

})


Enter fullscreen mode Exit fullscreen mode

The "OnMessage" listener method allows the server to listen for messages received from any connected client/player. It literally "listens" for messages and once it "hears" a message, it retrieves it and allows us to parse that message and do what we would like with it. For now we will keep this method somewhat empty, but we will be returning to it later to add some additional functionality.

the next piece of code is used for dealing with client/player disconnections:



client.on('close', () => {

})


Enter fullscreen mode Exit fullscreen mode

The "OnClose" method shown above manages what happens when a client "closes" it's connection with the server. This method is triggered from the client sending a "close" message. It allows for us to do any cleanup needed when a client disconnects. In our case, we use this functionality to remove a client/player from our playerData JSON object which we will cover a little bit later.

Finally, outside of our OnConnection Method we have a pretty straight-forward function.



wss.on('listening', () => {...


Enter fullscreen mode Exit fullscreen mode

This above function sets up the ability for our server to listen on our specified port. Essentially it allows our server to work as needed and it also has a simple Debug statement that we can use to ensure our server is running/deployed correctly.

With the code we have just added, we have the basic framework for our multiplayer game server. Our next steps will be to add the specific functionality needed to send and receive messages to and from the player.

Receiving Client Messages/Data

Now that we have our basic framework for our game server, lets add a few lines of code to allow our client/player to communicate properly with our server. In our "OnMessage" method, let's add this code:



client.on('message', (data) => {
    var dataJSON = JSON.parse(data)

    console.log("Player Message")
    console.log(dataJSON)
})


Enter fullscreen mode Exit fullscreen mode

So what does this line of code do?



var dataJSON = JSON.parse(data)


Enter fullscreen mode Exit fullscreen mode

So what we are doing here is parsing the data received from our client/player from a JSON string into a JSON object as it will be easier to access as an object. We then just print out the message received to be viewed in our terminal window/log. This data received contains any information we would like to convey to the server (and other players) from the player. This could include position data, actions performed, chat messages sent, etc. In the future, we will be adding quite a bit more code into this method as this will be our main means of receiving communication from our server. This means we will be using this method to evaluate different messages and responding accordingly.

Responding to Closed Connection

As mentioned before, it will be imperative for us to deal with clients who have disconnected from our server in some capacity. For now, we will be just adding some code that will log that a player/client has disconnected and their ID. In the future we will be adding some additional code but for the moment this is all we will need:



client.on('close', () => {
    console.log('This Connection Closed!')
    console.log("Removing Client: " + client.id)
})


Enter fullscreen mode Exit fullscreen mode

Testing our WebSocket Server

With that last bit of code, our base framework for WebSocket server is complete. So let's do a brief test to make sure everything was coded correctly.

Navigating back to our terminal window, type this command and press the Enter/Return key:



node server.js


Enter fullscreen mode Exit fullscreen mode

You should then see in a your terminal window these lines:



server started
listening on 8080


Enter fullscreen mode Exit fullscreen mode

With that we have just confirmed that our code works!!

For the complete complete code, visit the code repo for this part/episode:

Creating Our WebSocket Server Repo


And with all those lines of code we are setting the base for our Multiplayer Game Server. But you may be wondering, "How can we be sure that WebSocket and our persistent connection is actually working?". Well, in the next part of this blog series, we will be covering making the client or game application that will be communicating with our server.

I'm excited about this portion of the series because we will be building our gaming application using the Unity3D engine and will then get an opportunity to see our server in action.

Unity3D Logo

So stick around because I promise you, it'll be a good time. Even if you are not an aspiring game developer, you will have the opportunity learn the basics of how the client portion of a WebSocket application works and walk away with new useful skills.

As always, Thank you for checking out this blog!

Onwards and Upwards!

Bradston Henry

==== FOLLOW ME ON SOCIAL MEDIA ====

Twitter: Bradston Dev
Dev.to: @bradstondev
Youtube: Bradston YT
LinkedIn: Bradston Henry

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .