Service Discovery
Service Discovery was added with Version 2.0. It allows you to find and connect to a ServerStart within your local network.
You will need two basic classes to get this going. First, you will require a "ServiceDiscoveryHub".
You can provide it like this:
int port = 8888;
ServiceDiscoverHub hub = ServiceDiscoveryHub.open(port);
try {
hub.listen();
} catch(SocketException e) {
e.printStackTrace();
}
This hub will now listen to any UDP-Messages and then tries to match them. Without anything else, you can be found, but no one will know anything about you. To specify this, you have to change the example a bit work:
int port = 8888;
int targetPort = 4444;
ServiceDiscoverHub hub = ServiceDiscoveryHub.open(port, targetPort);
try {
hub.listen();
} catch(SocketException e) {
e.printStackTrace();
}
This ServiceDiscoveryHub will now tell anyone, that tries to find it, that whatever he is "hubbing" can be found on the targetPort within the network.
So, how exactly do we now request the ServiceDiscovery?
This can be done with the ServiceDiscoverer interface. This is used like this:
ServiceDiscoverer discoverer = ServiceDiscoverer.open(8888);
try {
discoverer.findServiceHubs();
} catch (SocketException e) {
e.printStackTrace();
}
"But Thorben", i hear you ask. "My program only terminates and does nothing, what am i missing?". Don't worry, we will fix this. First, let the current Thread sleep. The method findServiceHubs is working asynchronously and will therefor not block! The second thing we have to do, is to provide a callback, that will actually print out what we found. Let's just change the ServiceDiscoveryHub
ServiceDiscoveryHub hub = ServiceDiscoveryHub.open(8888, 4444);
try {
discoverer.listen();
} catch (SocketException e) {
e.printStackTrace();
}
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
The call Thread.currentThread().join() will let the thread you are running this on, wait until said Thread is finished; It will never terminate naturally. Since this is an example, i will let it slip through. Now for the ServiceDiscoverer.
ServiceDiscoverer discoverer = ServiceDiscoverer.open(8888);
discoverer.onDiscover(serviceHubLocation -> System.out.println("Found a service hub pointing to " + serviceHubLocation.getAddress() + ":" + serviceHubLocation.getPort()));
try {
discoverer.findServiceHubs();
} catch (SocketException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
So, why is this asynchronous?
Upon calling findServiceHubs, a broadcast request is send through the Network. We don't know if an when it will reach any Hub and if and when any response will come back. Therefor we start a Thread, that listens for a request/response, but we do not stop until a response has come back. This Thread will actually run until we kill the JVM, or until we call discoverer.close()
or hub.terminate()
respectively. If we now run our example, we will get an output, maybe even multiple outputs. The will look something like this:
Found a service hub at /192.168.178.155:4444
Obviously, the IP is just an example. Yours may vary from this, based on your local network configuration.
You can utilize this form of Service Discovery very easily to connect a ClientStart and a ServerStart. We would change the ServiceHub example in the following way:
First, let's introduce a ServerStart to the ServiceDiscoveryHub example.
ServerStart serverStart = ServerStart.at(4444);
serverStart.addClientConnectedHandler(System.out::println);
serverStart.launch();
new Thread(() -> {
try {
serverStart.acceptAllNextClients();
} catch (ClientConnectionFailedException e) {
e.printStackTrace();
}
}).start();
ServiceDiscoveryHub hub = ServiceDiscoveryHub.open(8888);
hub.connect(serverStart);
try {
discoverer.listen();
} catch (SocketException e) {
e.printStackTrace();
}
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
We did a few things here. We created a ServerStart at the previous targetPort (4444) and started a Thread to acceptAllNextClients. We then no longer tell the ServiceDiscoveryHub which is the targetPort. Instead we connect the hub and the ServerStart. To validate, that we connect, i also added a ClientConnectedHandler, that will print out any Client that connect. The idea is, that this Hub will tell every request, where the connected ServerStart can be found and we can see a console println with a Client.
For the ServiceDiscoverer, we can do the following:
ServiceDiscoverer discoverer = ServiceDiscoverer.open(8888);
discoverer.onDiscover(serviceHubLocation -> {
System.out.println("Found a ServiceHub!");
ClientStart clientStart = serviceHubLocation.toClientStart();
try {
clientStart.launch();
} catch (StartFailedException e) {
e.printStackTrace();
}
discoverer.close();
});
try {
discoverer.findServiceHubs();
} catch (SocketException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
The only thing that changed, is the "onDiscover" callback. In here we request a ClientStart by stating serviceHubLocation.toClientStart()
and launch it afterwards. Then we close the discoverer. "Why is that?", you might ask. We are making broadcast attempts though all of our network interfaces. This may result in multiple responses, because we have multiple network interfaces. The result may be something like this:
NativeClient{sessionValue=NativeSession{identifiedValue=false, identifierValue=}}
This tells us, that we successfully connected! Of course in this example we are missing multiple things. Like handling the StartFailedException correctly.
What you cant see, is the exchange of the Hub and the Discoverer. The Discoverer sends out a simple String NET_COM_SERVICE_DISCOVER_REQUEST
. The Hub takes this String, checks if it is exactly this String and if, answers with it own String. This might look like this:
NET_COM_SERVICE_DISCOVER_REQUEST;STATUS:200,TARGET:4444,SERVER_NAME:NO_NAME
This String is then processed by the Discoverer and, if everything is okay, the callback will be informed.
You can change this String, to match your needs. For example, let's add the current running time to it and only accept a Server, that run for at least 5 seconds. For the ServiceDiscoveryHub, we have have to provide a header entry like this:
ServerStart serverStart = ServerStart.at(4444);
serverStart.addClientConnectedHandler(System.out::println);
serverStart.launch();
long timeRunning = System.currentTimeMillis();
new Thread(() -> {
try {
serverStart.acceptAllNextClients();
} catch (ClientConnectionFailedException e) {
e.printStackTrace();
}
}).start();
ServiceDiscoveryHub hub = ServiceDiscoveryHub.open(8888);
hub.addHeaderEntry(header -> header.addEntry("TIME_RUNNING", Long.toString(System.currentTimeMillis() - startTime)));
hub.connect(serverStart);
try {
discoverer.listen();
} catch (SocketException e) {
e.printStackTrace();
}
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
All we do is, that we take the currentTimeMillis after launch and then, add the according header entry. On the ServiceDiscoverer, we have to parse this Header entry. We can do it like this:
ServiceDiscoverer discoverer = ServiceDiscoverer.open(8888);
discoverer.addHeaderMapping("TIME_RUNNING", (s, discoveryProcessingRequest) -> {
String value = s;
// We can access the header through the discoveryProcessingRequest like this
// The String s however is exactly the corresponding header entry.
// String value = discoveryProcessingRequest.header().get("TIME_RUNNING");
long timeRunning = Long.valueOf(value);
return TimeUnit.MILLISECONDS.toSeconds(timeRunning) >= 5;
});
discoverer.onDiscover(serviceHubLocation -> {
System.out.println("Found a ServiceHub!");
ClientStart clientStart = serviceHubLocation.toClientStart();
try {
clientStart.launch();
} catch (StartFailedException e) {
e.printStackTrace();
}
discoverer.close();
});
try {
discoverer.findServiceHubs();
} catch (SocketException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
} catch(InterruptedException e) {
e.printStackTrace();
}
Here we add the HeaderMapping for our TIME_RUNNING entry. We return true, if the Server has run for more than 5 seconds, else false. If we now connect to the Server faster than in 5 seconds, we get the following output:
[YOUR-TIME] (NetComThread[priority=7, number=1]) : WARN : >>> Header Entry marked as faulty: TIME_RUNNING:2007. This implies no Location found.
[YOUR-TIME] (NetComThread[priority=7, number=1]) : WARN : >>> Unknown broadcast response. Ignoring ..
But if we patiently wait the 5 seconds, the example works as intended.
This is a rather stupid example, i know, but you can easily add anything to it.
This will most definitely be relocated into it's own separate Maven project! If you use this and update your version, you should take a look at what maven-module to include. The package will be the same, but you will have to specify the maven-module to use it.
Introduction
Advanced
Outdated
Further Reading
Versions explained
Examples