WOscClient.cpp

An example of how to build a full fledged OSC system and how to inherit from WOscNetReturn is shown in this example with its header (WOscClient.h) prepended here:


/** The network return address. Through multiple inheritance this object
 * inherits the functionality required by WOscLib to keep it alive until
 * all messages ob a bundle are processed (reference counting) and the
 * networking data structure which contains the actual ip/port information.
 *
 */
class TheNetReturnAddress:
    public WOscNetReturn 
{
public:
    struct sockaddr_in  m_addr;
};

///////////////////////////////////////////////////////////////////////////////

class WOscClient;

/** All methods used in WOscClient are derived from this class. In a real
 * project the methods should be implemented in a header file to inline them
 * and eliminate the c++ overhead.
 *
 * This class avoids some pointer-casting but wouldn't be necessary but makes
 * all methods used type-save.
 */
class WOscClientMethod:
    public WOscReceiverMethod
{
public:
    WOscClientMethod(
        WOscContainer* parent,
        WOscClient* receiverContext,
        const char* methodName,
        const char* methodDescription);
    virtual void Method(
        const WOscMessage *message,
        const WOscTimeTag& when,
        const TheNetReturnAddress* networkReturnAddress) = 0;
    virtual WOscClient* GetContext();
protected:
    virtual void Method(
        const WOscMessage *message,
        const WOscTimeTag& when,
        const WOscNetReturn* networkReturnAddress);
};

///////////////////////////////////////////////////////////////////////////////

/** A simple method which prints "hello world" on the console. Furthermore
 * All parameters send with a "/hello" message are printed on the console.
 *
 */
class TheOscEchoMethod:
    public WOscClientMethod
{
public:
    TheOscEchoMethod(
        WOscContainer* parent,
        WOscClient* receiverContext);
    virtual void Method(
        const WOscMessage *message,
        const WOscTimeTag& when,
        const TheNetReturnAddress* networkReturnAddress);
};

///////////////////////////////////////////////////////////////////////////////

/** This is the central class which implements a full featured OSC server
 * including the network-layer implementation.
 *
 */
class WOscClient:
    public WOscReceiver
{
public:
    WOscClient();
    virtual ~WOscClient();
    /** Errors which can occur in the network layer. */
    enum WOscClientErrors
    {
        WOS_ERR_NO_ERROR         =  0, /**< No error.                        */
        WOS_ERR_SOCKET_CREATE    = -1, /**< Error when creating the socket.  */
        WOS_ERR_SOCKET_BIND      = -2, /**< Error when binding the local
                                        * address and port to the socket.    */
        WOS_ERR_SOCKET_REUSE     = -3, /**< Error when setting the socket-
                                        * reuse option.                      */
        WOS_ERR_SOCKET_BROADCAST = -4, /**< Error when setting the socket-
                                        * broadcast option.                  */
        WOS_ERR_SOCKET_BLOCK     = -5, /**< Error when setting the socket to
                                        * the non-blocking mode.             */
        WOS_ERR_SOCKET_WSASTART  = -6, /**< Error when starting the Windoze
                                        * winsock subsystem (Windows builds
                                        * only).                             */
    };
    WOscClientErrors NetworkInit(int port);
    WOscClientErrors NetworkHalt();
    WOscClientErrors CheckForPackets();
    bool Exit() const ;
    void SetExit() ;

    virtual void NetworkSend(
        const char* data,
        int dataLen,
        const WOscNetReturn* networkReturnAddress);
        
protected:
    virtual void HandleOffendingPackets(
        const char* const data,
        int dataLen,
        const WOscException& exception);
    virtual void HandleNonmatchedMessages(
        const WOscMessage* msg,
        const WOscNetReturn* networkReturnAddress);

private:
    /** The network address of the server when initialized.                  */
    TheNetReturnAddress     m_serverAddr;
    /** Databuffer for received data.                                        */
    char*                   m_rxbuffer;
    /** The socket handler of this OSC server.                               */
    int                     m_hSocket;
    /** Flag set when termination of the application or server is desired.   */
    bool                    m_exit;
};




#include "WOscConfig.h"
///////////////////////////////////////////////////////////////////////////////
// OS dependent includes
///////////////////////////////////////////////////////////////////////////////
   
#if OS_IS_LINUX == 1 || OS_IS_MACOSX == 1 || OS_IS_CYGWIN == 1
#   include <unistd.h>      //  usleep
#   include <fcntl.h>
#   include <netinet/in.h>
#   include <arpa/inet.h>
#   include <sys/socket.h>
#elif OS_IS_WIN32 == 1
#   include "windows.h"
#   include "winsock2.h"
#   define socklen_t    int
#else
#   error "Invalid Platform"
#endif

///////////////////////////////////////////////////////////////////////////////
// general includes
///////////////////////////////////////////////////////////////////////////////

#include <iostream>
#include <stdlib.h>
#include <string.h>

#include "WOscReceiver.h"
#include "WOscNetReturn.h"
#include "WOscReceiverMethod.h"

#include "WOscClient.h"

///////////////////////////////////////////////////////////////////////////////
// defines
///////////////////////////////////////////////////////////////////////////////
   
#define OSC_SERVER_PORT         10000
#define PROTOCOL_UDP            17
#define WOS_MAX_RX_UDP_PACKET   2048

const char* WOSC_CLIENT_HELP_STR = 
        "\n"
        " WOscClient 1.00 help\n"
        "\n"
        " Invoking \"woscclient\":\n"
        " woscclient [local port] [remote port] [remote IP]\n"
        "\n"
        " Commands controlling \"woscclient\":\n"
        "\n"
        " exit           Terminate \"woscclient\".\n"
        " slp [port]     Set local UDP port to [port]\n"
        " srp [port]     Set remote server UDP destination port to [port].\n"
        " srip [ip]      Set remote server UDP destination IP to [ip].\n"
        "                  whereas [ip] is an IP string in dotted decimal notation\n"
        "                  (127.0.0.1).\n"
        "\n"
        " /[msg] [prms]  Send an OSC message \"/msg\" to remote OSC server with\n"
        "                  parameters [params]. [params] can be a space separated\n"
        "                  list containing integers ( 666 ), floats ( 3.14 ) and\n"
        "                  strings ( hello_world ).\n"
        "\n"
        " [              Start a bundle (currently no nesting). Messages can be\n"
        "                  added to the bundle afterwards as depicted above.\n"
        " ]              Close a bundle and transmit it.\n"
        "\n";

#if OS_IS_LINUX == 1 || OS_IS_MACOSX == 1 || OS_IS_CYGWIN == 1
socklen_t WOS_SIZE_NRA = sizeof(sockaddr_in);
#else
int WOS_SIZE_NRA = sizeof(sockaddr_in);
#endif

///////////////////////////////////////////////////////////////////////////////
// WOscClientMethod
///////////////////////////////////////////////////////////////////////////////

/** Constructor. Initializes WOscReceiverMethod base class. */
WOscClientMethod::WOscClientMethod(WOscContainer* parent,
        WOscClient* receiverContext, const char* methodName,
        const char* methodDescription) :
    WOscReceiverMethod(parent, (WOscReceiver*) receiverContext, methodName,
            methodDescription)
{
}

/** Type-save access to receiver object, which is owning this object. */
WOscClient* WOscClientMethod::GetContext()
{
    return (WOscClient*)WOscReceiverMethod::GetContext();
}

/** Type-save method interface. All methods inherited from this class get
 * the correct TheNetReturnAddress pointers without caring about the
 * system below.
 */
void WOscClientMethod::Method(const WOscMessage *message,
        const WOscTimeTag& when, const WOscNetReturn* networkReturnAddress)
{
    Method(message, when, (TheNetReturnAddress*) networkReturnAddress);
}

///////////////////////////////////////////////////////////////////////////////
// TheOscEchoMethod
///////////////////////////////////////////////////////////////////////////////

/** Constructor. Sets the method name and info in the base class. */
TheOscEchoMethod::TheOscEchoMethod(WOscContainer* parent,
        WOscClient* receiverContext) :
    WOscClientMethod(parent, receiverContext, "echo", "")
{
}

/** The echo method. Echoes the message back to its origin. The "origin" can be
 * modified by passing an integer and string parameter with the message. The
 * integer modifies the return port and the string the return IP.
 */
void TheOscEchoMethod::Method(const WOscMessage *message,
        const WOscTimeTag& when,
        const TheNetReturnAddress* networkReturnAddress)
{
    std::cout << "Echo received from:\n" 
        << "Port: " << ntohs(networkReturnAddress->m_addr.sin_port) << "\n"
        << "IP:   " << inet_ntoa( networkReturnAddress->m_addr.sin_addr ) <<std::endl;
}

///////////////////////////////////////////////////////////////////////////////
// WOscClient
///////////////////////////////////////////////////////////////////////////////

/** Constructor. Sets up the OSC address space, allocates an UDP receive buffer
 * and initializes the exit flag to "false".
 */
WOscClient::WOscClient()
{
    // setup OSC address space //

    // containers
    WOscContainer* cntRoot = new WOscContainer();
    WOscContainer* cntTest = new WOscContainer(cntRoot, "test");
    
    // "root" methods
    WOscMethod* msgEcho = new TheOscEchoMethod(cntRoot, this);
    msgEcho->AddMethodAlias(cntTest, "echo");
    
    // add alias of method to same container where the original resides (to test the clean up)
    msgEcho->AddMethodAlias(cntTest, "echo2");

    // add alias of container to same container where the original resides (to test the clean up)
    cntTest->AddContainerAlias(cntRoot, "test2");

    SetAddressSpace(cntRoot);

    WOscString addressSpaceStr = cntRoot->GetAddressSpace();
    std::cout<<"Client address space:\n"<<addressSpaceStr.GetBuffer()<<std::flush;

    // allocate RX buffer
    m_rxbuffer = new char[WOS_MAX_RX_UDP_PACKET];

    m_exit = false;
}

/** Destructor. Removes the address space and deallocates the UDP receive
 * buffer.
 */
WOscClient::~WOscClient()
{
    // remove address space //
    WOscContainer* as = GetAddressSpace();
    if ( as ) as->RemoveAll();
}

/** Checks the exit flag.
 * \returns
 * True if application/serve exit is requested.
 */
bool WOscClient::Exit() const
{
    return m_exit;
}

/** Sets the exit flag, which is used to check for server termination.       */
void WOscClient::SetExit()
{
    m_exit = true;
}

/** Sends data over the network.
 * \param data
 * Pointer to buffer containing the data to be sent.
 *
 * \param dataLen
 * Number of bytes in the data buffer.
 *
 * \param networkReturnAddress
 * The destination network address.
 */
void WOscClient::NetworkSend(
    const char* data,
    int dataLen,
    const WOscNetReturn* networkReturnAddress)
{
    const TheNetReturnAddress* nra = (const TheNetReturnAddress*)networkReturnAddress;
    int actSend = sendto(
        m_hSocket,
        data,
        dataLen,
        0,
        (struct sockaddr*)&nra->m_addr,
        WOS_SIZE_NRA ) ;
    
    // check if transmission was successful
    if ( dataLen != actSend )
        std::cout << "Error sending packet."<<std::endl ;
}

/** Callback for OSC packets which have caused an exception in the OSC 
 * receiver.
 * \param data
 * Pointer to buffer containing the packet data.
 * \param dataLen
 * Length of the packet data in the buffer.
 * \exception
 * Exception that caused this handler to be called.
 */
void
WOscClient::HandleOffendingPackets(const char* const data, int dataLen,
        const WOscException& exception)
{
    std::cout<<"HandleOffendingPackets."<<std::endl;
}

/** Callback for messages not matching any address in the local OSC address
 * space.
 * \param msg
 * Message which hasn't been processed.
 * \param networkReturnAddress
 * Network origin of the message.
 */
void WOscClient::HandleNonmatchedMessages(
    const WOscMessage* msg,
    const WOscNetReturn* networkReturnAddress)
{
    int nStr = msg->GetNumStrings();
    int nInt = msg->GetNumInts();
    int nFlt = msg->GetNumFloats();

    if ( !nStr && !nInt && !nFlt )
        std::cout << "OSC message \"" << msg->GetOscAddress().GetBuffer()
            << "\" received."<< std::endl;
    else
        std::cout << "OSC message \"" << msg->GetOscAddress().GetBuffer()
            << "\" contains:"<<std::endl;

    for (int i = 0; i < nStr; i++ )
        std::cout << "  str["<<i<<"]\t" << 
            msg->GetString(i).GetBuffer()<< std::endl ;
    for (int i = 0; i < nInt; i++ )
        std::cout << "  int["<<i<<"]\t" << 
            msg->GetInt(i) << std::endl ;
    for (int i = 0; i < nFlt; i++ )
        std::cout << "  flt["<<i<<"]\t" << 
            msg->GetFloat(i) << std::endl ;
}

/** Initializes the underlying UDP network layer.
 * \param port
 * Port at which WOscClient should listen for OSC data.
 * \returns
 * Error code on error, WOS_ERR_NO_ERROR on success.
 */
WOscClient::WOscClientErrors WOscClient::NetworkInit(int port)
{
    int err;
    const int REUSE_TRUE = 1, BROADCAST_TRUE = 1;
    
#if OS_IS_WIN32 == 1
    // fucking windows winsock startup
    WSADATA wsa;
    err = WSAStartup(MAKEWORD(2,0),&wsa);
    if ( err != 0 ) {
        std::cout << "Error starting Windows socket subsystem."<<std::endl ;
        return WOS_ERR_SOCKET_WSASTART;
    }
#endif  // #if OS_IS_WIN32 == 1

    // create socket
    m_hSocket = socket (AF_INET, SOCK_DGRAM, PROTOCOL_UDP);
    if (m_hSocket < 0) {
        std::cout << "Create socket error."<<std::endl;
        return WOS_ERR_SOCKET_CREATE;
    }
    
    // initialize server address to localhost:port
    m_serverAddr.m_addr.sin_family         = AF_INET;
    m_serverAddr.m_addr.sin_addr.s_addr    = htonl (INADDR_ANY);
    m_serverAddr.m_addr.sin_port           = htons (port);
    
    // set socket to reuse the address
    err = setsockopt(
        m_hSocket,
        SOL_SOCKET,
        SO_REUSEADDR,
        (const char*)&REUSE_TRUE,
        sizeof(REUSE_TRUE));
    if ( err != 0 ) {
        std::cout << "Error setting socket reuse."<<std::endl ;
        return WOS_ERR_SOCKET_REUSE;
    }
    // enable broadcasting for this socket
    setsockopt(
        m_hSocket,
        SOL_SOCKET,
        SO_BROADCAST,
        (const char*)&BROADCAST_TRUE,
        sizeof(BROADCAST_TRUE));
    if ( err != 0 ) {
        std::cout << "Error setting socket broadcast."<<std::endl ;
        return WOS_ERR_SOCKET_BROADCAST;
    }
    
    // disable blocking, polling is used in this example.
#if OS_IS_LINUX == 1 || OS_IS_MACOSX == 1 || OS_IS_CYGWIN == 1
    err = fcntl(m_hSocket, F_SETFL, O_NONBLOCK);
#elif OS_IS_WIN32 == 1
    // disable blocking (for this example)
    unsigned long val = 1;
    err = ioctlsocket(m_hSocket, FIONBIO , &val);
#endif
    if ( err != 0 ) {
        std::cout << "Error setting socket unblock."<<std::endl ;
        return WOS_ERR_SOCKET_BLOCK;
    }
    
    // bind for listening
    err = bind (
        m_hSocket,
        (struct sockaddr *)&m_serverAddr.m_addr,
        WOS_SIZE_NRA );
    if ( err != 0 ) {
        std::cout << "Error socket bind."<<std::endl ;
        return WOS_ERR_SOCKET_BIND;
    }

    return WOS_ERR_NO_ERROR;
}

/** Stops the network interface.
 * \returns
 * WOS_ERR_NO_ERROR on success.
 */
WOscClient::WOscClientErrors WOscClient::NetworkHalt()
{
    // close socket... 
#if OS_IS_LINUX == 1 || OS_IS_MACOSX == 1 || OS_IS_CYGWIN == 1
    close(m_hSocket);
#elif OS_IS_WIN32 == 1
    closesocket(m_hSocket);
    WSACleanup();
#endif
    return WOS_ERR_NO_ERROR;
}

/** Check if there is an UDP packet. On packet arrival it will be passed 
 * to the OSC receiver. It returns if there are no more packets available.
 * \returns
 * WOS_ERR_NO_ERROR if no error occurred.
 */
WOscClient::WOscClientErrors WOscClient::CheckForPackets()
{
    // as long there are packets waiting
    bool morePackets = 1;
    while(morePackets)
    {
        // has to be allocated on heap, because is reference
        // counted
        TheNetReturnAddress* nra = new TheNetReturnAddress;
        nra->m_addr.sin_family         = AF_INET;
        nra->m_addr.sin_addr.s_addr    = htonl (INADDR_ANY);
        nra->m_addr.sin_port           = htons (0);
    
        // receive from network
        int nReceived = recvfrom(
            m_hSocket,
            m_rxbuffer,
            WOS_MAX_RX_UDP_PACKET,
            0,
            (struct sockaddr*)&nra->m_addr,
            (socklen_t*)&WOS_SIZE_NRA);
    
        // nReceived is the number of bytes actually read from 
        // network interface
        if (nReceived > 0) {
            NetworkReceive (m_rxbuffer, nReceived, nra);

            // test fast bundle parser
#if 1
            WOscBundleParser bundleParser;
            try {
                bundleParser.Parse(m_rxbuffer, nReceived);
                std::cout<<"Bundle time: "<<bundleParser.GetTimeTag().GetRawTimeTag()<<", bundle items: "<<bundleParser.GetItemCount()<<std::endl;
            } catch (const WOscException& exp) {
                std::cout<<"Error parsing raw bundle ("<<exp.GetErrStr()<<")."<<std::endl;
            }
#endif

        } else {
            delete nra;
            morePackets = false;    // stop receiving process
        }
    }
    return WOS_ERR_NO_ERROR;
}

///////////////////////////////////////////////////////////////////////////////
// main
///////////////////////////////////////////////////////////////////////////////

/** The program's main function of WOscClient.
 * Arguments:
 *  - 1. Local port at which the client listens.
 *  - 2. Remote port at which the remote server listens.
 *  - 3. Remote IP at which the remote server listens.
 *
 * When started, it accepts commandline input:
 *
 *  - "/message [params]" whereas "message" can be any OSC message as "/hello"
 *    and [params] can be some parameters (float, integer and string)
 *  - "exit" to exit WOscClient.
 *  - "slp port" to set local port to "port".
 */
int main(int argc, char *argv[])
{
    WOscClient client;
    int localport = OSC_SERVER_PORT + 1;
    int remoteport = OSC_SERVER_PORT;
    char remoteIP[32];
    
    strcpy(remoteIP, "127.0.0.1");

    // process arguments
    if ( argc > 1 )
        localport = atoi( argv[1] );
    if ( argc > 2 )
        remoteport = atoi( argv[2] );
    if ( argc > 3 )
        strcpy(remoteIP, argv[3]);
    
#if OS_IS_LINUX == 1 || OS_IS_MACOSX == 1 || OS_IS_CYGWIN == 1
    std::cout << "WOscClient 1.00 (UNIX/POSIX build)."<<std::endl ;
#elif OS_IS_WIN32 == 1
    std::cout << "WOscClient 1.00 (Windows build)."<<std::endl ;
#endif

    std::cout << "Setting up network layer:\n" <<
        "  local port:  " << localport << "\n" <<
        "  remote port: " << remoteport << "\n" <<
        "  remote IP:   " << remoteIP <<std::endl;

    if ( client.NetworkInit( localport ) != WOscClient::WOS_ERR_NO_ERROR ) {
        std::cout << "Exit."<<std::endl ;
        return -1;
    }
    
    std::cout << "Please enter some commands..."<<std::endl ;
    
    WOscBundle theBundle;
    bool bundleOpen = false;

    // run osc server as long the "exit" flag isn't set.
    while ( ! client.Exit() ) {
        char input[1024];
        std::cin.getline(input,1024,'\n');
        
        if ( input[0] == '/' ) {
            // message assembly
            try {
                // parse parameters
                char* params[100];
                int nParams = 0;
                char* next = strchr ( input, ' ' );
                while ( next != 0 || nParams >= 100 ) {
                    // add parameter to list 
                    params[nParams] = next + 1;
                    // and zero-terminate previous
                    *next = 0;
                    // try to find new parameter
                    next = strchr ( params[nParams++], ' ' );
                }
                
                // assemble message
                WOscMessage msg(input);
                // add parameters
                for ( int i = 0; i < nParams; i++ ) {
                    int parInt = atoi(params[i]);
                    float parFlt = atof(params[i]);
                    // check if float
                    if ( strchr ( params[i], '.' ) ) {
                        msg.Add( parFlt );
                    } else if ( parInt != 0 ) {
                        msg.Add( parInt );
                    } else {
                        msg.Add( params[i] );
                    }
                }
                // we pack message into bundle when open
                if ( bundleOpen ) {
                    theBundle.Add(new WOscMessage(msg));
                } else {
                    // set destination address
                    TheNetReturnAddress ra;
                    ra.m_addr.sin_family         = AF_INET;
                    ra.m_addr.sin_addr.s_addr    = inet_addr(remoteIP);
                    ra.m_addr.sin_port           = htons(remoteport);
                    client.NetworkSend(msg.GetBuffer(), msg.GetBufferLen(), &ra);
                }
            } catch(const WOscException& e ) {
                std::cout<<"Exception: " << e.GetErrStr() <<std::endl;
            }
        }
        // open bundle
        else if (input[0] == '[') {
            if ( bundleOpen ) {
                std::cout<<"Bundle already open."<<std::endl;
            } else {
                theBundle.Reset();
                bundleOpen = true;
            }
        }
        // open bundle
        else if (input[0] == ']') {
            if ( bundleOpen ) {
                // set destination address
                TheNetReturnAddress ra;
                ra.m_addr.sin_family         = AF_INET;
                ra.m_addr.sin_addr.s_addr    = inet_addr(remoteIP);
                ra.m_addr.sin_port           = htons(remoteport);
                client.NetworkSend(theBundle.GetBuffer(), theBundle.GetBufferLen(), &ra);
                bundleOpen = false;
            } else {
                std::cout<<"No bundle to close."<<std::endl;
            }
        }
        // other commands
        else {
            // exit command
            if ( strcmp("exit",input) == 0 ) {
                break;
            }
            // help command
            else if ( strcmp("help",input) == 0 ) {
                std::cout << WOSC_CLIENT_HELP_STR ;
            }
            // set local port (slp)
            else if  ( strncmp("slp",input,3) == 0 ) {
                localport = atoi( &input[4] );
                std::cout << "  Trying to set local port to " << localport << "."<<std::endl ;
                client.NetworkHalt();
                if ( client.NetworkInit( localport ) != WOscClient::WOS_ERR_NO_ERROR ) {
                    std::cout << "Exit."<<std::endl ;
                    return -1;
                }
                std::cout << "  Port successfully changed and network restarted."<<std::endl ;
            }
            // set remote port (srp)
            else if  ( strncmp("srp",input,3) == 0 ) {
                remoteport = atoi( &input[4] );
                std::cout << "  Remote port set to " << remoteport << "."<<std::endl ;
            }
            // set remote IP (srip)
            else if  ( strncmp("srip",input,4) == 0 )
            {
                strcpy(remoteIP, &input[5]);
                std::cout << "  Remote IP set to " << remoteIP << "."<<std::endl ;
            }
        }
#if OS_IS_LINUX == 1 || OS_IS_MACOSX == 1 || OS_IS_CYGWIN == 1
        usleep(100000);
#elif OS_IS_WIN32 == 1
        Sleep(100);
#endif
        client.CheckForPackets();
    }

    // stop network layer
    client.NetworkHalt();
    
    std::cout << "Exiting. Bye." <<std::endl;
    return 0;
}
Generated on Sat Oct 23 03:05:59 2010 for WOscLib by  doxygen 1.6.3