javascriptnode.jsasynchronousdgrams

UDP pinger timeout in javascript dgram node


So, for a course i'm taking, we're coding a UDP pinger in Javascript, using Node.js and Dgram. We've been given the following assignment:

Create the client code for an application. Your client should send 10 ping messages to the target UDP server. For each message, your client should calculate the round trip time from when the package is sent to when the response is received. Should a package be dropped along the way, the client is to handle this as well. This should be done by having the client wait 1 second for a response after sending each package. If no reply is received, the client should log accordingly (package lost, no response, timeout, etc.) and send a new package to try again. However, the total amount of packages sent should still only be 10. The client should also calculate a percentage of packages lost/no response received, and log this before connection is closed.

THis if course seems rather straight forward, and I thought so. I've been coding it for a while, and I'm almost finished, but I'm having issues with the aspect of making the client send a package, await response, and then act accordingly.

So far, what my code does is basically to send a ping, and when a pong is received, it sends another ping. What I can't figure out is how to make it log that a response wasn't received before sending the next package. In other words, I know how to make it react to a received response, I just don't know how to make it respond if no response is given within a set timeframe. I've tried playing around with if-statements and loops, as well as async functions, but I haven't made it work yet, so now I'm asking for help.

Code is here:

const dgram = require("dgram");
const ms = require("ms"); 

var client = dgram.createSocket("udp4");


const PORT = 8000;
const HOST = "localhost";

let today = "";
let t0 = "";
let t1 = "";
let RTT = "";

let sentPackages = "";
let receivedPackages = "";

const messageOutbound = Buffer.from("You Up?");

sendPackage();

const x = setInterval(sendPackage, 1000);
client.on("message", (message, remote) => {
    receivedPackages++
    today = new Date();
    t1 = today.getTime();
    console.log(
      `Message from: ${remote.address}:${remote.port} saying: ${message}`
    );
    RTT = ms(t1 - t0, { long: true });
    console.log(RTT);
    const x = setInterval(sendPackage, 1000);
  });

client.on('error', (err) => {
  console.log(`server error:\n${err.stack}`);
  server.close();
});  

async function sendPackage() {
  if (sentPackages < 10) {
    client.send(messageOutbound, 0, messageOutbound.length, PORT, HOST, () => {
      sentPackages++
      let today = new Date();
      t0 = today.getTime();
      console.log(
        `message has been sent to ${HOST}:${PORT}. Message sent at: ${t0}`
      );
    });
  } else {
    calculateLoss();
    client.close();
  }
};
function calculateLoss() {
  let amountLost =  sentPackages - receivedPackages;
  let percentageLoss = amountLost / sentPackages * 100
  console.log(amountLost);
  console.log(percentageLoss +"% of packages lost");
};

Solution

  • I would use async / await to simply wait 1000ms / 1s between messages, then keep track of all messages in an array.

    We identify messages with a uuid, so we can ensure that messages we receive can be matched to those we send.

    We can then log all the required statistics afterwards:

    const dgram = require("dgram");
    const uuid = require('uuid');
    
    const PORT = 8000;
    const HOST = "localhost";
    
    const client = dgram.createSocket("udp4");
    
    // Array that keeps track of the messages we send
    let messages = [];
    
    // When we get a message, decode it and update our message list accordingly...
    client.on("message", (messageBuffer, remote) => {
        let receivedMessage = bufferToMessage(messageBuffer);
        // Find the message we sent and set the response time accordingly.
        let message = messages.find(message => message.uuid === (receivedMessage ||{}).uuid);
        if (message) {
            message.responseTimestamp = new Date().getTime();
        }
    });
    
    client.on('error', (err) => {
        console.log(`server error:\n${err.stack}`);
        server.close();
    });  
    
    function createMessage() {
        return { uuid: uuid.v4() };
    }
    
    function messageToBuffer(message) {
        return Buffer.from(JSON.stringify(message), "utf-8");
    }
    
    function bufferToMessage(buffer) {
        try {
            return JSON.parse(buffer.toString("utf-8"));
        } catch (error) {
            return null;
        }
    }
    
    // Wait for timeout milliseconds
    function wait(timeout) {
        return new Promise(resolve => setTimeout(resolve, timeout));
    }
    
    function sendMessage(message, port, host) {
        // Save the messages to our list...
        messages.push(message);
        console.log(`Sending message #${messages.length}...`);
        // Set the time we send out message...
        message.sentTimestamp = new Date().getTime();
        let messageBuffer = messageToBuffer(message);
        return new Promise((resolve, reject) => {
            client.send(messageBuffer, 0, messageBuffer.length, port, host, (error, bytes) => {
                if (error) {
                    reject(error);
                } else {
                    resolve(bytes);
                }
            })
        });      
    }
    
    async function sendMessages(messageCount, port, host, timeout) {
        for(let messageIndex = 0; messageIndex < messageCount; messageIndex++) {
            let message = createMessage();
            await sendMessage(message, port, host);
            await wait(timeout);
            if (message.responseTimestamp) {
                console.log(`Response received after ${message.responseTimestamp - message.sentTimestamp} ms...`);
            } else {
                console.log(`No response received after ${timeout} ms...`);
            }
        }
        logStatistics(messages);
    }
    
    function logStatistics(messages) {
        let messagesSent = messages.length;
        let messagesReceived = messages.filter(m => m.responseTimestamp).length;
        let messagesLost = messagesSent - messagesReceived;
        console.log(`Total messages sent: ${messagesSent}`);
        console.log(`Total messages received: ${messagesReceived}`);
        console.log(`Total messages lost: ${messagesLost} / ${(100*messagesLost / (messages.length || 1) ).toFixed(2)}%`);
        if (messagesReceived > 0) {
            console.log(`Average response interval:`, messages.filter(m => m.responseTimestamp).reduce((averageTime, message) =>  {
                averageTime += (message.responseTimestamp - message.sentTimestamp) / messagesReceived;
                return averageTime;
            }, 0) + " ms");
        }
    }
    
    sendMessages(10, PORT, HOST, 1000);