LSCS Simulator and JPL Simulator

The JVM simulator server is implemented in the SocketServerStream class. It implements the same low level protocol as the JPL C library, located in the m1cs-lscs-sim subproject. The main difference in the current version is that the Scala version of the server supports a “DELAY ms” command, which is used to simulate a command that takes ms milliseconds to complete. For testing, it also takes a q command that causes the server to shut down.

Starting the Socket Server

For testing, the JVM socket server can be started with:

new SocketServerStream()(actorSystem)

The C version of the server can be started with the CmdSrvSim command. See the README.md in the m1cs-lscs-sim subproject for details on the C version.

Creating a Socket Client

In order to create a socket client, you need either an ActorSystem[SpawnProtocol] or an ActorContext, since the client needs to create an actor internally, in order to manage responses.

To use an ActorSystem, use the SocketClientStream.withSystem() method:

implicit val system: ActorSystem[SpawnProtocol.Command] = ActorSystem(SpawnProtocol(), "SocketServerStream")
  
val client1 = SocketClientStream.withSystem("client1")

If you are already in an actor, you can use the ActorContext instead (used only in the same thread to create a child actor):

    Behaviors.setup[Command] { ctx =>
      val io = SocketClientStream(ctx, name)
      // ...
    }

Both methods have optional arguments for the host and port to use to connect to the server. The deault is localhost:8023, which is the same default used by the C library.

Sending Messages to the Server

The class used to access the socket server is SocketClientStream and it can talk to either the C or the Scala version of the server.

The API for the client is basically just: send(message), which returns a non-blocking Future response:

Scala
sourceimplicit val system: ActorSystem[SpawnProtocol.Command] = ActorSystem(SpawnProtocol(), "SocketServerStream")
implicit val ece: ExecutionContextExecutor              = system.executionContext
implicit val timout: Timeout                            = Timeout(30.seconds)

// Start the server
new SocketServerStream()(system)

test("Basic test") {
  val client1 = SocketClientStream.withSystem("client1")
  val client2 = SocketClientStream.withSystem("client2")
  val client3 = SocketClientStream.withSystem("client3")

  def showResult(msg: SocketMessage): SocketMessage = {
    println(s"XXX showResult: $msg")
    msg
  }
  val f0 = client1.send("IMMEDIATE arg1 arg2").map(showResult)
  val f1 = client1.send("DELAY 2000").map(showResult)
  val f2 = client2.send("DELAY 1000").map(showResult)
  val f3 = client3.send("DELAY 500").map(showResult)
  val f4 = client1.send("DELAY 200").map(showResult)
  val f5 = client2.send("IMMEDIATE arg1 arg2 arg3").map(showResult)

  val f = for {
    resp0 <- f0
    resp1 <- f1
    resp2 <- f2
    resp3 <- f3
    resp4 <- f4
    resp5 <- f5
  } yield {
    client1.terminate()
    client2.terminate()
    client3.terminate()
    List(resp0, resp1, resp2, resp3, resp4, resp5)
  }
  val list = Await.result(f, 30.seconds)
  println(s"XXX test1 result = $list")
  assert(list.forall(_.cmd.endsWith(" Completed.")))
}

The type of the message sent is SocketMessage, which has the same layout as the JPL C socket message:

Scala
source/**
 * The type of a message sent to the server (also used for the reply).
 *
 * @param hdr message header
 * @param cmd the actual text of the command
 */
case class SocketMessage(hdr: MsgHdr, cmd: String) {

In most cases you don’t need to provide the message header, since it is generated automatically from the message text. It contains information, such as the size of the message and a sequence number, which is also returned as part of the response. The header is represented by the MsgHdr class, below:

Scala
source/**
 * @param msgId message type
 * @param srcId sender application id
 * @param msgLen message length including header(bytes
 * @param seqNo sequence number
 */
case class MsgHdr(msgId: MessageId, srcId: SourceId, msgLen: Int, seqNo: Int)

The type of the response to sending a command is the same (SocketMessage).

Stopping the Server

For testing, you can use the terminate() method on the client to cause the server to shutdown, ending the connection. This is not supported in the C version.

Wire Format

The wire format for a socket message is based on the C library version and uses the same header and byte order.

The source code for this page can be found here.