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.
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.
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 this step, we will implement a part of the block's behavior related to the establishment of a communication channel, i.e., socket.
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.
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.
catch()
part of method connect:sendToBlock("START_FAILED");
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.
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.
The content of the received message is sent through a parameter to the signal.
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.
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);
}
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.
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.
This is already covered in the beginning of this tutorial.
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.
Then, inspect the steps that contains parameter nodes and the type of parameter nodes.
Since all of the steps includes at least one input or output pin, all of them will be used in creating the contract below.
There is one initial transition, i.e., the activity step 1. Let us create the 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:
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.
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.
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.
Now, let us test block Client Transporter that we just created.
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.
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.
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.
Well done! Now, you can create blocks with their corresponding block contract.