#include "normSocket.h" #include #include #ifdef WIN32 #include "win32InputHandler.cpp" // brings in the entire implementation #else #include #include // for, well, fnctl() #include // obvious child #endif // if/else WIN32/UNIX // BUILD: (assumes "normApi.h" in "include" ... // g++ -I../include -o normClient normClient.cpp normSocket.cpp ../lib/libnorm.a ../protolib/lib/libprotokit.a -lresolv // NOTES: // 1) THIS IS A WORK IN PROGRESS AND NOT YET FUNCTIONAL! // 2) Some of the functions here may be added to the NORM API in the future. // Client initiates connection to "server". When the "client" gets a NORM_REMOTE_SENDER_NEW // from the "server", it knows its connection has been accepted. // To deny a connection, the "server" can send a stream close??? Which means the server will have // to establish state just to deny a client a connection (yuck!) void Usage() { fprintf(stderr, "Usage: normClient [connect [/][,]][debug ][trace]\n"); } const unsigned int MSG_LENGTH_MAX = 64; int main(int argc, char* argv[]) { char serverAddr[64]; strcpy(serverAddr, "127.0.0.1"); UINT16 serverPort = 5000; char groupAddr[64]; const char* groupAddrPtr = NULL; const char* mcastInterface = NULL; NormNodeId clientId = 2; bool trace = false; unsigned int debugLevel = 0; for (int i = 1; i < argc; i++) { const char* cmd = argv[i]; unsigned int len = strlen(cmd); if (0 == strncmp(cmd, "connect", len)) { // connect /[,] const char* val = argv[++i]; const char* portPtr = strchr(val, '/'); if (NULL == portPtr) { fprintf(stderr, "normClient error: missing number\n"); Usage(); return -1; } portPtr++; unsigned int addrTextLen = portPtr - val; if (addrTextLen > 0) { addrTextLen -= 1; strncpy(serverAddr, val, addrTextLen); serverAddr[addrTextLen] = '\0'; } else { fprintf(stderr, "normClient error: missing \n"); Usage(); return -1; } char portText[32]; const char* groupPtr = strchr(portPtr, ','); if (NULL == groupPtr) { strcpy(portText, portPtr); } else { groupPtr++; unsigned int portTextLen = groupPtr - portPtr - 1; strncpy(portText, portPtr, portTextLen); portText[portTextLen] = '\0'; } if (1 != sscanf(portText, "%hu", &serverPort)) { fprintf(stderr, "normClient error: invalid \n"); Usage(); return -1; } if (NULL != groupPtr) { strcpy(groupAddr, groupPtr); groupAddrPtr = groupAddr; } } else if (0 == strncmp(cmd, "interface", len)) { mcastInterface = argv[++i]; } else if (0 == strncmp(cmd, "trace", len)) { trace = true; } else if (0 == strncmp(cmd, "debug", len)) { if (1 != sscanf(argv[++i], "%u", &debugLevel)) { fprintf(stderr, "normClient error: invalid debug level\n"); Usage(); return -1; } } else if (0 == strncmp(cmd, "id", len)) { const char* val = argv[++i]; unsigned long id; if (1 != sscanf(val, "%lu", &id)) { fprintf(stderr, "normClient error: invalid 'id' value\n"); Usage(); return -1; } clientId = id; } else { fprintf(stderr, "normClient error: invalid command \"%s\"\n", cmd); Usage(); return -1; } } NormInstanceHandle instance = NormCreateInstance(); // Initate connection to server ... fprintf(stderr, "normClient: connecting to %s/%hu ...\n", serverAddr, serverPort); NormSocketHandle normSocket = NormOpen(instance); // setting 'localPort' param here to zero lets an ephemeral port be picked NormConnect(normSocket, serverAddr, serverPort, 0, groupAddrPtr, clientId); /* // Optional code to test NormWrite() immediately after NormConnect() call // (Note this is _not_ compatible with newer stale connection reject code) const char* helloStr = "Hello\n"; unsigned int helloLen = strlen(helloStr) + 1; NormWrite(normSocket, helloStr, helloLen); NormFlush(normSocket);*/ if (trace) { NormSetMessageTrace(NormGetSocketSession(normSocket), true); if (NULL != groupAddrPtr) NormSetMessageTrace(NormGetSocketMulticastSession(normSocket), true); } if (0 != debugLevel) NormSetDebugLevel(debugLevel); //NormSetDebugLevel(3); //NormSetMessageTrace(NormGetSocketSession(normSocket), true); #ifdef WIN32 HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); Win32InputHandler inputHandler; inputHandler.Open(); HANDLE handleArray[2]; handleArray[0] = NormGetDescriptor(instance); handleArray[1] = inputHandler.GetEventHandle(); #else // On Unix, use a select() call to multiplex input reading and NormSocket handling fd_set fdset; FD_ZERO(&fdset); // Get our input (STDIN) descriptor and set non-blocking FILE* inputFile = stdin; int inputfd = fileno(inputFile); if (-1 == fcntl(inputfd, F_SETFL, fcntl(inputfd, F_GETFL, 0) | O_NONBLOCK)) perror("normClient: fcntl(inputfd, O_NONBLOCK) error"); // Get our NormInstance descriptor int normfd = NormGetDescriptor(instance); # endif // if/else WIN32 bool keepGoing = true; bool writeReady = false; int inputLength = 0; unsigned int bytesWritten = 0; const unsigned int BUFFER_LENGTH = 2048; char inputBuffer[BUFFER_LENGTH]; bool inputNeeded = false; // will be set to "true" upon CONNECT while (keepGoing) { bool normEventPending = false; bool inputEventPending = false; #ifdef WIN32 DWORD handleCount = inputNeeded ? 2 : 1; DWORD waitStatus = MsgWaitForMultipleObjectsEx(handleCount, // number of handles in array handleArray, // object-handle array INFINITE, // time-out interval QS_ALLINPUT, // input-event type 0); if ((WAIT_OBJECT_0 <= waitStatus) && (waitStatus < (WAIT_OBJECT_0 + handleCount))) { if (0 == (waitStatus - WAIT_OBJECT_0)) normEventPending = true; else inputEventPending = true; } else if (-1 == waitStatus) { perror("normClient: MsgWaitForMultipleObjectsEx() error"); break; } else { // TBD - any other status we should handle? // (e.g. WAIT_TIMEOUT, WAIT_ABANDONED or WAIT_IO_COMPLETION) continue; // ignore for now } #else FD_SET(normfd, &fdset); int maxfd = normfd; if (inputNeeded) { FD_SET(inputfd, &fdset); if (inputfd > maxfd) maxfd = inputfd; } else { FD_CLR(inputfd, &fdset); } int result = select(maxfd+1, &fdset, NULL, NULL, NULL); if (result <= 0) { perror("normClient: select() error"); break; } if (FD_ISSET(inputfd, &fdset)) inputEventPending = true; if (FD_ISSET(normfd, &fdset)) normEventPending = true; #endif // if/else WIN32/UNIX if (inputEventPending) { // Read input into our txBuffer #ifdef WIN32 inputLength = inputHandler.ReadData(inputBuffer, BUFFER_LENGTH); if (inputLength > 0) { // We got our input bytesWritten = 0; inputNeeded = false; } else if (inputLength < 0) { // Input stream has likely closed, initiate client shutown // TBD - initiate client shutdown if (NULL == groupAddrPtr) { fprintf(stderr, "normClient: CLOSING connection to server ...\n"); NormShutdown(normSocket); } inputNeeded = false; // TBD -should we also fclose(inputFile)??? inputHandler.Close(); } // else zero bytes read, still need input #else inputLength = fread(inputBuffer, 1, BUFFER_LENGTH, inputFile); if (inputLength > 0) { // We got our input bytesWritten = 0; inputNeeded = false; } else if (feof(inputFile)) { // TBD - initiate client shutdown if (NULL == groupAddrPtr) { fprintf(stderr, "normClient: CLOSING connection to server ...\n"); NormShutdown(normSocket); } inputNeeded = false; // TBD -should we also fclose(inputFile)??? if (stdin != inputFile) { fclose(inputFile); inputFile = NULL; } // else stick around to receive stuff from the server } else if (ferror(inputFile)) { switch (errno) { case EINTR: // interupted, try again break; case EAGAIN: // input starved, wait for next notification break; default: perror("normClient: error reading input?!"); break; } } #endif // if/else WIN32/UNIX } // end if inputEventPending if (normEventPending) { // There's a NORM event pending NormSocketEvent event; if (NormGetSocketEvent(instance, &event)) { switch (event.type) { case NORM_SOCKET_ACCEPT: // shouldn't happen break; case NORM_SOCKET_CONNECT: { char remoteAddr[16]; unsigned int addrLen = 16; UINT16 remotePort; NormGetPeerName(normSocket, remoteAddr, &addrLen, &remotePort); fprintf(stderr, "normClient: CONNECTED to server %s/%hu\n", serverAddr, remotePort); inputNeeded = true; writeReady = true; break; } case NORM_SOCKET_READ: { // This is a cue to try to read data from stream // For our test app here, the data is read and output to STDOUT bool rxReady = true; while (rxReady) { char buffer[1024]; ssize_t bytesRead = NormRead(event.socket, buffer, 1024); if (bytesRead < 0) { // This shouldn't happen with ack-based flow control used fprintf(stderr, "normClient: broken stream ...\n"); continue; } if (bytesRead > 0) { #ifdef WIN32 // Use Win32 WriteFile() so there is no buffer delay DWORD dwWritten; WriteFile(hStdout, buffer, bytesRead, &dwWritten, NULL); #else fwrite(buffer, sizeof(char), bytesRead, stdout); #endif // if/else WIN32 } // If less bytes read than request then need // to wait for next NORM_SOCKET_READ event if (bytesRead < 1024) rxReady = false; } break; } case NORM_SOCKET_WRITE: writeReady = true; break; case NORM_SOCKET_CLOSING: fprintf(stderr, "normClient: server CLOSING connection ...\n"); writeReady = false; inputNeeded = false; break; case NORM_SOCKET_CLOSE: { fprintf(stderr, "normClient: connection to server CLOSED.\n"); writeReady = false; inputNeeded = false; keepGoing = false; break; } case NORM_SOCKET_NONE: break; } // end switch(event.type) } else { fprintf(stderr, "normClient: NormGetSocketEvent() returned false\n"); } } // end if normEventPending // If we have data in our inputBuffer and the NormSocket is "writeReady", then send it if (writeReady && (inputLength > 0)) { bytesWritten += NormWrite(normSocket, inputBuffer + bytesWritten, inputLength - bytesWritten); if (bytesWritten < inputLength) { // Couldn't write whole inputBuffer, need to wait for NORM_SOCKET_WRITE event writeReady = false; } else { // inputBuffer has been completely written inputLength = 0; inputNeeded = true; NormFlush(normSocket); } } } // end while(keepGoing) NormClose(normSocket); #ifdef WIN32 inputHandler.Close(); #else if ((stdin != inputFile) && (NULL != inputFile)) { fclose(inputFile); inputFile = NULL; } #endif // if/else WIN32 fprintf(stderr, "normClient: Done.\n"); return 0; }