It's All That Simple!
A Step-by-step Example to
Create and Test a Component in J-Sim
June 04, 2002
This document presents a step-by-step
example in writing a simple protocol component in J-Sim. We
assume that you have read the four examples in the INET tutorial, as some of the materials covered there will not be
repeated here. The example implements a simple 'echo'
protocol that is similar to 'ping' under the UNIX environment.
The sender at a node sends an echo-request UDP
packet to a receiver at another node which then returns an echo-reply
packet immediately. With the echo protocol, one can
calculate the round trip time between two nodes. Although the
example is simple, it covers all the essential aspects in
creating a new component in J-Sim, and may serve as a
template for you to implement other protocol components. For complete
document, please refer to Component Writer's Guide.
There are three steps in
implementing a protocol component:
In a new file echopkt.java,
we first define a new packet
class for the 'echo' protocol. The new class is used to
carry the information for the echo protocol.
1 /** 2 * Example application packet class - echopkt. 3 */ 4 public class echopkt 5 { 6 public int type; 7 public double time; 8 public echopkt(int type_, double time_) 9 { 10 type = type_; 11 time = time_; 12 } 13 }
|
The variable 'type' is set to '1' if the packet is an echo request (sent
from a sender to a receiver) and '2' if the packet is an echo
reply (sent by a receiver in response to an echo request).
The variable 'time' is a time stamp when the packet is sent. It
will be copied by a receiver to the corresponding echo reply
so that a sender can calculate the round-trip time.
In a new file echoer.java,
we extend the class Module and define the echo protocol:
1 import drcl.comp.Port; 2 import drcl.comp.Contract; 3 import drcl.inet.contract.DatagramContract; 4 5 /** 6 * Example application class - echoer. 7 */ 8 public class echoer extends drcl.net.Module 9 { 10 final static int REQ=1, RPL=2, tos=0; 11 12 static { 13 setContract(echoer.class, drcl.net.Module.PortGroup_DOWN + "@", 14 new DatagramContract(Contract.Role_PEER)); 15 } 16 17 public echoer() 18 { 19 super(); 20 } 21 22 public echoer(String id_) 23 { 24 super(id_); 25 } 26 27 void sendmsg(int type_, double tm_, long dst_, int dport_) 28 { 29 echopkt pkt_ = new echopkt(type_, tm_); 30 downPort.doSending(new DatagramContract.Message(pkt_, 10/*size*/, 31 drcl.net.Address.NULL_ADDR, dst_, dport_, tos)); 32 } 33 34 public void send_echo_request(long dst_, int dport_) 35 { 36 debug("Sending an Echo packet to port " + dport_ + " at node" + dst_); 37 sendmsg(REQ, getTime(), dst_, dport_); 38 } 39 40 protected void dataArriveAtDownPort(Object data_, Port downPort_) 41 { 42 if (!(data_ instanceof DatagramContract.Message)) 43 { 44 error(data_, "dataArrivedAtDownPort()", downPort_, "unrecognized data"); 45 return; 46 } 47 DatagramContract.Message datagram_ = (DatagramContract.Message) data_; 48 long src_ = datagram_.getSource(); 49 int sport_ = datagram_.getSourcePort(); 50 echopkt pkt_ = (echopkt)datagram_.getContent(); 51 if (pkt_.type==RPL) 52 { 53 double rtt = getTime() - pkt_.time; 54 debug("Getting a reply, round-trip time is: " + rtt); 55 } 56 else { 57 debug("Getting a request, sending time is: " + pkt_.time); 58 sendmsg(RPL, pkt_.time, src_, sport_); 59 } 60 } 61 }
|
- 1-3:
- We import several classes that are used in this
component.
- 8:
- Define the echoer class as an extended class of
drcl.net.Module in which
upPort
and downPort are defined for a module to fit into a protocol stack. - 10:
- Define several constants for the protocol: REQ and RPL
are the
packet type (echo request or echo reply), and tos
is the type of service, all of which are passed to the lower layer protocol (UDP) when an
echoer
sends a packet.
- 13-14:
- Bind the
downPort of this component with
drcl.inet.contract.DatagramContract
using the setContract method in the drcl.comp.Component
class. - 17-25:
- Define the constructors of echoer.
- 27-32:
- Define the method used to send a
packet (an echo request or echo reply) to the peer (specified by
dst_ and
dport_). The method
first creates an echopkt object by setting its type and a
timestamp (line 29). Then, it creates and sends a DatagramContract
message at downPort (line 30-31). - 29-33:
- Define the send_echo_request() method.
This method is to be called interactively at runtime.If the destination node and port number are set to valid
values, the echoer sends an echo request to the
destination.
- 40-60:
- Implement
the
dataArriveAtDownPort()
method. This method is defined in
drcl.net.Module and is invoked when an
object arrives at
the module's downPort. An echoer only expects to receive an echo
request or an echo reply (packaged as defined in the DatagramContract):
- If the packet is an echo request, the method composes an echo reply
with the timestamp copied from the echo request
and sends the reply back to the sender.
- If the packet is an echo reply, the method calculates the RTT and sends the result as
a debug message.
To facilitate programming, we have implemented
several "template" classes in J-Sim. To implement an application
layer protocol, one can extend an application layer class SUDPApplication that has been designed to take care of, and
hence hide from users, many low-level details (as shown above). In particular,
this class
provides a set of methods to send/receive a datagram. Below is the new class, new_echoer.java,
which does the same thing as echoer.
1 import drcl.comp.Port; 2 import drcl.comp.Contract; 3 4 /** 5 * Example application class - new_echoer. 6 */ 7 public class new_echoer extends drcl.inet.application.SUDPApplication 8 { 9 final static int REQ=1, RPL=2; 10 11 public new_echoer() 12 { 13 super(); 14 } 15 16 public new_echoer(String id_) 17 { 18 super(id_); 19 } 20 21 public void send_echo_request(long dst_, int dport_) 22 { 23 debug("Sending an Echo packet to port " + dport_ + " at node " + dst_); 24 sendmsg(new echopkt(REQ, getTime()), 10/*size*/, dst_, dport_); 25 } 26 27 protected void dataArriveAtDownPort(Object data_, Port downPort_) 28 { 29 long src_ = getPeerAddress(data_); 30 int sport_ = getPeerPort(data_); 31 echopkt pkt_ = (echopkt)getContent(data_); 32 if (pkt_.type==RPL) 33 { 34 double rtt = getTime() - pkt_.time; 35 debug("Getting a reply, round-trip time is: " + rtt); 36 } 37 else { 38 debug("Getting a request, sending time is: " + pkt_.time); 39 sendmsg(new echopkt(RPL, pkt_.time), 10/*size*/, src_, sport_); 40 } 41 } 42 }
|
- 7:
- Define the
new_echoer class as an extended class of
drcl.inet.application.SUDPApplication. As the
downPort of a SUDPApplication class is bound with the
DatagramContract, we do not have to bind it explicitly. - 21-25:
- Define the method used to send an echo request to a
peer (specified by
dst_ and dport_). This method is similar to the
same method in the echoer class except that new_echoer takes
advantage of the sendmsg()
method defined in the SUDPApplication
class to send the packet. Here we do not have to explicitly create a
contract message and
send it at the downPort. - 27-41:
- Implement the
dataArriveAtDownPort()
method. This block of codes is identical to what in the echoer
class except that we take advantage of the getPeerAddress(),
getPeerPort()
and getContent()
methods defined in SUDPApplication.
After writing and compiling the
Java classes, we are now ready to test (or
simulate) the protocol. This
is done by writing a TCL script to construct the simulation scenario. The
easiest way for a beginner is perhaps to cut-and-paste from existing
examples [e.g., Examples 1, 2, 3, 4 in the INET Tutorial]. Here we use the
scenario constructed in Example
2.
The TCL script used to
test echoer/new_echoer is shown below. (echo.tcl)
1 # echoer.tcl 2 # 3 # Test echoer/new_echoer in the following topology with echoer at h0 and h2 4 # 5 # Topology: 6 # 7 # h0 ----- n1 ----- h2 8 # 9 10 cd [mkdir -q drcl.comp.Component /test] 11 12 # create the topology 13 puts "create topology..." 14 set link_ [java::new drcl.inet.Link] 15 $link_ setPropDelay 0.3; # 300ms 16 set adjMatrix_ [java::new {int[][]} 3 {{1} {0 2} {1}}] 17 java::call drcl.inet.InetUtil createTopology [! .] $adjMatrix_ $link_ 18 19 puts "create builders..." 20 # NodeBuilder: 21 set nb [mkdir drcl.inet.NodeBuilder .nodeBuilder] 22 $nb setBandwidth 1.0e7; #10Mbps 23 24 puts "build..." 25 $nb build [! n?] 26 $nb build [! h?] { 27 udp drcl.inet.transport.UDP 28 echo 101/udp new_echoer 29 } 30 ! h?/udp setTTL 3 31 32 # Configure the bottleneck bandwidth and buffer size 33 ! n1 setBandwidth 1 1.0e4; # 10Kbps at interface 1 34 ! n1 setBufferSize 1 6000; # ~10 packets at interface 1 35 36 puts "setup static routes..." 37 java::call drcl.inet.InetUtil setupRoutes [! h0] [! h2] "bidirection" 38 39 puts "set up simulator..." 40 set sim [attach_simulator .] 41 42 puts "Done!"
|
As most of the Tcl and RUV commands are explained in details in Example
2, we do not repeat
the efforts here. You may also refer to Tcl/Jacl
quick reference and script
reference for a detailed account of these commands.
- 12-17:
- Specify the the attributes of links, and configure the topology.
- 19-30:
- Create a node builder and build the nodes. Note that the echo protocol is connected to UDP at port 101.
- 32-34:
- Configure the bandwidth and buffer
size of the bottleneck link.
- 36-37:
- Set up static (shortest path)
routes between the hosts.
- 39-40:
- Set up the simulation runtime.
Now, we have every piece of code
we need for testing the echo protocol. You can now use
command "java drcl.ruv.System echo.tcl" to
launch the simulation at a terminal (an xterm in UNIX or a DOS prompt
in Windows). After doing that, the
following window pops up and the scenario is constructed.
Then we use the following command to make both hosts to send echo requests to
each other:
! h0/echo send_echo_request 2 101; ! h2/echo send_echo_request 0 101
The session should look like:
The first few lines are
created by the TCL script (via the puts command) to inform of the
scenario construction process. At the 8th line, we issue the command that makes h0
and h2 send echo requests to each other. The last 4 lines display how the echoers respond with
echo
replies, and calculate RTTs (both are (10 + 8 + 20) * 8 * (10-4 + 3
* 10-7)
+ 0.3 * 4) = 1.2304912
seconds).
Note that we stop the simulation first by "$sim stop"
to make sure that the two hosts send the echo requests at the same time.
If we do not stop simulation first, then the simulation automatically starts
when h0 sends the echo request. The time when h2
sends its request may be different at different runs.
Write the protocol to run directly on top of CSL
How about we just run the protocol directly on top of CSL (core service
layer, think of it as the IP protocol layer)? The code is similar but this
time we extend drcl.inet.Protocol: (echoer2.java)
1 import drcl.comp.Port; 2 import drcl.inet.InetPacket; 3 4 /** 5 * Example protocol class - echoer2. 6 */ 7 public class echoer2 extends drcl.inet.Protocol 8 { 9 final static int REQ=1, RPL=2, tos=0; 10 11 public echoer2() 12 { super(); } 13 14 public echoer2(String id_) 15 { super(id_); } 16 17 void sendmsg(int type_, double tm_, long dst_) 18 { 19 echopkt pkt_ = new echopkt(type_, tm_); 20 21 forward(pkt_, 10/*size*/, drcl.net.Address.NULL_ADDR/*src*/, dst_, 22 false/*routerAlert*/, 255/*TTL*/, tos); 23 } 24 25 public void send_echo_request(long dst_) 26 { 27 debug("Sending an Echo packet to node" + dst_); 28 sendmsg(REQ, getTime(), dst_); 29 } 30 31 protected void dataArriveAtDownPort(Object data_, Port downPort_) 32 { 33 InetPacket ipkt_ = (InetPacket)data_; 34 echopkt pkt_ = (echopkt)ipkt_.getBody(); 35 long src_ = ipkt_.getSource(); 36 if (pkt_.type==RPL) 37 { 38 double rtt = getTime() - pkt_.time; 39 debug("Getting a reply, round-trip time is: " + rtt); 40 } 41 else { 42 debug("Getting a request, sending time is: " + pkt_.time); 43 sendmsg(RPL, pkt_.time, src_); 44 } 45 } 46 }
|
The differences here are:
- We use the forward() method to send out our packet (in raw InetPacket)
instead of using datagram (line 21-22).
- We don't specify port number here as it is now the "protocol ID" (as in
the TCP/IP jargon) and will be specified in the script when we build the
nodes.
- The data received is an InetPacket instead of a datagram service message
(line 33).
The TCL script now becomes: (echo2.tcl)
1 # echoer2.tcl 2 # 3 # Test echoer2 in the following topology with echoer at h0 and h2 4 # 5 # Topology: 6 # 7 # h0 ----- n1 ----- h2 8 # 9 10 cd [mkdir -q drcl.comp.Component /test] 11 12 # create the topology 13 puts "create topology..." 14 set link_ [java::new drcl.inet.Link] 15 $link_ setPropDelay 0.3; # 300ms 16 set adjMatrix_ [java::new {int[][]} 3 {{1} {0 2} {1}}] 17 java::call drcl.inet.InetUtil createTopology [! .] $adjMatrix_ $link_ 18 19 puts "create builders..." 20 # NodeBuilder: 21 set nb [mkdir drcl.inet.NodeBuilder .nodeBuilder] 22 $nb setBandwidth 1.0e7; #10Mbps 23 24 puts "build..." 25 $nb build [! n?] 26 $nb build [! h?] { 27 echo 1111/csl echoer2 28 } 29 30 # Configure the bottleneck bandwidth and buffer size 31 ! n1 setBandwidth 1 1.0e4; # 10Kbps at interface 1 32 ! n1 setBufferSize 1 6000; # ~10 packets at interface 1 33 34 puts "setup static routes..." 35 java::call drcl.inet.InetUtil setupRoutes [! h0] [! h2] "bidirection" 36 37 puts "set up simulator..." 38 set sim [attach_simulator .] 39 40 puts "Done!"
|
The only difference is that now we put the new echo2 protocol directly on top
of CSL (line 27). A testing session is shown as follows:
The round-trip time now is a bit smaller than the previous example because
now the packet size is 10 + 20 = 30 bytes. The exact calculation is
(30 * 8 * (10-4 + 3 * 10-7)
+ 0.3 * 4) = 1.224072 (seconds).
OK, we have written a
new application protocol and test it with a TCL script! it is
all that simple! To implement a specific protocol, we recommend that you
browse through some of the Java class examples in the same protocol family. For example, you may want to browse
through DV classes (TCP classes) before you implement a new routing
protocol (transport layer protocol).
Happy coding and we hope
that you find J-Sim useful.
|