MIT 6.030:

Introduction to IntertransmitPacketive Programming

Laboratory 3: Nodes and Channels

Contents


int i = 0;
while( i < outSize() )
{
     // try, blah blah
     i++;
}

Just as with a while loop, we can use break; to stop the loop.

Question 3: Chapter 9, exercise 2. (Hint: Console also has a readln() method.) [Define a class that periodically reads from the Console and writes the value back to the Console.]

Answer: To read and write from the Console, we can use cs101.io.Console -- to do something periodically, we can implement cs101.lang.Animate and use an AnimatorThread. So:


import cs101.io.Console;
import cs101.lang.*;

public class ReaderWriter implements Animate
{
     AnimatorThread myThread;

     public ReaderWriter( )
     {
          this.myThread = new AnimatorThread( this );
          this.myThread.startExecution( );
     }

     public void transmitPacket( )
     {
          String myString = Console.readln();
          Console.println( myString );
     }
}

You should be familiar with how to use AnimatorThread and Console.

Laboratory

We will attempt to build up a fully functional class from the most basic to the most robust. To begin, we create the following class, which has the minimal code to produce a valid, working NodeBehavior. This class will both build and run, though it isn't very exciting because it doesn't even attempt to move packets.
package nodeNet;
public BoringNodeBehavior implements NodeBehavior {
public void transmitPacket(InputChannelVector inputChannels,
OutputChannelVector outputChannels) {
// transmitPacket is empty.
}
}

Implementing strategy #1:

Now we try to implement strategy #1 from above. We will read from a single input, and write to a single output. Here's our first attempt:
package nodeNet;
public Strategy1_1 implements NodeBehavior {

Object o; // storage for the object we will read/write

public void transmitPacket(InputChannelVector inputChannels,
OutputChannelVector outputChannels) {
// first we try to read a packet, catching the
// necesssary exceptions
try {
o = inputChannel.firstElement().readObject();
} catch (ChannelEmptyException e1) {
} catch (ChannelDisabledException e2) {}

// now we have a packet, so we try to write it.
try {
outputChannels.firstElement().writeObject(o);
} catch (ChannelFullException e3) {
} catch (ChannelDisabledException e4) {
}
}

Alas, we find that while we seem to do okay in certain circumstances, our code can crash, eat packets, or create them. How can we fix this? Well, the first problem, our code can crash, is caused by the lines where we access the 1st element of the ChannelVector (either the input or the output) without ensuring that that channel even exists! Imagine the situation where there are no inputs or outputs. Does it make sense to access the first (non-existent) element? We remedy the situation with the following modifications:
package nodeNet;
public Strategy1_2 implements NodeBehavior {

Object o; // storage for the object we will read/write

public void transmitPacket(InputChannelVector inputChannels,
OutputChannelVector outputChannels) {
// first we try to read a packet, catching the
// necesssary exceptions
try {
// check for at least one input
if (inputChannels.size() != 0) {
o = inputChannel.firstElement().readObject();
}
} catch (ChannelEmptyException e1) {
} catch (ChannelDisabledException e2) {}

// now we have a packet, so we try to write it.
try {
// check for at least one output
if (outputChannels.size() != 0) {
outputChannels.firstElement().writeObject(o);
}
} catch (ChannelFullException e3) {
} catch (ChannelDisabledException e4) {
}
}

Now we need to take care of the problems where we create and destroy packets. How do we create packets? Well, suppose we have read a packet and successfully written it. Our transmitPacket method is called again and this time we fail to successfully read a packet, but Object o is still successfully written to the output channel. Oops! We need to prevent this double (or triple, or quadruple, or...) writing somehow. The following does the trick. Now whenever we write a packet we prevent ourselves from ever writing it again by unsticking our label and checking to make sure we only write packets if Object o is non-null.
package nodeNet;
public Strategy1_3 implements NodeBehavior {

Object o; // storage for the object we will read/write

public void transmitPacket(InputChannelVector inputChannels,
OutputChannelVector outputChannels) {
// first we try to read a packet, catching the
// necesssary exceptions
try {
// check for at least one input
if (inputChannels.size() != 0) {
o = inputChannel.firstElement().readObject();
}
} catch (ChannelEmptyException e1) {
} catch (ChannelDisabledException e2) {}

// now we have a packet, so we try to write it.
try {
// check for at least one output, and check to make sure
// we have a packet ready to write.
if ((outputChannels.size() != 0) && (o != null)) {
outputChannels.firstElement().writeObject(o);
// if no exception is thrown, we have successfully written
// the packet, so unstick the label from the Object.
o = null;
}
} catch (ChannelFullException e3) {
} catch (ChannelDisabledException e4) {
}
}

The final problem we face is that our node sometimes eats packets, meaning that not every packet that enters via one of the inputChannels makes it to an OutputChannel. Borrowing from the above code, though, we can see that the problem is that we read a packet, regardless of whether or not we wrote the one we already had. To prevent this, only do a readObject if o is null (has been written). This final implementation fo our first strategy is thus:
package nodeNet;
public Strategy1_4 implements NodeBehavior {

Object o; // storage for the object we will read/write

public void transmitPacket(InputChannelVector inputChannels,
OutputChannelVector outputChannels) {
// first we try to read a packet, catching the
// necesssary exceptions
try {
// check for at least one input, and make sure we're not
// already holding an Object.
if ((inputChannels.size() != 0) && (o == null)) {
o = inputChannel.firstElement().readObject();
}
} catch (ChannelEmptyException e1) {
} catch (ChannelDisabledException e2) {}

// now we have a packet, so we try to write it.
try {
// check for at least one output, and check to make sure
// we have a packet ready to write.
if ((outputChannels.size() != 0) && (o != null)) {
outputChannels.firstElement().writeObject(o);
// if no exception is thrown, we have successfully written
// the packet, so unstick the label from the Object.
o = null;
}
} catch (ChannelFullException e3) {
} catch (ChannelDisabledException e4) {
}
}

Implementing strategy #2

As mentioned above, strategy #2 is used by IntermediateNodeBehavior. This is a bit more complex, but doable if you've managed to get strategy #1 us and working. The differences are in these areas: class IntermediateNodeBehavior implements NodeBehavior
{
// flag to tell us whether we should read or write
private boolean readToWrite;

// counters to determine which input/output to write to first
private int nextInIndex, nextOutIndex;

// the packet we read/write
private Object packetToBeSent;

private void readFromChannel(InputChannelVector inputChannels)
{
// if there's no input channel, exit right away. Note that this
// is not strictly necessary (the for loop is correct even for
// the case where we have size() == 0.)
if (inputChannels.size() == 0) return;

// try to read from each InputChannel. Stop when either:
// 1. a packet has been successfully read, or
// 2. when we have tried each input channel at least once.
for (int i = 0; i < inputChannels.size() && !readToWrite; i++)
{
try
{
packetToBeSent =
inputChannels.elementAt( nextInIndex ).readObject();
readyToWrite = true;
}
catch(ChannelEmptyException exc) {}
catch(ChannelDisabledException exc2) {}

// Increment the channel we will read from, modulo size()
nextInIndex = (nextInIndex + 1) % inputChannels.size();
}
}

private void writeToChannel(OutputChannelVector outputChannels)
{
// If there's no out channel, exit right away. As above, this
// is not stricly necessary.
if (outputChannels.size() == 0) return;

// try to write to each OutputChannel. Stop when either:
// 1. a packet has been successfully written, or
// 2. when we have tried each out channel at least once.
for (int i = 0; i < outputChannels.size() && readToWrite; i++)
{
try
{
outputChannels.elementAt( nextOutIndex ).writeObject( packetToBeSent );
readyToWrite = false;
}
catch(ChannelFullException exc) {}
catch(ChannelDisabledException exc2) {}

// Increment the channel we will write to, modulo size();
nextOutIndex = ++nextOutIndex % outputChannels.size();
}
}

public void transmitPacket(InputChannelVector inputs,
OutputChannelVector outputs)
{
if (readToWrite) { writeToChannel( outputs ); }
else /* not readToWrite */ { readFromChannel( inputs ); }
}
}

Important Note:

IntermediateNodeBehavior is not necessarily the best, most efficient, or most "correct" implementation of NodeBehavior. Many of you have code that is as effective in moving packets. Your code is correct too. The code we have given you here is one possible solution, and probably could be improved upon. Indeed, some of the things included in our code are unnessary for correctness, but were included to make what was happening more clear. For instance, we do not really need to have the lines:

if (inputChannels.size() == 0) return;
if (outputChannels.size() == 0) return;
becuase the for loop that follows would correctly exit without ever attempting a read/writeObject. So, if you think your implementation is better than ours, let us know and we'll post it to the homepage.

This course is a part of Lynn Andrea Stein's Rethinking CS101 project at the MIT AI Lab and the Department of Electrical Engineering and Computer Science at the Massachusetts Institute of Technology.