// 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 #include // for srand() #ifdef UNIX #include // 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.0015); 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(2); // 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); NormSetGrttEstimate(norm_session, 0.250); // 1 msec initial grtt NormSetTransmitRate(norm_session, 10.0e+06); // in bits/second //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, 1024*1024, 1400, 64, 0); norm_tx_stream = NormStreamOpen(norm_session, 1024*1024); // Activate tx timer and force first message transmission ActivateTimer(tx_msg_timer); OnTxTimeout(tx_msg_timer); } // 6) If receiver, start receiver if (receiver) { NormStartReceiver(norm_session, 1024*1024); worker_thread_dispatcher.StartThread(); } 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"); 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 { // We filled the stream buffer tx_msg_index += bytesWritten; tx_msg_timer.Deactivate(); } return true; } // end NormThreadApp::OnTxTimeout() void NormThreadApp::DoNormEvent(ProtoDispatcher::Descriptor descriptor, ProtoDispatcher::Event theEvent, const void* userData) { NormThreadApp* theApp = reinterpret_cast((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_REMOTE_SENDER_NEW: case NORM_REMOTE_SENDER_ACTIVE: 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 % 100) == 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 % 100) == 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()