NORM-mirror/common/normThreadTest.cpp

469 lines
14 KiB
C++

// This is a test application for experimenting
// with the NORM API implementation during its
// development. A better-documented and complete
// example of the NORM API usage will be provided
// when the NORM API is more complete.
// This tests the use of the NORM API in a multi-threaded app
#include "normApi.h"
#include "protokit.h" // for protolib debug, stuff, etc
#include <stdio.h>
#include <stdlib.h> // for srand()
#ifdef UNIX
#include <unistd.h> // for "sleep()"
#endif // UNIX
class NormThreadApp : public ProtoApp
{
public:
NormThreadApp();
virtual ~NormThreadApp();
// Overrides from ProtoApp or NsProtoSimAgent base
bool OnStartup(int argc, const char*const* argv);
bool ProcessCommands(int argc, const char*const* argv);
void OnShutdown();
bool OnCommand(const char* cmd, const char* val);
private:
enum CmdType {CMD_INVALID, CMD_NOARG, CMD_ARG};
CmdType CommandType(const char* cmd);
static const char* const cmd_list[];
static void DoNormEvent(ProtoDispatcher::Descriptor descriptor,
ProtoDispatcher::Event theEvent,
const void* userData);
void OnNormEvent();
static void DoWorkerEvent(const void* clientData);
void WorkerReadStream();
enum {MSG_SIZE_MAX = 1024};
bool OnTxTimeout(ProtoTimer& timer);
// Our NORM API handles
NormInstanceHandle norm_instance;
NormSessionHandle norm_session;
NormObjectHandle norm_tx_stream;
NormObjectHandle norm_rx_stream;
bool sender;
bool receiver;
// This timer controls our message tx rate
ProtoTimer tx_msg_timer;
char tx_msg_buffer[MSG_SIZE_MAX];
unsigned int tx_msg_length;
unsigned int tx_msg_index;
// We use a ProtoDispatcher for a "worker" thread
// to handle NORM events for a specific session
ProtoDispatcher worker_thread_dispatcher;
}; // end class NormThreadApp
// Our application instance
PROTO_INSTANTIATE_APP(NormThreadApp)
NormThreadApp::NormThreadApp()
: norm_instance(NORM_INSTANCE_INVALID),
norm_session(NORM_SESSION_INVALID),
norm_tx_stream(NORM_OBJECT_INVALID), norm_rx_stream(NORM_OBJECT_INVALID),
tx_msg_length(0), tx_msg_index(0)
{
tx_msg_timer.SetListener(this, &NormThreadApp::OnTxTimeout);
tx_msg_timer.SetInterval(0.00001);
tx_msg_timer.SetRepeat(-1);
worker_thread_dispatcher.SetPromptCallback(DoWorkerEvent, this);
}
NormThreadApp::~NormThreadApp()
{
}
bool NormThreadApp::OnStartup(int argc, const char*const* argv)
{
// 1) (TBD) Process any command-line options
if (!ProcessCommands(argc, argv))
{
TRACE("error with commands\n");
return false;
}
// 2) Create a Norm API instance and "generic" input handler for notifications
norm_instance = NormCreateInstance();
ASSERT(NORM_INSTANCE_INVALID != norm_instance);
SetDebugLevel(3);
// Set a callback that will call NormGetNextEvent()
if (!dispatcher.InstallGenericInput(NormGetDescriptor(norm_instance), DoNormEvent, this))
{
DMSG(0, "NormThreadApp::OnStartup() InstallGenericInput() error\n");
NormDestroyInstance(norm_instance);
return false;
}
// 3) Create a "custom" unique node identifier
// Here's a trick to generate a _hopefully_ unique NormNodeId
// based on an XOR of the system's IP address and the process id.
// (We use ProtoAddress::GetEndIdentifier() to get the local
// "default" IP address for the system)
// (Note that passing "NORM_NODE_ANY" to the last arg of
// NormCreateSession() does a similar thing but without
// the processId XOR ... perhaps we should add the processId
// hack to that default "NORM_NODE_ANY" local NormNodeId picker???
ProtoAddress localAddr;
if (!localAddr.ResolveLocalAddress())
{
fprintf(stderr, "normTest: error resolving local IP address\n");
OnShutdown();
return false;
}
NormNodeId localId = localAddr.EndIdentifier();
#ifdef WIN32
DWORD processId = GetCurrentProcessId();
#else
pid_t processId = getpid();
#endif // if/else WIN32/UNIX
localId ^= (NormNodeId)processId;
// If needed, permutate to a valid, random NormNodeId
while ((NORM_NODE_ANY == localId) ||
(NORM_NODE_NONE == localId))
{
localId ^= (NormNodeId)rand();
}
//localId = 15; // for testing purposes
// 4) Create a NORM session
norm_session = NormCreateSession(norm_instance,
"224.1.1.1",
6001,
localId);
ASSERT(NORM_SESSION_INVALID != norm_session);
if(!NormSetMulticastInterface(norm_session,"eth0"))
{
fprintf(stderr, "normTest: Unable to set multicast interface to \"eth0\"\n");
//return false;
}
NormSetGrttEstimate(norm_session, 0.250); // 1 msec initial grtt
NormSetTransmitRate(norm_session, 80.0e+06); // in bits/second
NormSetTransmitRateBounds(norm_session, 10.0e+06, 10.0e+06);
NormSetCongestionControl(norm_session, true);
NormSetTxLoss(norm_session, 2.0);
//NormSetMessageTrace(norm_session, true);
//NormSetLoopback(norm_session, true);
// 5) If sender pick a random "session id", start sender, and create a stream
// We use a random "sessionId"
if (sender)
{
NormSessionId sessionId = (NormSessionId)rand();
//NormStartSender(norm_session, sessionId, 1024*1024, 1400, 64, 8);
NormStartSender(norm_session, sessionId, 2000000, 1400, 64, 8);
norm_tx_stream = NormStreamOpen(norm_session, 1800000);
// Activate tx timer and force first message transmission
ActivateTimer(tx_msg_timer);
OnTxTimeout(tx_msg_timer);
}
// 6) If receiver, start receiver
if (receiver)
{
worker_thread_dispatcher.StartThread();
NormStartReceiver(norm_session, 2000000);
}
return true;
} // end NormThreadApp::OnStartup()
void NormThreadApp::OnShutdown()
{
if (tx_msg_timer.IsActive()) tx_msg_timer.Deactivate();
if (NORM_INSTANCE_INVALID != norm_instance)
{
dispatcher.RemoveGenericInput(NormGetDescriptor(norm_instance));
NormDestroyInstance(norm_instance);
norm_instance = NORM_INSTANCE_INVALID;
}
worker_thread_dispatcher.Stop();
} // end NormThreadApp::OnShutdown()
bool NormThreadApp::OnTxTimeout(ProtoTimer& /*timer*/)
{
//TRACE("enter NormThreadApp::OnTxTimeout() ...\n");
while (1)
{
if (0 == tx_msg_length)
{
// Send a new message
unsigned int sendCount = 1;
sprintf(tx_msg_buffer, "normThreadTest says hello %u ", sendCount);
unsigned int msgLength = strlen(tx_msg_buffer);
memset(tx_msg_buffer + msgLength, 'a', 900 - msgLength);
tx_msg_length = 900;
tx_msg_index = 0;
}
unsigned int bytesHave = tx_msg_length - tx_msg_index;
unsigned int bytesWritten =
NormStreamWrite(norm_tx_stream, tx_msg_buffer + tx_msg_index, tx_msg_length - tx_msg_index);
//TRACE("wrote %u bytes to stream ...\n", bytesWritten);
if (bytesWritten == bytesHave)
{
tx_msg_length = 0;
}
else
{
if (0 == bytesWritten)
{
TRACE("ZERO bytes written\n");
}
// We filled the stream buffer
tx_msg_index += bytesWritten;
if (tx_msg_timer.IsActive()) tx_msg_timer.Deactivate();
break;
}
}
return true;
} // end NormThreadApp::OnTxTimeout()
void NormThreadApp::DoNormEvent(ProtoDispatcher::Descriptor descriptor,
ProtoDispatcher::Event theEvent,
const void* userData)
{
NormThreadApp* theApp = reinterpret_cast<NormThreadApp*>((void*)userData);
theApp->OnNormEvent();
} // end NormThreadApp::DoNormEvent()
void NormThreadApp::OnNormEvent()
{
static unsigned long updateCount = 0;
NormEvent theEvent;
if (NormGetNextEvent(norm_instance, &theEvent))
{
switch (theEvent.type)
{
case NORM_TX_QUEUE_EMPTY:
case NORM_TX_QUEUE_VACANCY:
/*if (NORM_TX_QUEUE_VACANCY == theEvent.type)
TRACE("NORM_TX_QUEUE_VACANCY ...\n");
else
TRACE("NORM_TX_QUEUE_EMPTY ...\n");*/
if (!tx_msg_timer.IsActive())
{
//ActivateTimer(tx_msg_timer);
OnTxTimeout(tx_msg_timer);
}
break;
case NORM_GRTT_UPDATED:
break;
case NORM_CC_ACTIVE:
TRACE("NORM_CC_ACTIVE ...\n");
break;
case NORM_CC_INACTIVE:
TRACE("NORM_CC_INACTIVE ...\n");
break;
case NORM_REMOTE_SENDER_NEW:
case NORM_REMOTE_SENDER_ACTIVE:
break;
case NORM_REMOTE_SENDER_INACTIVE:
NormNodeFreeBuffers(theEvent.sender);
break;
case NORM_RX_OBJECT_NEW:
TRACE("NORM_RX_OBJECT_NEW ...\n");
norm_rx_stream = theEvent.object;
break;
case NORM_RX_OBJECT_UPDATED:
//TRACE("NORM_RX_OBJECT_UPDATED ...\n");
updateCount++;
if ((updateCount % 1000) == 0)
TRACE("updateCount:%lu\n", updateCount);
worker_thread_dispatcher.PromptThread();
break;
case NORM_RX_OBJECT_ABORTED:
TRACE("NORM_RX_OBJECT_ABORTED ...\n");
break;
default:
TRACE("UNHANDLED NORM EVENT : %d...\n", theEvent.type);
break;
}
}
else
{
DMSG(0, "NormThreadApp::OnNormEvent() NormGetNextEvent() error?\n");
}
} // end NormThreadApp::OnNormEvent()
void NormThreadApp::DoWorkerEvent(const void* clientData)
{
NormThreadApp* theApp = (NormThreadApp*)clientData;
theApp->WorkerReadStream();
} // end NormThreadApp::DoWorkerEvent()
void NormThreadApp::WorkerReadStream()
{
unsigned int loopCount = 0;
static unsigned long readCount = 0;
char rxBuffer[2048];
unsigned int bytesRead = 1400;
do
{
if (NormStreamRead(norm_rx_stream, rxBuffer, &bytesRead))
{
//TRACE("read %u bytes from stream ...\n", bytesRead);
if (0 != bytesRead)
{
readCount++;
if ((readCount % 1000) == 0)
TRACE("readCount:%lu\n", readCount);
bytesRead = 1400;
}
}
else
{
TRACE("NormThreadApp::WorkerReadStream() stream broken!\n");
bytesRead = 0;
}
loopCount++;
} while (0 != bytesRead);
//TRACE("loopCount: %lu\n", loopCount);
} // end NormThreadApp::WorkerReadStream()
const char* const NormThreadApp::cmd_list[] =
{
"+debug", // debug level
"-send",
"-recv",
NULL
}; // end NormThreadApp::cmd_list[]
bool NormThreadApp::OnCommand(const char* cmd, const char* val)
{
size_t cmdlen = strlen(cmd);
if (!strncmp("debug", cmd, cmdlen))
{
}
else if (!strncmp("send", cmd, cmdlen))
{
sender = true;
}
else if (!strncmp("recv", cmd, cmdlen))
{
receiver = true;
}
else if (!strncmp("debug", cmd, cmdlen))
{
DMSG(0, "NormThreadApp::OnCommand(%s) unknown command\n", cmd);
return false;
}
return true;
} // end NormThreadApp::ProcessCommands()
NormThreadApp::CmdType NormThreadApp::CommandType(const char* cmd)
{
if (!cmd) return CMD_INVALID;
size_t len = strlen(cmd);
bool matched = false;
CmdType type = CMD_INVALID;
const char* const* nextCmd = cmd_list;
while (*nextCmd)
{
if (!strncmp(cmd, *nextCmd+1, len))
{
if (matched)
{
// ambiguous command (command should match only once)
return CMD_INVALID;
}
else
{
matched = true;
if ('+' == *nextCmd[0])
type = CMD_ARG;
else
type = CMD_NOARG;
}
}
nextCmd++;
}
return type;
} // end NormThreadApp::CommandType()
bool NormThreadApp::ProcessCommands(int argc, const char*const* argv)
{
int i = 1;
while ( i < argc)
{
CmdType cmdType = CommandType(argv[i]);
switch (cmdType)
{
case CMD_INVALID:
DMSG(0, "NormThreadApp::ProcessCommands() Invalid command:%s\n",
argv[i]);
return false;
case CMD_NOARG:
if (!OnCommand(argv[i], NULL))
{
DMSG(0, "NormThreadApp::ProcessCommands() OnCommand(%s) error\n",
argv[i]);
return false;
}
i++;
break;
case CMD_ARG:
if (!OnCommand(argv[i], argv[i+1]))
{
DMSG(0, "NormThreadApp::ProcessCommands() OnCommand(%s, %s) error\n",
argv[i], argv[i+1]);
return false;
}
i += 2;
break;
}
}
return true;
} // end NormThreadApp::ProcessCommands()