423 lines
14 KiB
C++
423 lines
14 KiB
C++
#include "normSocket.h"
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#ifdef WIN32
|
|
#include "win32InputHandler.cpp" // brings in the entire implementation
|
|
#else
|
|
#include <sys/select.h>
|
|
#include <fcntl.h> // for, well, fnctl()
|
|
#include <errno.h> // 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 <serverAddr>[/<port>][,<groupAddr>]][debug <level>][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 <serverAddr>/<port>[,<groupAddr>]
|
|
const char* val = argv[++i];
|
|
const char* portPtr = strchr(val, '/');
|
|
if (NULL == portPtr)
|
|
{
|
|
fprintf(stderr, "normClient error: missing <port> 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 <serverAddr>\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 <port>\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();
|
|
NormSocketHandle normSocket = NormOpen(instance);
|
|
|
|
if (trace)
|
|
{
|
|
NormSetMessageTrace(NormGetSocketSession(normSocket), true);
|
|
if (NULL != groupAddrPtr)
|
|
NormSetMessageTrace(NormGetSocketMulticastSession(normSocket), true);
|
|
}
|
|
if (0 != debugLevel) NormSetDebugLevel(debugLevel);
|
|
|
|
//NormSetDebugLevel(3);
|
|
//NormSetMessageTrace(NormGetSocketSession(normSocket), true);
|
|
|
|
// Initate connection to server ...
|
|
fprintf(stderr, "normClient: connecting to %s/%hu ...\n", serverAddr, serverPort);
|
|
// 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);*/
|
|
|
|
|
|
|
|
#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;
|
|
}
|