Android network service discovery
I had troubles setting up network service discovery on Android and using bonjour (or bonjour-service) on nodejs server. I had troubles setting up just a basic usage, following android documentation and npm package documentation.
After lots of debugging and eventually diffing the network packets of npm packages and mDNS/DNS-SD (a tool which worked) using Wireshark, I was able to figure out what’s the problem. The bonjour and bonjour-service are missing host
field when registring/publishing your service!
I’m now sharing my code to get you up to start if you’re having problems or starting up with network service discovery (NSD) on android.
On the server
package.json
{
"name": "nsd-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"bonjour-service": "^1.0.11"
}
}
…or just use npm install bonjour-service
.
index.js
const { Bonjour } = require('bonjour-service');
const bonjour = new Bonjour();
bonjour.publish({
name: 'NsdChatServer',
type: 'nsdchat',
host: 'NsdChatServer.local',
protocol: 'tcp',
port: 3000,
});
Don’t forget the host
property!
On the Android app
AbstractServiceDiscovery.java
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.util.Log;
import java.net.InetAddress;
public abstract class AbstractServiceDiscovery {
private static final String TAG = "AbstractServiceDiscovery";
private final NsdManager nsdManager;
private final String serviceType;
private final String serviceName;
private final NsdManager.DiscoveryListener discoveryListener;
private final NsdManager.ResolveListener resolveListener;
public AbstractServiceDiscovery(NsdManager nsdManager, String serviceType, String serviceName) {
this.nsdManager = nsdManager;
this.serviceType = serviceType;
this.serviceName = serviceName;
this.discoveryListener = new DiscoveryListener();
this.resolveListener = new ResolveListener();
}
/**
* Starts discovering services.
*
* When service has been successfully found, {@link #onServiceDiscovered(InetAddress, int)} will
* be called with appropriate host and port information.
*
* If there is any issue during discovery, {@link #onServiceDiscoveryFailed()} will be called.
*/
public void startDiscoveringServices() {
nsdManager.discoverServices(serviceType, NsdManager.PROTOCOL_DNS_SD, this.discoveryListener);
}
/**
* Stops the service discovery.
*/
public void stopDiscoveringServices() {
nsdManager.stopServiceDiscovery(this.discoveryListener);
}
/**
* Called when service has been discovered.
*
* @param address Host address of remote server
* @param port Host port of remote server
*/
protected abstract void onServiceDiscovered(InetAddress address, int port);
/**
* Called when service discovery has been started.
*
*/
protected abstract void onServiceDiscoveryStarted();
/**
* Called when discovery fails for any reason.
*/
protected abstract void onServiceDiscoveryFailed();
public class ResolveListener implements NsdManager.ResolveListener {
@Override
public void onResolveFailed(NsdServiceInfo service, int errorCode) {
Log.e(TAG, "Failed resolving remote service. Error code: " + errorCode);
AbstractServiceDiscovery.this.onServiceDiscoveryFailed();
}
@Override
public void onServiceResolved(NsdServiceInfo service) {
Log.d(TAG, "Remote service successfully resolved. Host: " + service.getHost().toString() + " port: " + service.getPort());
AbstractServiceDiscovery.this.onServiceDiscovered(service.getHost(), service.getPort());
}
};
public class DiscoveryListener implements NsdManager.DiscoveryListener {
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Failed discovering remote services. Error code: " + errorCode);
AbstractServiceDiscovery.this.onServiceDiscoveryFailed();
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(TAG, "Failed discovering remote services. Error code: " + errorCode);
nsdManager.stopServiceDiscovery(AbstractServiceDiscovery.this.discoveryListener);
AbstractServiceDiscovery.this.onServiceDiscoveryFailed();
}
@Override
public void onDiscoveryStarted(String s) {
Log.d(TAG, "Started discovering remote service...");
AbstractServiceDiscovery.this.onServiceDiscoveryStarted();
}
@Override
public void onDiscoveryStopped(String serviceType) {
Log.d(TAG, "Discovering remote service stopped");
}
@Override
public void onServiceFound(NsdServiceInfo service) {
Log.d(TAG, "Discovered remote service with service type " + service.getServiceType() + " and name " + service.getServiceName());
if (service.getServiceType().equals(serviceType)) {
Log.d(TAG, "Resolving service...");
nsdManager.resolveService(service, resolveListener);
}
}
@Override
public void onServiceLost(NsdServiceInfo service) {
Log.d(TAG, "Service lost while discovering remote service");
}
}
}
ServerDiscovery.java
import android.net.nsd.NsdManager;
import com.github.kklisura.android.sensor.client.network.AbstractServiceDiscovery;
import java.net.InetAddress;
public class ServerDiscovery extends AbstractServiceDiscovery {
public ServerDiscovery(NsdManager nsdManager) {
super(nsdManager, "_nsdchat._tcp.", "NsdChatServer");
}
@Override
protected void onServiceDiscovered(InetAddress address, int port) {
// We got our service!
}
@Override
protected void onServiceDiscoveryStarted() {
// Started discovery...
}
@Override
protected void onServiceDiscoveryFailed() {
// Discovery failed for some reason
}
}
And you would use it like this in some activity:
ServerDiscovery serverDiscovery = new ServerDiscovery((NsdManager) getSystemService(Context.NSD_SERVICE));
serverDiscovery.startDiscoveringServices();
// serverDiscovery.stopDiscoveringServices();