cnet v4.0.4 | |
home topology files command‑line options the core API FAQ download and install |
A complete stopandwait Data-Link Layer protocolAt the risk of stealing the thunder of other professors or instructors, we finally present a complete example of the stopandwait Data-Link Layer protocol. No effort is made to describe cnet features that we've seen above, nor how the protocol works - please consult a good undergraduate textbook on data communications and computer networking. The example is imaginatively named stopandwait.c, supported by STOPANDWAIT. This implementation is based on Tanenbaum's `protocol 4', 2nd edition, p227 (or his 3rd edition, p205). This protocol employs only data and acknowledgement frames - piggybacking and negative acknowledgements are not supported. We first define some global types, data structures, and variables for this protocol. It is important to understand that each of these is unique to each of the nodes in the simulation. Although each of the nodes will typically use the same source code file, each node has its own local copy of its variables. It is not possible for one node to modify the variables in another node. The only way for the nodes to communicate is via the Physical Layer.
#include <cnet.h>
#include <stdlib.h>
#include <string.h>
typedef enum { DL_DATA, DL_ACK } FRAMEKIND;
typedef struct {
char data[MAX_MESSAGE_SIZE];
} MSG;
typedef struct {
FRAMEKIND kind; // only ever DL_DATA or DL_ACK
size_t len; // the length of the msg field only
int checksum; // checksum of the whole frame
int seq; // only ever 0 or 1
MSG msg;
} FRAME;
#define FRAME_HEADER_SIZE (sizeof(FRAME) - sizeof(MSG))
#define FRAME_SIZE(f) (FRAME_HEADER_SIZE + f.len)
int ackexpected = 0;
int nextdatatosend = 0;
int dataexpected = 0;
MSG lastmsg;
size_t lastlength = 0;
CnetTimerID lasttimer = NULLTIMER;
We first declare a type,
We next look at the mandatory
EVENT_HANDLER(reboot_node)
{
if(nodeinfo.nodenumber > 1) {
fprintf(stderr,"This is not a 2-node network!\n");
exit(1);
}
CHECK(CNET_set_handler( EV_APPLICATIONREADY, application_ready, 0));
CHECK(CNET_set_handler( EV_PHYSICALREADY, physical_ready, 0));
CHECK(CNET_set_handler( EV_DRAWFRAME, draw_frame, 0));
CHECK(CNET_set_handler( EV_TIMER1, timeouts, 0));
CHECK(CNET_set_handler( EV_DEBUG1, showstate, 0));
CHECK(CNET_set_debug_string( EV_DEBUG1, "State"));
if(nodeinfo.nodenumber == 1)
CNET_enable_application(ALLNODES);
}
EVENT_HANDLER(show_state)
{
printf(
"\tackexpected\t= %d\n\tnextdatatosend\t= %d\n\tdataexpected\t= %d\n",
ackexpected, nextdatatosend, dataexpected);
}
As this file only provides a Data-Link layer protocol,
we first ensure that this is only a network of 2 nodes.
Note that if any node calls the C function
cnet provides the ability for our interior protocols to control
the rate of new messages coming ''down'' from the Application Layer.
We do this by enabling and disabling the Application Layer or,
more particularly,
by enabling and disabling the generation of messages for certain remote hosts.
We need to call
in
The first thing of interest that will occur after each node has rebooted
is that one node's Application Layer will generate and announce a new
message for delivery.
We handle the
EVENT_HANDLER(application_ready)
{
CnetAddr destaddr;
lastlength = sizeof(MSG);
CHECK(CNET_read_application(&destaddr, (char *)&lastmsg, &lastlength));
CNET_disable_application(ALLNODES);
printf("down from application, seq=%d\n",nextdatatosend);
transmit_frame(&lastmsg, DL_DATA, lastlength, nextdatatosend);
nextdatatosend = 1-nextdatatosend;
}
We first determine the maximum sized message that our protocol can handle
and pass that, by reference, to the
Our
void transmit_frame(MSG *msg, FRAMEKIND kind, size_t msglen, int seqno)
{
FRAME f;
f.kind = kind;
f.seq = seqno;
f.checksum = 0;
f.len = msglen;
switch(kind) {
case DL_ACK :
printf("ACK transmitted, seq=%d\n",seqno);
break:
case DL_DATA : {
CnetTime timeout;
memcpy(&f.msg, (char *)msg, (int)msglen);
printf(" DL_DATA transmitted, seq=%d\n",seqno);
timeout = FRAME_SIZE(f)*(CnetTime)8000000 / linkinfo[1].bandwidth) +
linkinfo[1].propagationdelay;
lasttimer = CNET_start_timer(EV_TIMER1, timeout, 0);
break:
}
}
msglen = FRAME_SIZE(f);
f.checksum = CNET_ccitt((unsigned char *)&f, (int)msglen);
CHECK(CNET_write_physical(1, (char *)&f, &msglen));
}
We initialize the header fields of a frame, of type
We finally calculate a checksum of the frame to be transmitted,
embed the value of the checksum in the frame itself(!), and call
Next, we provide the most complex handler of the We next use one of the provided checksum functions to determine if the frame has been corrupted in its travel. Although not demanded by cnet, is is necessary to use the same checksum function on both sender and receiver. We extract the expected checksum, as calculated by the sender, from the frame itself(!) and compare it with the locally calculated value. It is unwise to attempt to make any sense of any of the contents of a corrupted frame. If we detect corruption, we simply ignore the newly arrived frame, confident that it will be retransmitted in the future.
If the frame is an expected acknowledgment, we know that its corresponding
data frame has arrived safely, and so we stop the retransmission timer.
Gloating with pride, we call
If the frame is expected data, we write a copy of the frame's embedded
message (only) to our Application Layer with
EVENT_HANDLER(physical_ready)
{
FRAME f;
size_t len;
int link, checksum;
len = sizeof(FRAME);
CHECK(CNET_read_physical(&link, (char *)&f, &len));
checksum = f.checksum;
f.checksum = 0;
if(CNET_ccitt((unsigned char *)&f, (int)len) != checksum) {
printf("\t\t\t\tBAD checksum - frame ignored\n");
return; // bad checksum, ignore frame
}
switch(f.kind) {
case DL_ACK : {
if(f.seq == ackexpected) {
printf("\t\t\t\tACK received, seq=%d\n", f.seq);
CNET_stop_timer(lasttimer);
ackexpected = 1-ackexpected;
CNET_enable_application(ALLNODES);
}
break:
}
case DL_DATA : {
printf("\t\t\t\tDATA received, seq=%d, ", f.seq);
if(f.seq == dataexpected) {
printf("up to application\n");
len = f.len;
CHECK(CNET_write_application((char *)&f.msg, &len));
dataexpected = 1-dataexpected;
}
else
printf("ignored\n");
transmit_frame((MSG *)NULL, DL_ACK, 0, f.seq);
break:
}
}
}
If the topology file sets either of the
EVENT_HANDLER(timeouts)
{
if(timer == lasttimer) {
printf("timeout, seq=%d\n", ackexpected);
transmit_frame(&lastmsg, DL_DATA, lastlength, ackexpected);
}
}
Done. A real Data-Link Layer protocol providing reliable delivery in the presence of frame corruption and loss. Lessons learntAlthough cnet tries not to dictate how you write your interior protocols, there are obviously a number of common ideas that appear in these example protocols. Some are necessary to enable correct interaction between the cnet event scheduler and your event handlers, but others may simply be considered as good style to both minimize errors and to make your protocols readable by others:
|