Knock Knock

Summary. The goal of this tutorial is to show you how to create a building block implementing a particular behavior and create its corresponding block contract. Moreover, we show how to comply with one Reactive Bocks' important rule, namely, operations must not block or wait. The block handles the communication part for a role in a Knock Knock joke.

As you already know, a knock knock joke goes like the following:

Server: Knock Knock!

Client: Who's there?

Server: Turnip

Client: Turnip who?

Server: Turnip the heat, it's cold in here!

In this tutorial, we only create an application for the client role.

Suggestion. Do Send Multiple Emails tutorial first.


Step 1: Install the Reactive blocks SDK and Import the Tutorial Project

  • In case you have not installed the SDK yet, follow the instructions here.
  • Open the Reactive Blocks Perspective
  • Start the import wizard as explained here. Import the following project:
    • (Level 2) Knock Knock (start here)

There is a similar project, namely (2) Application: Knock Knock (finished). This project contains the outcome of this tutorial.

The tutorial project contains blocks Client UI and Client App that will be used to test the block we created during the tutorial. The server implementation of a knock knock joke is taken from the Java tutorials and already included in the project.


Step 2: Create a Local Building Block

The block that we create will have the following behavior:

When the block is started, the communication channel to a knock knock server is established. If the connection cannot be created, an error is reported. Otherwise, the block will wait for a message from the server which thereafter a reply can be sent. This exchange of messages (with the send-reply pattern) can happen several times. Then, the client can stop the block.

Let us first create a new local building block:

  • In the Block View, click the icon of a block with a + sign (new block) to create a new building block.
  • Fill in Client Transporter as the name of the block.
  • Remember to select Building Block as the template.
  • Create the block in the project which you just imported in Step 1.

Step 3: Behavior - Connection Establishment

In this step, we will implement a part of the block's behavior related to the establishment of a communication channel, i.e., socket.

  • In the behavior page of block Client Transporter, add an input pin of type Start (single) named start.
  • Add an operation connect which will establish a connection to a knock knock server.
  • Add a control flow from start to connect.

Client Transporter

Now, let us fill in the content of operation *connect with code that opens a socket to a knock knock server which will be running locally on port 4444.

  • Go to the corresponding Java page of block Client Transporter
  • Fill in the code shown below:
private Socket socket = null;
private PrintWriter out = null;
private BufferedReader in = null;
 
public void connect() {
  try {
    //create a socket
    //the server is running on localhost, port 4444;
    String s = null; 
    socket = new Socket(s, 4444);
    out = new PrintWriter(socket.getOutputStream(), true);
    in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  } catch (Exception e) {
      log("Failed to connect to server! ", e);
  }
}

log() is an operation in every building block (notice extends Block in the class declaration for Chat UI block) that is used to log events that are considered important or helpful. By default, the message passed in this method will be displayed in the console at runtime.

The process of opening a socket may fail. As there may be other behavior triggered by this event, we take the event in the block's behavior. In SDK, it means we need to - send a signal to the corresponding block, and - make the block ready to receive the signal.

Sending a signal to block is done by method sendToBlock(). Making the block ready to receive a signal is done by adding a reception in the block's behavior description.

  • Add the following line inside the catch() part of method connect:
sendToBlock("START_FAILED");
  • Go to the Behavior page of block Client Transporter.
  • Right click the diagram and add an reception to event that is called START_FAILED.
  • Add a fork and an output pin of type Termination (alternative) named startFailed.
  • Connect the nodes in the diagram as depicted in the following figure.

Client Transporter - START_FAILED


Step 4: Behavior - Listening for Incoming Messages (Concurrency)

The block will need to listen for incoming messages from the server. As you know, this action is blocking. Reactive Blocks does not allow an operation to be blocked so that applications can be executed efficiently and stay responsive. Hence, the task should be executed in a separate thread.

Messages received from the server needs to be sent to the block Client Transporter. As shown in step 3 this is done by using method sendToBlock() and adding a reception.

  • Go to the corresponding Java page of block Client Transporter.
  • Add the code for creating thread and sending received messages as shown in the following snippet:
public void connect() {
  Runnable r = new Runnable() {
    @Override
    public void run() {
      try {
        //create a socket
	//the server is running on localhost, port 4444;
	String s = null; 
	socket = new Socket(s, 4444);
	out = new PrintWriter(socket.getOutputStream(), true);
	in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 
	//listening for messages from the server
	String fromServer;
	while ((fromServer = in.readLine()) != null) {
	  sendToBlock("MESSAGE_IN", fromServer);
	}
      } catch (Exception e) {
	log("Failed to connect to server! ", e);
	sendToBlock("START_FAILED");
      }
    }
  };
  Executors.newSingleThreadExecutor().execute(r);
}

Note that here we decide to let the thread create the socket too.

  • Go to the Behavior page, add a new reception called MESSAGE_IN.

The content of the received message is sent through a parameter to the signal.

  • Right-click the event reception MESSAGE_IN and choose Data Type > java.lang.String.

Event reception data type menu

  • Add an output pin of type Streaming Output named fromServer.
  • Add the data type java.lang.String to the parameter node.
  • Connect the nodes as depicted in the figure below.

Client Transporter Connections


Step 5: Behavior - Send Messages to the Server and Close Connection

In a knock knock joke, a client sends a reply message for every message it receives from a server. We model this by an incoming flow carrying the message to the block. The flow is connected to an operation which sends the message. Thereafter, we need to make the block ready to receive the subsequent message from the server. The command to close the communication channel can come from the block's environment. We model this by another incoming flow and an operation that release resources.

  • In the Behavior page, add two streaming input pins, namely toServer and stop.
  • Add data type java.lang.String to the toServer pin.
  • Add an output pin of type Termination (alternative) named stopped.
  • Add two operation called send and stop.
  • Fill the following snippet for the method send and stop:
public void send(String toServer) {
	out.println(toServer);
}

public void stop() {
	Runnable r = new Runnable() {
		@Override
		public void run() {
			try {
				if (in != null) {
					in.close();
				}
				if (out != null) {
					out.close();
				}
				if (socket != null) {
					in.close();
					socket.close();
				}
			} catch (Exception e) {
				log("Error stopping the client!", e);
			}
		}
	};
	Executors.newSingleThreadExecutor().execute(r);
}
  • Go back to the Behavior page, add a merge node.
  • Connect the nodes as depicted in the following figure:

Note that the merge node is used in order to make the block ready to receive event MESSAGE again after sending a reply to the server.


Step 6: Creating the Block's Contract

Before using the block, we need to create the block's contract. How to edit a block contract is described here. An example how to create a block contract is also given here.

Let us follow the guidelines to create contracts.

1. Informal behavior description

This is already covered in the beginning of this tutorial.

2. Identification of activity steps

Next, we find the activity steps of the block. In general, you first need to identify the input parameter nodes and all the triggers, such as receptions and timers. Then, for each node, imagine there is a token flowing from it. Follow the token along in the diagram until it flows out of the block or you find a node, e.g., a reception, where the token(s) can stop.

The block has five activity steps as shown in the figures below.

3. Activity steps and parameter nodes

Then, inspect the steps that contains parameter nodes and the type of parameter nodes.

  • Activity step 1 includes a starting input pin start. This should be an initial transition.
  • Activity step 2 includes a streaming output pin fromServer.
  • Activity step 3 includes a terminating output pin startFailed. This should be a transition to a final state.
  • Activity step 4 includes a streaming input pin toServer.
  • Activity step 5 includes a streaming input pin stop and a terminating output pin stopped. This should be a transition to a final state.

Since all of the steps includes at least one input or output pin, all of them will be used in creating the contract below.

4. Creating the ESM

There is one initial transition, i.e., the activity step 1. Let us create the transition.

  • On the Contract page, right click and choose + Add transiton….
  • You will get a window like the following figure.
  • Select Initial State as the Source State.
  • Select active as the Target State.
  • Select pin start.
  • Click Finish.

Transition

Then, according to the position of the tokens after activity step 1 is executed, either step 2 or step 3 can be taken. Let's deal with step 2 first.

Activity step 2 corresponds to the arrival of the first knock knock message from the Server. By now we know that the source state for the related transition is state active. But, what is its target state and label? According to the informal behavior description, the client sends a reply upon receiving a message from the server, which corresponds to activity step 4. We can combine these two steps in one transition or create one transition for each step.

We choose to keep step 2 and 4 separated as the reply can come from a user (e.g., from a GUI) which, therefore, needs waiting. Moreover, we want to model the “send-reply” pattern of the knock knock joke. Hence, we have two transitions as follows:

  • From state active to a new state which is called waiting involving parameter fromServer (corresponds to activity step 2).
  • From state waiting back to state active involving parameter toServer (corresponds to activity step 4).
  • Now, create those two transitions, similarly like when we create the initial transition. Note that you might need to choose New State and give a new name in the Target State instead of selecting one available state.
  • Your contract transition table should look like the following figure:

Transition Table

In a knock knock joke the server sends the last message. Hence, the block Client Transporter can be stopped after receiving a message from the server (state waiting). This stopping action corresponds to activity step 5.

  • Add a new transition from state waiting to a final state and labelled with stop/stopped. Note that in order to get the correct label, stop must be placed higher than stopped. You can use the Move Up and Move Down buttons to achieve this.

Transition Order

Now, let's deal with the last activity step, #3. Obviously this step corresponds to a transition from state active to a final state and labelled /startFailed. However, notice that after activity step 2 is executed and the block is in state waiting, there is still a token in reception START_FAILED and hence activity step 3 can be executed. But, in this tutorial we choose to use the notification only when the connection is started.

  • Add a transition from state active to a final state involving node startFailed.
  • Your contract should look like the following figure (right click and choose Open Diagram to get the diagram):

Final Contract

Let us analyze our block. You should get a warning Flow stopped at 'startFailed'. It is the one that we ignore, namely the block is in state waiting and there is a token in reception START_FAILED which can flow to parameter node startFailed.


Step 7: Creating an Application

Now, let us test block Client Transporter that we just created.

  • In the tutorial project, find and open block Client App. It should already contain inner block Client UI and an initial node that is connected to the start pin of block Client UI.
  • Drag and drop block Client Transporter into the application.
  • Add a fork node and two activity final nodes.
  • Connect the new block with other nodes as depicted in the following figure.

Application Connections

Analyze the application block. You should find neither error nor warning. You might be wondering why the warning when we analyze block Client Transporter is not shown. This is because when a composed block is being analyzed, the inner blocks are not analyzed further. But, their block contracts are used to analyze the composed block.

You can also use the animator tool to understand the behavior of the application.


Step 8: Build and Execute the Application

Now, you can build executable code from the block Client App.

To test the client app, we need to start a knock knock server first. Right click the generated code and choose Run As and then Java Application. You will get a window like the following figure.

Select Java Application dialog

Choose KnockKnockServer - com.bitreactive.tutorial.knock.knock.server.

Now start our Client App by repeating the same action, but now choose Start - com.bitreactive.tutorial.knock.knock.clientapp.

You will get a window similar to the following figure. You can fill it in with the client part of knock knock joke.

Knock-Kock Client

Well done! Now, you can create blocks with their corresponding block contract.