Esempio n. 1
0
static status_t ReadIncomingData(const String & desc, DataIO & readIO, const SocketMultiplexer & multiplexer, Queue<ByteBufferRef> & outQ)
{
   if (multiplexer.IsSocketReadyForRead(readIO.GetReadSelectSocket().GetFileDescriptor()))
   {
      uint8 buf[4096];
      int32 ret = readIO.Read(buf, sizeof(buf));
      if (ret > 0) 
      {
         LogTime(MUSCLE_LOG_TRACE, "Read " INT32_FORMAT_SPEC " bytes from %s:\n", ret, desc());
         LogHexBytes(MUSCLE_LOG_TRACE, buf, ret);
     
         ByteBufferRef toNetworkBuf = GetByteBufferFromPool(ret, buf);
         if (toNetworkBuf()) (void) outQ.AddTail(toNetworkBuf); 
      }
      else if (ret < 0) {LogTime(MUSCLE_LOG_ERROR, "Error, readIO.Read() returned %i\n", ret); return B_ERROR;}
   }
   return B_NO_ERROR;
}
Esempio n. 2
0
// This program is equivalent to the portableplaintext client, except
// that we communicate with a child process instead of a socket.
int main(int argc, char ** argv)
{
   CompleteSetupSystem css;

   if (argc < 3) PrintUsageAndExit();

   const uint32 numProcesses = atol(argv[1]);
   if (numProcesses == 0) PrintUsageAndExit();

   const char * cmd = argv[2];

   Hashtable<String,String> testEnvVars;
   (void) testEnvVars.Put("Peanut Butter", "Jelly");
   (void) testEnvVars.Put("Jelly", "Peanut Butter");
   (void) testEnvVars.Put("Oranges", "Grapes");

   Queue<DataIORef> refs;
   for (uint32 i=0; i<numProcesses; i++)
   {
      ChildProcessDataIO * dio = new ChildProcessDataIO(false);
      refs.AddTail(DataIORef(dio));
      printf("About To Launch child process #" UINT32_FORMAT_SPEC ":  [%s]\n", i+1, cmd); fflush(stdout);
      ConstSocketRef s = (dio->LaunchChildProcess(argc-2, ((const char **) argv)+2, ChildProcessLaunchFlags(MUSCLE_DEFAULT_CHILD_PROCESS_LAUNCH_FLAGS), NULL, &testEnvVars) == B_NO_ERROR) ? dio->GetReadSelectSocket() : ConstSocketRef();
      printf("Finished Launching child process #" UINT32_FORMAT_SPEC ":  [%s]\n", i+1, cmd); fflush(stdout);
      if (s() == NULL)
      {
         LogTime(MUSCLE_LOG_CRITICALERROR, "Error launching child process #" UINT32_FORMAT_SPEC " [%s]!\n", i+1, cmd);
         return 10;
      }
   }

   StdinDataIO stdinIO(false);
   PlainTextMessageIOGateway stdinGateway;
   QueueGatewayMessageReceiver stdinInputQueue;
   stdinGateway.SetDataIO(DataIORef(&stdinIO, false)); 

   SocketMultiplexer multiplexer;

   for (uint32 i=0; i<refs.GetNumItems(); i++)
   {
      printf("------------ CHILD PROCESS #" UINT32_FORMAT_SPEC " ------------------\n", i+1);
      PlainTextMessageIOGateway ioGateway;
      ioGateway.SetDataIO(refs[i]);
      ConstSocketRef readSock = refs[i]()->GetReadSelectSocket();
      QueueGatewayMessageReceiver ioInputQueue;
      while(1)
      {
         int readFD = readSock.GetFileDescriptor();
         multiplexer.RegisterSocketForReadReady(readFD);

         const int writeFD = ioGateway.HasBytesToOutput() ? refs[i]()->GetWriteSelectSocket().GetFileDescriptor() : -1;
         if (writeFD >= 0) multiplexer.RegisterSocketForWriteReady(writeFD);

         const int stdinFD = stdinIO.GetReadSelectSocket().GetFileDescriptor();
         multiplexer.RegisterSocketForReadReady(stdinFD);

         if (multiplexer.WaitForEvents() < 0) printf("testchildprocess: WaitForEvents() failed!\n");

         // First, deliver any lines of text from stdin to the child process
         if ((multiplexer.IsSocketReadyForRead(stdinFD))&&(stdinGateway.DoInput(ioGateway) < 0))
         {
            printf("Error reading from stdin, aborting!\n");
            break;
         }

         const bool reading    = multiplexer.IsSocketReadyForRead(readFD);
         const bool writing    = ((writeFD >= 0)&&(multiplexer.IsSocketReadyForWrite(writeFD)));
         const bool writeError = ((writing)&&(ioGateway.DoOutput() < 0));
         const bool readError  = ((reading)&&(ioGateway.DoInput(ioInputQueue) < 0));
         if ((readError)||(writeError))
         {
            printf("Connection closed, exiting.\n");
            break;
         }

         MessageRef incoming;
         while(ioInputQueue.RemoveHead(incoming) == B_NO_ERROR)
         {
            printf("Heard message from server:-----------------------------------\n");
            const char * inStr;
            for (int i=0; (incoming()->FindString(PR_NAME_TEXT_LINE, i, &inStr) == B_NO_ERROR); i++) printf("Line %i: [%s]\n", i, inStr);
           
            printf("-------------------------------------------------------------\n");
         }

         if ((reading == false)&&(writing == false)) break;

         multiplexer.RegisterSocketForReadReady(readFD);
         if (ioGateway.HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(writeFD);
      }

      if (ioGateway.HasBytesToOutput())
      {
         printf("Waiting for all pending messages to be sent...\n");
         while((ioGateway.HasBytesToOutput())&&(ioGateway.DoOutput() >= 0)) {printf ("."); fflush(stdout);}
      }
   }
   printf("\n\nBye!\n");
   return 0;
}
Esempio n. 3
0
// This is a text based test client for the muscled server.  It is useful for testing
// the server, and could possibly be useful for other things, I don't know.
int main(int argc, char ** argv)
{
   CompleteSetupSystem css;

   String hostName;
   uint16 port = 2960;
   if (argc > 1) ParseConnectArg(argv[1], hostName, port, false);
   ConstSocketRef sock = Connect(hostName(), port, "portablereflectclient", false);
   if (sock() == NULL) return 10;

   // We'll receive plain text over stdin
   StdinDataIO stdinIO(false);
   PlainTextMessageIOGateway stdinGateway; 
   stdinGateway.SetDataIO(DataIORef(&stdinIO, false));

   // And send and receive flattened Message objects over our TCP socket
   TCPSocketDataIO tcpIO(sock, false);
   MessageIOGateway tcpGateway;
   tcpGateway.SetDataIO(DataIORef(&tcpIO, false));

   DataIORef networkIORef(&tcpIO, false);
   AbstractMessageIOGatewayRef gatewayRef(&tcpGateway, false);

#ifdef MUSCLE_ENABLE_SSL
   for (int i=1; i<argc; i++)
   {
      const char * a = argv[i];
      if (strncmp(a, "publickey=", 10) == 0)
      { 
         a += 10;  // skip past the 'publickey=' part
         SSLSocketDataIO * sslIO = new SSLSocketDataIO(sock, false, false);
         DataIORef sslIORef(sslIO);
         if (sslIO->SetPublicKeyCertificate(a) == B_NO_ERROR) 
         {
            LogTime(MUSCLE_LOG_INFO, "Using public key certificate file [%s] to connect to server\n", a);
            networkIORef = sslIORef;
            gatewayRef.SetRef(new SSLSocketAdapterGateway(gatewayRef));
            gatewayRef()->SetDataIO(networkIORef);
         }
         else
         {
            LogTime(MUSCLE_LOG_CRITICALERROR, "Couldn't load public key certificate file [%s] (file not found?)\n", a);
            return 10;
         }
      }
   }
#endif

   SocketMultiplexer multiplexer;
   QueueGatewayMessageReceiver stdinInQueue, tcpInQueue;
   bool keepGoing = true;
   uint64 nextTimeoutTime = MUSCLE_TIME_NEVER;
   while(keepGoing)
   {
      const int stdinFD       = stdinIO.GetReadSelectSocket().GetFileDescriptor();
      const int socketReadFD  = networkIORef()->GetReadSelectSocket().GetFileDescriptor();
      const int socketWriteFD = networkIORef()->GetWriteSelectSocket().GetFileDescriptor();

      multiplexer.RegisterSocketForReadReady(stdinFD);
      multiplexer.RegisterSocketForReadReady(socketReadFD);
      if (gatewayRef()->HasBytesToOutput()) multiplexer.RegisterSocketForWriteReady(socketWriteFD);
      if (multiplexer.WaitForEvents(nextTimeoutTime) < 0) printf("portablereflectclient: WaitForEvents() failed!\n");

      const uint64 now = GetRunTime64();
      if (now >= nextTimeoutTime)
      {
         // For OpenSSL testing:  Generate some traffic to the server every 50mS
         printf("Uploading timed OpenSSL-tester update at time " UINT64_FORMAT_SPEC "\n", now);
         MessageRef stateMsg = GetMessageFromPool();
         stateMsg()->AddString("username", "portablereflectclient");
         stateMsg()->AddPoint("position", Point((rand()%100)/100.0f, (rand()%100)/100.0f));
         stateMsg()->AddInt32("color", -1);

         MessageRef uploadMsg = GetMessageFromPool(PR_COMMAND_SETDATA);
         uploadMsg()->AddMessage("qt_example/state", stateMsg);
         gatewayRef()->AddOutgoingMessage(uploadMsg);

         nextTimeoutTime = now + MillisToMicros(50);
      }

      // Receive data from stdin
      if (multiplexer.IsSocketReadyForRead(stdinFD))
      {
         while(1)
         {
            const int32 bytesRead = stdinGateway.DoInput(stdinInQueue);
            if (bytesRead < 0)
            {
               printf("Stdin closed, exiting!\n");
               keepGoing = false;
               break;
            }
            else if (bytesRead == 0) break;  // no more to read
         }
      }

      // Handle any input lines that were received from stdin
      MessageRef msgFromStdin;
      while(stdinInQueue.RemoveHead(msgFromStdin) == B_NO_ERROR)
      {
         const String * st;
         for (int32 i=0; msgFromStdin()->FindString(PR_NAME_TEXT_LINE, i, &st) == B_NO_ERROR; i++)
         {
            const char * text = st->Cstr();

            printf("You typed: [%s]\n", text);
            bool send = true;
            MessageRef ref = GetMessageFromPool();

            const char * arg1 = (st->Length()>2) ? &text[2] : NULL;
            switch(text[0])
            {
               case 'm':
                  ref()->what = MAKETYPE("umsg");
                  if (arg1) ref()->AddString(PR_NAME_KEYS, arg1);
                  ref()->AddString("info", "This is a user message");
               break;

               case 'i':
                  ref()->what = PR_COMMAND_PING;
                  ref()->AddString("Test ping", "yeah");
               break;

               case 's':
               {
                  ref()->what = PR_COMMAND_SETDATA;
                  MessageRef uploadMsg = GetMessageFromPool(MAKETYPE("HELO"));
                  uploadMsg()->AddString("This node was posted at: ", GetHumanReadableTimeString(GetRunTime64()));
                  if (arg1) ref()->AddMessage(arg1, uploadMsg);
               }
               break;

               case 'k':
                  ref()->what = PR_COMMAND_KICK;
                  if (arg1) ref()->AddString(PR_NAME_KEYS, arg1);
               break;

               case 'b':
                  ref()->what = PR_COMMAND_ADDBANS;
                  if (arg1) ref()->AddString(PR_NAME_KEYS, arg1);
               break;

               case 'B':
                  ref()->what = PR_COMMAND_REMOVEBANS;
                  if (arg1) ref()->AddString(PR_NAME_KEYS, arg1);
               break;

               case 'g':
                  ref()->what = PR_COMMAND_GETDATA;
                  if (arg1) ref()->AddString(PR_NAME_KEYS, arg1);
               break;

               case 'G':
                  ref()->what = PR_COMMAND_GETDATATREES;
                  if (arg1) ref()->AddString(PR_NAME_KEYS, arg1);
                  ref()->AddString(PR_NAME_TREE_REQUEST_ID, "Tree ID!");
               break;

               case 'q':
                  keepGoing = send = false;
               break;

               case 'p':
                  ref()->what = PR_COMMAND_SETPARAMETERS;
                  if (arg1) ref()->AddString(arg1, "");
               break;

               case 'P':
                  ref()->what = PR_COMMAND_GETPARAMETERS;
               break;

               case 'L':
               {
                  // simulate the behavior of qt_example, for testing OpenSSL problem
                  ref()->what = PR_COMMAND_SETPARAMETERS;
                  ref()->AddBool("SUBSCRIBE:qt_example/state", true);
                  printf("Starting OpenSSL problem test...\n");
                  nextTimeoutTime = 0;
               }
               break;
 
               case 'x':
               {
                  ref()->what = PR_COMMAND_SETPARAMETERS;
                  StringQueryFilter sqf("sc_tstr", StringQueryFilter::OP_SIMPLE_WILDCARD_MATCH, "*Output*");
                  ref()->AddArchiveMessage("SUBSCRIBE:/*/*/csproj/default/subcues/*", sqf);
               }
               break;

               case 'd':
                  ref()->what = PR_COMMAND_REMOVEDATA;
                  if (arg1) ref()->AddString(PR_NAME_KEYS, arg1);
               break;

               case 'D':
                  ref()->what = PR_COMMAND_REMOVEPARAMETERS;
                  if (arg1) ref()->AddString(PR_NAME_KEYS, arg1);
               break;

               case 't':
               {
                  // test all data types
                  ref()->what = 1234;
                  ref()->AddString("String", "this is a string");
                  ref()->AddInt8("Int8", 123);
                  ref()->AddInt8("-Int8", -123);
                  ref()->AddInt16("Int16", 1234);
                  ref()->AddInt16("-Int16", -1234);
                  ref()->AddInt32("Int32", 12345);
                  ref()->AddInt32("-Int32", -12345);
                  ref()->AddInt64("Int64", 123456789);
                  ref()->AddInt64("-Int64", -123456789);
                  ref()->AddBool("Bool", true);
                  ref()->AddBool("-Bool", false);
                  ref()->AddFloat("Float", 1234.56789f);
                  ref()->AddFloat("-Float", -1234.56789f);
                  ref()->AddDouble("Double", 1234.56789);
                  ref()->AddDouble("-Double", -1234.56789);
                  ref()->AddPointer("Pointer", ref());
                  ref()->AddFlat("Flat", *ref());
                  char data[] = "This is some data";
                  ref()->AddData("Flat", B_RAW_TYPE, data, sizeof(data));
               }
               break;

               default:
                  printf("Sorry, wot?\n");
                  send = false;
               break;
            }

            if (send) 
            {
               printf("Sending message...\n");
               ref()->PrintToStream();
               gatewayRef()->AddOutgoingMessage(ref);
            }
         }
      }

      // Handle input and output on the TCP socket
      const bool reading = multiplexer.IsSocketReadyForRead(socketReadFD);
      const bool writing = multiplexer.IsSocketReadyForWrite(socketWriteFD);
      const bool writeError = ((writing)&&(gatewayRef()->DoOutput() < 0));
      const bool readError  = ((reading)&&(gatewayRef()->DoInput(tcpInQueue) < 0));
      if ((readError)||(writeError))
      {
         printf("Connection closed (%s), exiting.\n", writeError?"Write Error":"Read Error");
         keepGoing = false;
      }

      MessageRef msgFromTCP;
      while(tcpInQueue.RemoveHead(msgFromTCP) == B_NO_ERROR)
      {
         printf("Heard message from server:-----------------------------------\n");
         msgFromTCP()->PrintToStream();
         printf("-------------------------------------------------------------\n");
      }
   }

   if (gatewayRef()->HasBytesToOutput())
   {
      printf("Waiting for all pending messages to be sent...\n");
      while((gatewayRef()->HasBytesToOutput())&&(gatewayRef()->DoOutput() >= 0)) {printf ("."); fflush(stdout);}
   }
   printf("\n\nBye!\n");

   return 0;
}
int main(int argc, char ** argv)
{
   CompleteSetupSystem css;

   PrintExampleDescription();

   // Let's enable a bit of debug-output, just to see what the client is doing
   SetConsoleLogLevel(MUSCLE_LOG_DEBUG);

   MessageTransceiverThread mtt;
   if (mtt.StartInternalThread() != B_NO_ERROR)
   {
      LogTime(MUSCLE_LOG_CRITICALERROR, "Couldn't start the MessageTransceiverThread, aborting!\n");
      return 10;
   }

   if (mtt.AddNewConnectSession(localhostIP, SMART_SERVER_TCP_PORT, SecondsToMicros(1)) != B_NO_ERROR)
   {
      LogTime(MUSCLE_LOG_CRITICALERROR, "mtt.AddNewConnectSession() failed, aborting!\n");
      mtt.ShutdownInternalThread();
      return 10;
   }

   LogTime(MUSCLE_LOG_INFO, "This program is designed to be run in conjunction with example_4_smart_server\n");
   LogTime(MUSCLE_LOG_INFO, "You'll probably want to run multiple instances of this client at the same time, also.\n");
   printf("\n");
   PrintHelp();

   printf("\n");
   LogTime(MUSCLE_LOG_INFO, "Some example commands that you can enter:\n");
   LogTime(MUSCLE_LOG_INFO, "   subscribe /*/*       -> will set up a subscription that always lets you know who is connected\n");
   LogTime(MUSCLE_LOG_INFO, "   subscribe /*/*/*     -> will set up a subscription that always lets you know who set/deleted/updated a node\n");
   LogTime(MUSCLE_LOG_INFO, "   subscribe *          -> is the same as the previous command (the initial wildcards can be implicit)\n");
   LogTime(MUSCLE_LOG_INFO, "   set frood = groovy   -> create a node named 'frood' in your session-folder, with the word 'groovy' in its Message\n");
   LogTime(MUSCLE_LOG_INFO, "   delete frood         -> delete the node named 'frood' in your session-folder\n");
   LogTime(MUSCLE_LOG_INFO, "   delete f*            -> delete all nodes in your session-folder whose names start with f\n");
   LogTime(MUSCLE_LOG_INFO, "   delete *             -> delete all nodes in your session-folder\n");
   LogTime(MUSCLE_LOG_INFO, "   msg /*/* hello       -> say hello to everyone who is connected\n");
   LogTime(MUSCLE_LOG_INFO, "   msg /*/*/frood hello -> say hello to everyone who is connected and created a node named \'frood\' in their session-folder\n");
   LogTime(MUSCLE_LOG_INFO, "   die                  -> cause the client process to exit\n");
   printf("\n");

   // Run our own event loop to read from stdin and retrieve
   // feedback events from the MessageTransceiverThread.
   // (In other contexts this might be a QEventLoop or
   // a Win32 event loop or an SDL event loop or etc; anything
   // where the main thread needs to be doing some non-MUSCLE
   // event loop is a good use case for QMessageTransceiverThread)

   StdinDataIO stdinIO(false);
   SocketMultiplexer sm;
   while(true)
   {
      sm.RegisterSocketForReadReady(stdinIO.GetReadSelectSocket().GetFileDescriptor());
      sm.RegisterSocketForReadReady(mtt.GetOwnerWakeupSocket().GetFileDescriptor());

      sm.WaitForEvents();

      if (sm.IsSocketReadyForRead(stdinIO.GetReadSelectSocket().GetFileDescriptor()))
      {
         // Handle stdin input, and send a Message to the MessageTransceiverThread
         // for it to send on to the server, if appropriate
         uint8 inputBuf[1024];
         const int numBytesRead = stdinIO.Read(inputBuf, sizeof(inputBuf)-1);
         if (numBytesRead > 0)
         {
            String inputCmd((const char *) inputBuf, numBytesRead);
            inputCmd = inputCmd.Trim();
            if (inputCmd == "die") break;

            MessageRef msgToSend = ParseStdinCommand(inputCmd);
            if (msgToSend())
            {
               printf("Calling mtt.SendMessageToSessions() with the following Message:\n");
               msgToSend()->PrintToStream();
              (void) mtt.SendMessageToSessions(msgToSend);
            }
         }
         else if (numBytesRead < 0) break;
      }

      if (sm.IsSocketReadyForRead(mtt.GetOwnerWakeupSocket().GetFileDescriptor()))
      {
         // Handle any feedback events sent back to us from the MessageTransceiverThread
         uint32 code;
         MessageRef ref;
         String session;
         uint32 factoryID;
         IPAddressAndPort location;
         while(mtt.GetNextEventFromInternalThread(code, &ref, &session, &factoryID, &location) >= 0)
         {
            String codeStr;
            switch(code)
            {
               case MTT_EVENT_INCOMING_MESSAGE:      codeStr = "IncomingMessage";     break;
               case MTT_EVENT_SESSION_ACCEPTED:      codeStr = "SessionAccepted";     break;
               case MTT_EVENT_SESSION_ATTACHED:      codeStr = "SessionAttached";     break;
               case MTT_EVENT_SESSION_CONNECTED:     codeStr = "SessionConnected";    break;
               case MTT_EVENT_SESSION_DISCONNECTED:  codeStr = "SessionDisconnected"; break;
               case MTT_EVENT_SESSION_DETACHED:      codeStr = "SessionDetached";     break;
               case MTT_EVENT_FACTORY_ATTACHED:      codeStr = "FactoryAttached";     break;
               case MTT_EVENT_FACTORY_DETACHED:      codeStr = "FactoryDetached";     break;
               case MTT_EVENT_OUTPUT_QUEUES_DRAINED: codeStr = "OutputQueuesDrained"; break;
               case MTT_EVENT_SERVER_EXITED:         codeStr = "ServerExited";        break;
               default:                              codeStr = String("\'%1\'").Arg(GetTypeCodeString(code)); break;
            }
            printf("Event from MTT:  type=[%s], session=[%s] factoryID=[" UINT32_FORMAT_SPEC "] location=[%s]\n", codeStr(), session(), factoryID, location.ToString()());
            if (ref()) ref()->PrintToStream();
         }
      }
   }

   mtt.ShutdownInternalThread();

   return 0;
}
Esempio n. 5
0
void HandleSession(const ConstSocketRef & sock, bool myTurnToThrow, bool doFlush)
{
   LogTime(MUSCLE_LOG_ERROR, "Beginning catch session (%s)\n", doFlush?"flush enabled":"flush disabled");

   TCPSocketDataIO sockIO(sock, false);
   uint64 lastThrowTime = 0;
   uint8 ball = 'B';  // this is what we throw back and forth over the TCP socket!
   uint64 min=((uint64)-1), max=0;
   uint64 lastPrintTime = 0;
   uint64 count = 0;
   uint64 total = 0;
   SocketMultiplexer multiplexer;
   while(1)
   {
      int fd = sock.GetFileDescriptor();
      multiplexer.RegisterSocketForReadReady(fd);
      if (myTurnToThrow) multiplexer.RegisterSocketForWriteReady(fd);

      if (multiplexer.WaitForEvents() < 0)
      {
         LogTime(MUSCLE_LOG_ERROR, "WaitForEvents() failed, aborting!\n");
         break;
      }

      if ((myTurnToThrow)&&(multiplexer.IsSocketReadyForWrite(fd)))
      {
         int32 bytesWritten = sockIO.Write(&ball, sizeof(ball));
         if (bytesWritten == sizeof(ball))
         {
            if (doFlush) sockIO.FlushOutput();   // nagle's algorithm gets toggled here!
            lastThrowTime = GetRunTime64();
            myTurnToThrow = false;  // we thew the ball, now wait to catch it again!
         }
         else if (bytesWritten < 0)
         {
            LogTime(MUSCLE_LOG_ERROR, "Error sending ball, aborting!\n");
            break;
         }
      }

      if (multiplexer.IsSocketReadyForRead(fd))
      {
         int32 bytesRead = sockIO.Read(&ball, sizeof(ball));
         if (bytesRead == sizeof(ball))
         {
            if (myTurnToThrow == false)
            {
               if (lastThrowTime > 0)
               {
                  uint64 elapsedTime = GetRunTime64() - lastThrowTime;
                  count++;
                  total += elapsedTime;
                  min = muscleMin(min, elapsedTime);
                  max = muscleMax(max, elapsedTime);
                  if (OnceEvery(MICROS_PER_SECOND, lastPrintTime)) LogTime(MUSCLE_LOG_INFO, "count=" UINT64_FORMAT_SPEC" min=" UINT64_FORMAT_SPEC "us max=" UINT64_FORMAT_SPEC "us avg=" UINT64_FORMAT_SPEC "us\n", count, min, max, total/count);
               }
               myTurnToThrow = true;  // we caught the ball, now throw it back!
            }
         }
         else if (bytesRead < 0)
         {
            LogTime(MUSCLE_LOG_ERROR, "Error reading ball, aborting!\n");
            break;
         }
      }
   }
}
// This program tests the SocketMultiplexer class by seeing how many chained socket-pairs
// we can chain a message through sequentially
int main(int argc, char ** argv)
{
   CompleteSetupSystem css;

   uint32 numPairs = 5;
   if (argc > 1) numPairs = atoi(argv[1]);

   bool quiet = false;
   if ((argc > 2)&&(strcmp(argv[2], "quiet") == 0)) quiet = true;

#ifdef __APPLE__
   // Tell MacOS/X that yes, we really do want to create this many file descriptors
   struct rlimit rl;
   rl.rlim_cur = rl.rlim_max = (numPairs*2)+5;
   if (setrlimit(RLIMIT_NOFILE, &rl) != 0) perror("setrlimit");
#endif

   printf("Testing %i socket-pairs chained together...\n", numPairs);

   Queue<ConstSocketRef> senders;   (void) senders.EnsureSize(numPairs, true);
   Queue<ConstSocketRef> receivers; (void) receivers.EnsureSize(numPairs, true);
   
   for (uint32 i=0; i<numPairs; i++) 
   {
      if (CreateConnectedSocketPair(senders[i], receivers[i]) != B_NO_ERROR)
      {
         printf("Error, failed to create socket pair #" UINT32_FORMAT_SPEC "!\n", i);
         return 10;
      }
   }

   // Start the game off
   char c = 'C';
   if (SendData(senders[0], &c, 1, false) != 1)
   {
      printf("Error, couldn't send initial byte!\n");
      return 10;
   }

   uint64 count = 0;
   uint64 tally = 0;
   uint64 minRunTime = (uint64)-1;
   uint64 maxRunTime = 0;
   SocketMultiplexer multiplexer;
   uint64 endTime = GetRunTime64() + SecondsToMicros(10);
   bool error = false;
   while(error==false)
   {
      for (uint32 i=0; i<numPairs; i++)
      {
         if (multiplexer.RegisterSocketForReadReady(receivers[i].GetFileDescriptor()) != B_NO_ERROR)
         {
            printf("Error, RegisterSocketForRead() failed for receiver #" UINT32_FORMAT_SPEC "!\n", i);
            error = true;
            break;
         }
      }
      if (error) break;

      uint64 then = GetRunTime64();
      if (then >= endTime) break;

      int ret = multiplexer.WaitForEvents();
      if (ret < 0)
      {
         printf("WaitForEvents errored out, aborting test!\n"); 
         break;
      }

      uint64 elapsed = GetRunTime64()-then; 
      if (quiet == false) printf("WaitForEvents returned %i after " UINT64_FORMAT_SPEC " microseconds.\n", ret, elapsed);

      count++;
      tally += elapsed;
      minRunTime = muscleMin(minRunTime, elapsed);
      maxRunTime = muscleMax(maxRunTime, elapsed);
      
      for (uint32 i=0; i<numPairs; i++)
      {
         if (multiplexer.IsSocketReadyForRead(receivers[i].GetFileDescriptor()))
         {
            char buf[64];
            int32 numBytesReceived = ReceiveData(receivers[i], buf, sizeof(buf), false);
            if (quiet == false) printf("Receiver #" UINT32_FORMAT_SPEC " signalled ready-for-read, read " INT32_FORMAT_SPEC " bytes.\n", i, numBytesReceived);
            if (numBytesReceived > 0)
            {
               uint32 nextIdx = (i+1)%numPairs;
               int32 sentBytes = SendData(senders[nextIdx], buf, numBytesReceived, false);
               if (quiet == false) printf("Sent " INT32_FORMAT_SPEC " bytes on sender #" UINT32_FORMAT_SPEC "\n", sentBytes, nextIdx);
            }
         }
      }
   }
   printf("Test complete:  WaitEvents() called " UINT64_FORMAT_SPEC " times, averageTime=" UINT64_FORMAT_SPEC "uS, minimumTime=" UINT64_FORMAT_SPEC "uS, maximumTime=" UINT64_FORMAT_SPEC "uS.\n", count, tally/(count?count:1), minRunTime, maxRunTime);
   return 0;
}