Adding New Broker Service Access Points
I was talking with the developers of the Broker today and we realized that it was about time to add in a dial direct access mode for the services that register themselves with the Broker. Up to now, all services would register themselves with the Broker via a single socket connection, and then multiplex that socket with many channels, each identified by a unique 16-byte ID. This is all well and good, but should there be a problem on one of these channels that's non-recoverable, the entire socket will be dropped, and with it all the channels. Not ideal.
The solution is to have a service open up a ephemeral port, and then send that data to the Broker in the registration message so that the Broker can use that additional listener as a different way to establish connections to the service. Think of it as "dial direct, and save".
In boost asio, this is pretty simple. In the service handler code I added a listen() method so that calling this would create a new acceptor, bind it, and start it listening. Additionally, I'd pull out the ephemeral port from the acceptor so I could later send it to the Broker.
bool MMDServiceHandler::listen() { bool error = false; // first, let's make sure we have what we need... a Boss... if (!error && (mBoss == NULL)) { error = true; cLog.error("[listen] the MMDService is missing... please check on this."); } // create an acceptor if we don't already have one if (!error && (mAcceptor == NULL)) { mAcceptor = new boost::asio::ip::tcp::acceptor(mBoss->mIOService); if (mAcceptor == NULL) { error = true; cLog.error("[listen] unable to create acceptor - very bad news."); } } // if we're OK, then fire up the acceptor for listening - pretty basic if (!error) { using namespace boost::asio::ip; // make the 'endpoint' in boost terms - IPv4 and a random port tcp::endpoint ep(tcp::v4(), 0); // open up the acceptor and set SO_REUSEADDR to enabled mAcceptor->open(ep.protocol()); mAcceptor->set_option(tcp::acceptor::reuse_address(true)); // ...now bind the bad boy and start listening mAcceptor->bind(ep); mAcceptor->listen(); // get the port so we can log what we've done tcp::endpoint lep = mAcceptor->local_endpoint(); mPort = lep.port(); // create a new MMDClientProxy to populate when we get hit MMDCLientProxy *proxy = new MMDCLientProxy(this, mAcceptor->io_service()); // now fire off the async accept on this guy mAcceptor->async_accept(proxy->getSocket(), boost::bind(&MMDServiceHandler::handleIncomingConnection, this, boost::asio::placeholders::error, proxy)); } return !error; }
The only real tricky point was that the endpoint I created with the port of zero wasn't modified by boost for the port number that was actually used. I needed to go into the acceptor, get it's local endpoint, and then from it, get the port. Took me a few minutes to figure that one out - not a lot of documentation on that point.
With this, and the existing code in the handler to handle the multiplexed channel connections, it wasn't too bad to make this new client proxy a fully functioning "portal" to the service. Pretty nice.
Now we need to update the Broker to vend this information, and then the client code has to be updated to read it and make the direct connection. Pretty neat stuff.