Bidirectional OSC

Working on an application that requires arbitrary connections between any number of iPads and any number of apple tvs in a showroom. I’m using bonjour to handle all the service discovery and whatnot, and thought OSC might be the go for the actual command sending. There’s multiple channels of communication that need to happen which i thought OSC would be suited for. /handshake/* for anything related to pairing, /control/* to any control specific commands etc.

So i assume that the application will need both a osc::SenderTcp and a osc::ReceiverTcp to handle the incoming and outgoing messages. I can see that there’s a constructor for each of them that takes an externally created TCPSocketRef but I can’t for the life of me get it to work. It probably doesn’t help that I know next to nothing about either asio or osc itself. :confused:

tcp::endpoint endpoint = tcp::endpoint ( asio::ip::address_v4::from_string ( "127.0.0.1" ), definition.Port ); // Tried 0.0.0.0 as well

_tcpTransport = std::make_shared<tcp::socket>( App::get()->io_service() );
_tcpTransport->open( endpoint.protocol(), error );
_tcpTransport->bind( endpoint );

_receiver = std::make_unique<osc::ReceiverTcp>( _tcpTransport ); 
_sender = std::make_unique<osc::SenderTcp>( _tcpTransport, endpoint );

There’s an error upon construction that is sent to the callback, and then one upon each subsequent call to _sender->send ( ... ) which is Tcp Send: Socket is not connected - Code: 57

Am I missing something simple, or is this just not how OSC is intended to be used and I should write a custom communication layer directly on top of ASIO?

Thanks,

A.

Hi,

I’m not so familiar with these neither, just some thoughts:
since the network environment sounds like pretty stable, any specific reason you have to use TCP?
I think if you go with UDP you should be able to have them setup easily.

*updated
And on a close look at your code, shouldn’t the sender/receiver use different ports?

-seph

1 Like

Indeed, just broadcast over UDP. That way you can also run sender and receiver on a single computer, making testing easier. OSC messages are usually small enough to not cause any bandwidth problems when broadcasted.

See also Cinder’s OSC samples for more information.

I’m currently working on similar things, and also chose the UDP route with broadcasting. Seems to work fine so far.

I’ll probably have to use a combination of TCP and UDP. There’s certain messages like remotely controlling a 3D camera position where reliability isn’t that important. but there’s other one’s like pairing that the messages absolutely have to get through. I’ve since found I was missing a call to ``::accept` that was the problem in this specific case, but there’s a host of other issues to work through. Thanks for your help, everyone.

Sorry to reopen a very old thread, but I was just now trying to do the same thing as @lithium . I don’t see a way to do it without exposing a new (public) method on SenderTcp:

    TcpSocketRef getSocket() const { return mSocket; }

This is because you need the (already connected) socket of the client’s SenderTcp in the OnConnectFn that’s called when connect() succeeds, so you can create a ReceiverTcp that uses the same socket.

In this way, you can do bidirectional OSC communication on a single TCP socket. (Or am I missing some other way to do it without modifying the OSC Cinderblock?)

On the other (server) side, everything works without any changes required, because you receive the new TcpSocketRef as an argument in the ReceiverTcp::accept() handler, so you can instantiate a new SenderTcp using that socket and its remote endpoint…

Alternatively, the OnConnectFn could receive the newly-connected socket as an argument (as the OnAcceptFn already does), but this would affect a lot of existing code.

Thanks,
Glen.

Just to follow-up to myself… (-;

No, we don’t need to add a getSocket() method on SenderTcp (although that would be handy). Instead, you can just create your own socket first (saving it for later use):

    using protocol = asio::ip::tcp;
    uint16_t const localPort{ 0 }; // this will make it allocate a free port
    mSocket = std::make_shared<protocol::socket>(app::App::get()->io_service(),
        protocol::endpoint(protocol::v4(), localPort));

Now use these to initialize your sender (surrounded by appropriate try/catch, as it may throw), e.g:

    std::string const serverHost{ "127.0.0.1" }; // or wherever
    uint16_t const serverPort{ 11339 }; // wherever the server is listening
    auto endPoint = protocol::endpoint(asio::ip::address::from_string(serverHost),
        serverPort);
    mOscSender = std::make_unique<osc::SenderTcp>(mSocket, endPoint);

Then, after you’ve called connect(), in the callback (if it succeeded), create your receiver using the same mSocket you created and stored previously:

    mOscReceiver = std::make_unique<osc::ReceiverTcp>(mSocket);

Then set your listeners as desired… Finally – it works great!

Hope this is helpful for someone else,
Glen.

2 Likes