// This example NORM application acts as unicast "server". A NORM receive-only session "listens" // on a "serverPort". When new remote senders are detected, the sender is assigned to a // newly create "client" NormSession that is "connected" to the remote sender/client addr/port. // The NormSetRxPortReuse() call provides an option to "connect" the underlying NORM UDP socket // to the remote sender/client address/port. Note this is not yet supported for NORM-CCE (ECN) // congestion control operation. // By creating "client" NormSessions for each client connection, this enables multiple clients // from the same host (with the same NormNodeId to connect to the server at the same time. // Otherwise, clients with the same NormNodeId would "collide" at the server receive session. // Even the approach here is not perfect as a packet from a another client instance with the // same NormNodeId might change the given "remote sender" source addr/port before the // connected "client" session is created as a sort of race condition. // A potential enhancement to NORM would be to allow tracking of multiple remote senders // with the same NormNodeId but different instanceIds ... // BUILD: (assumes "normApi.h" in "include" ... // g++ -I../include -o normServer normServer.cpp normSocket.cpp ../lib/libnorm.a ../protolib/lib/libprotokit.a -lresolv // EXAMPLE NOTES: // // 0) THIS IS A WORK IN PROGRESS AND NOT YET FUNCTIONAL! // // 1) Some of the functions here may be added to the NORM API in the future. // // 2) The "main()" below is a single-threaded example with a single NormInstance and a // single NormGetNextEvent() main loop. Note that the NormAccept() call allows for a // new instance (and hence new NormDescriptor) for each accepted client, so multiple // threads or event thread-pooling versions of this could be implemented for // performance or application design purposes. A future version of the NORM API and // this could even provide for very "socket-like" API calls where each "client" // session has its own descriptor independent of threading (e.g. a "normSocket.h" API // that is implemented around the current low level NORM API). This sort of "NORM // socket" approach could be supported for unicast and SSM streams without too much // difficulty. #include "normSocket.h" #include // for inet_ntoa #include // for fprintf() #include // for memcmp() #include // for std::map<> #include #include // for, well, fnctl() #include // obvious child // Our "server" indexes clients by their source addr/port class ClientInfo { public: ClientInfo(UINT8 ipVersion = 0, const char* theAddr = NULL, UINT16 thePort = 0); bool operator < (const ClientInfo& a) const; int GetAddressFamily() const; const char* GetAddress() const {return client_addr;} UINT16 GetPort() const {return client_port;} const char* GetAddressString() const; void Print(FILE* filePtr) const; private: UINT8 addr_version; // 4 or 6 char client_addr[16]; // big enough for IPv6 UINT16 client_port; }; // end class ClientInfo ClientInfo::ClientInfo(UINT8 addrVersion, const char* clientAddr, UINT16 clientPort) : addr_version(addrVersion), client_port(clientPort) { if (NULL == clientAddr) addrVersion = 0; // forces zero initialization switch (addrVersion) { case 4: memcpy(client_addr, clientAddr, 4); memset(client_addr+4, 0, 12); break; case 6: memcpy(client_addr, clientAddr, 16); break; default: memset(client_addr, 0, 16); break; } } // returns "true" if "this" less than "a" (used by C++ map) bool ClientInfo::operator <(const ClientInfo& a) const { if (addr_version != a.addr_version) return (addr_version < a.addr_version); else if (client_port != a.client_port) return (client_port < a.client_port); else if (4 == addr_version) return (0 > memcmp(client_addr, a.client_addr, 4)); else return (0 > memcmp(client_addr, a.client_addr, 16)); } // end ClientInfo::operator <() int ClientInfo::GetAddressFamily() const { if (4 == addr_version) return AF_INET; else return AF_INET6; } // end ClientInfo::GetAddressFamily() const char* ClientInfo::GetAddressString() const { static char text[64]; text[63] = '\0'; int addrFamily; if (4 == addr_version) addrFamily = AF_INET; else addrFamily = AF_INET6; inet_ntop(addrFamily, client_addr, text, 63); return text; } // end ClientInfo::GetAddressString() void ClientInfo::Print(FILE* filePtr) const { char text[64]; text[63] = '\0'; int addrFamily; if (4 == addr_version) addrFamily = AF_INET; else addrFamily = AF_INET6; inet_ntop(addrFamily, client_addr, text, 63); fprintf(filePtr, "%s/%hu", text, client_port); } // end ClientInfo::Print() // C++ map used to index client sessions by the client source addr/port typedef std::map ClientMap; ClientInfo NormGetClientInfo(NormNodeHandle client) { char addr[16]; // big enough for IPv6 unsigned int addrLen = 16; UINT16 port; NormNodeGetAddress(client, addr, &addrLen, &port); int addrFamily; UINT8 version; if (4 == addrLen) { addrFamily = AF_INET; version = 4; } else { addrFamily = AF_INET6; version = 6; } return ClientInfo(version, addr, port); } // end NormGetClientInfo() NormSocketHandle FindClientSocket(ClientMap& clientMap, const ClientInfo& clientInfo) { ClientMap::iterator it = clientMap.find(clientInfo); if (clientMap.end() != it) return &(it->second); else return NORM_SOCKET_INVALID; } // end FindClientSocket() int main(int argc, char* argv[]) { ClientMap clientMap; UINT16 serverPort = 5000; UINT16 serverInstanceId = 1; char groupAddr[64]; const char* groupAddrPtr = NULL; const char* mcastInterface = NULL; 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, "listen", len)) { // listen [/] const char* val = argv[++i]; const char* portPtr = strchr(val, '/'); if (NULL != portPtr) portPtr++; else portPtr = val; unsigned int addrTextLen = portPtr - val; if (addrTextLen > 0) { addrTextLen -= 1; strncpy(groupAddr, val, addrTextLen); groupAddr[addrTextLen] = '\0'; groupAddrPtr = groupAddr; } if (1 != sscanf(portPtr, "%hu", &serverPort)) { fprintf(stderr, "normServer error: invalid \"%s\"\n", portPtr); return -1; } } 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, "normServer error: invalid debug level\n"); return -1; } } else { fprintf(stderr, "normServer error: invalid command \"%s\"\n", cmd); return -1; } } // For unicast operation in this demo app, the server only "talks back" // to one client on a first come, first serve basis. (Multiple clients // can connect and send data _to_ the server, but in this simple example, // the server only sends to one at a time. For the multicast server case, // the server multicasts to the entire group. NormSocketHandle firstClientSocket = NORM_SOCKET_INVALID; NormInstanceHandle instance = NormCreateInstance(); NormSocketHandle serverSocket = NormListen(instance, serverPort, groupAddrPtr); if (trace) NormSetMessageTrace(NormGetSession(serverSocket), true); if (0 != debugLevel) NormSetDebugLevel(debugLevel); //NormSetDebugLevel(8); //NormSetMessageTrace(NormGetSession(serverSocket), true); // We 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); bool keepGoing = true; bool writeReady = false; unsigned 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) { 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)) { // Read input into our txBuffer inputLength = fread(inputBuffer, 1, BUFFER_LENGTH, inputFile); if (inputLength > 0) { // We got our input bytesWritten = 0; inputNeeded = false; } else if (feof(inputFile)) { // TBD - initiate server shutdown if it's job is // only until the input is closed // Meanwhile, we just stick around to receive stuff from the clients inputNeeded = false; // TBD - should also fclose(inputFile)?? } 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; } } } if (FD_ISSET(normfd, &fdset)) { // There's a NORM event pending NormSocketEvent event; if (NormGetSocketEvent(instance, &event)) { ClientInfo clientInfo; if (NORM_NODE_INVALID != event.sender) clientInfo = NormGetClientInfo(event.sender); switch (event.type) { case NORM_SOCKET_ACCEPT: { if (event.socket == serverSocket) { // Possibly a new "client" connecting to our "server" // First confirm that this really is a new client. if (NORM_SOCKET_INVALID != FindClientSocket(clientMap, clientInfo)) { // We think we're already connected to this client fprintf(stderr, "normServer: duplicative %s client ...\n", (NORM_REMOTE_SENDER_NEW == event.event.type) ? "new" : "reset"); continue; } NormSocketHandle clientSocket = NormAccept(serverSocket, event.sender); // TBD - For multicast, if we are sending a tx_stream, we could flush it here to // bring the new receiver "up to date" ... probably would be best to // do this on some sort of timer-basis in the case of a bunch of receivers // joining in a short window of time ... if (trace) // note we're already tracing the mcast session NormSetMessageTrace(NormGetSession(clientSocket), true); //NormSetMessageTrace(NormGetSession(clientSocket), true); clientMap[clientInfo] = clientSocket; fprintf(stderr, "normServer: ACCEPTED connection from %s/%hu\n", clientInfo.GetAddressString(), clientInfo.GetPort()); // We have at least one client, so lets serve up some juicy input inputNeeded = true; writeReady = true; } else { // shouldn't happen } break; } case NORM_SOCKET_CONNECT: { fprintf(stderr, "normServer: CONNECTED to %s/%hu ...\n", clientInfo.GetAddressString(), clientInfo.GetPort()); if (NORM_SOCKET_INVALID == firstClientSocket) firstClientSocket = event.socket; 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, "normServer: broken stream ...\n"); continue; } if (bytesRead > 0) fwrite(buffer, sizeof(char), bytesRead, stdout); if (bytesRead < 1024) rxReady = false; } break; } case NORM_SOCKET_WRITE: // We only demo server data transmission for the multicast server case // (see comment below) if ((NULL != groupAddrPtr) || (event.socket == firstClientSocket)) writeReady = true; break; case NORM_SOCKET_CLOSING: fprintf(stderr, "normServer: client %s/%hu CLOSING connection ...\n", clientInfo.GetAddressString(), clientInfo.GetPort()); if (event.socket == firstClientSocket) firstClientSocket = NORM_SOCKET_INVALID; break; case NORM_SOCKET_CLOSED: { fprintf(stderr, "normServer: connection to client %s/%hu CLOSED ...\n", clientInfo.GetAddressString(), clientInfo.GetPort()); if (event.socket == firstClientSocket) firstClientSocket = NORM_SOCKET_INVALID; clientMap.erase(clientInfo); break; } case NORM_SOCKET_NONE: break; break; } // end switch(event.type) } else { fprintf(stderr, "normServer: NormGetNextSocketEvent() returned false\n"); } } // end if FD_ISSET(normfd) // For our _multicast_ "normServer" example, the server can send to the group // (For a unicast "normServer", we would need to do something more complex // to manage sending data to each individual client that connects to us. // So, for the moment, the unicast "normServer" only sends to the "firstClientSocket" NormSocketHandle sendSocket = (NULL != groupAddrPtr) ? serverSocket : firstClientSocket; if (NORM_SOCKET_INVALID != sendSocket) { if (writeReady && (inputLength > 0)) { // We have data in our inputBuffer and the NormSocket is "writeReady", so send it bytesWritten += NormWrite(sendSocket, 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(sendSocket); } } } } // end while (keepGoing) } // end main()