Kurose’s book exercises (labs) — UDP Pinger (Server and Client)

The exercise description is:

In this lab, you will study a simple Internet ping server written in the Java language, and implement a corresponding client. The functionality provided by these programs are similar to the standard ping programs available in modern operating systems, except that they use UDP rather than Internet Control Message Protocol (ICMP) to communicate with each other. (Java does not provide a straightforward means to interact with ICMP.)

The ping protocol allows a client machine to send a packet of data to a remote machine, and have the remote machine return the data back to the client unchanged (an action referred to as echoing). Among other uses, the ping protocol allows hosts to determine round-trip times to other machines.

ICMP Message

Pinger Message Class:

class PingMessage(addr: InetAddress, port: Int, message: String) {
  def getAddress = addr
  def getMessage = message
  def getPort = port
}

UDP Pinger Protocol

class UDPPinger {
  protected val socket = new DatagramSocket()
 
  def sendPing(ping: PingMessage) {
    val host = ping.getAddress
    val port = ping.getPort
    val message = ping.getMessage
    val msgBytes = message.getBytes
    val msglen = message.length
    try {
      val packet = new DatagramPacket(msgBytes, msglen, host, port)
      socket.send(packet)
      println("Sent message to " + host + ":" + port)
    } catch {
      case e: IOException =>
        println("Error sending packet: " + e)
    }
  }
 
  def receivePing = {
    val recvBuf = new Array[Byte](Configs.MAX_PING_LEN)
    val recvPacket = new DatagramPacket(recvBuf, Configs.MAX_PING_LEN)
    socket.receive(recvPacket)
    println("Received message from " +
      recvPacket.getAddress + ":" + recvPacket.getPort)
    val recvMsg = new String(recvPacket.getData)
    val reply = new PingMessage(recvPacket.getAddress,
      recvPacket.getPort, recvMsg)
    reply
  }
}

UDP Pinger Client

The multithreaded client class:

class UDPClient(remoteHost: String, remotePort: Int, numberOfPings: Int)
    extends UDPPinger with Runnable {
  var numReplies = 0
  val replies = new Array[Boolean](numberOfPings)
  val rtt = new Array[Long](numberOfPings)
 
  def this(remoteHost: String, remotePort: Int) =
    this(remoteHost, remotePort, Configs.NUM_PINGS)
 
  def run {
    sendAllPings
    sendReplyTimeout
    printStatistics
  }
 
  private def sendAllPings {
    socket.setSoTimeout(Configs.TIMEOUT)
    for (i < - 0 until numberOfPings) {
      val message = "PING " + i + " " + (new Date).getTime + " "
      replies(i) = false
      rtt(i) = Configs.RTT
      val hostName = InetAddress.getByName(remoteHost)
      val ping = new PingMessage(hostName, remotePort, message)
      sendPing(ping)
      handleReply(receivePing.getMessage)
    }
  }
 
  private def sendReplyTimeout {
    socket.setSoTimeout(Configs.REPLY_TIMEOUT)
    while (numReplies < numberOfPings) {
      try {
        handleReply(receivePing.getMessage)
      } catch {
        case e: SocketTimeoutException =>
          numReplies = numberOfPings
      }
    }
  }
 
  private def printStatistics {
    for (i < - 0 until numberOfPings) {
      val s =
        if (rtt(i) > 0)
          rtt(i).toString
        else
          "1"
      println("PING " + i + ": " + replies(i) + " RTT: " + s + " ms")
    }
  }
 
  private def handleReply(reply: String) {
    val tmp = reply.split(" ")
    val pingNumber = tmp(1).toInt
    val then = tmp(2).toLong
    replies(pingNumber) = true
    rtt(pingNumber) = (new Date).getTime - then
    numReplies += 1
  }
}

The Client:

object PingerClient {
  def main(args: Array[String]) {
    val host = "127.0.0.1"
    val port = 9876
    println("Contacting host " + host + " at port " + port)
    val client =  new UDPClient(host, port, 20)
    client.run
  }
}

UDP Pinger Server

object PingerServer {
  def main(args: Array[String]) {
    val serverPort = 9876
    val serverSocket = new DatagramSocket(serverPort)
    val receiveData = new Array[Byte](Configs.MAX_PING_LEN)
    while (true) {
      val receivePacket = new DatagramPacket(receiveData, receiveData.length)
      serverSocket.receive(receivePacket)
      val sentence = new String(receivePacket.getData)
      if (sentence != null && !sentence.equals(""))
        println(sentence)
      val ip = receivePacket.getAddress
      val port = receivePacket.getPort
      val data = sentence.getBytes
      val sendPacket = new DatagramPacket(data, data.length, ip, port)
      serverSocket.send(sendPacket)
    }
  }
}

Secondary and auxiliary modules

object Configs {
  val MAX_PING_LEN = 1024
  val NUM_PINGS = 10
  val TIMEOUT = 1000
  val REPLY_TIMEOUT = 5000
  val RTT = 1000000
}

Simulations

Assuming you’re running the client and server on the same machine (127.0.0.1), you can run the code:

object PingerTestSimulation {
  def main(args: Array[String]) {
    println("Start ping simulation...")
    new Thread {
      override def run {
        PingerServer.main(args)
      }
    }.start
    new Thread { 
      override def run {
        PingerClient.main(args)
      }
    }.start
  }
}

These codes can be found here: UDPPinger.scala, PingerServer.scala, PingerClient.scala e PingerTestSimulation.scala