Applied Informatics Zeroconf Library

Zeroconf User Guide

Contents

Introduction

The Zeroconf library is Applied Informatics implementation of the Zeroconf standard (http://zeroconf.org), based on Apple's open-source Bonjour (or alternatively, Avahi) implementation.

Zeroconf allows to

Requirements

Before being able to work with Zeroconf one must install the mdnsResponder library from Apple.

Windows (Visual Studio)

Unix/Linux

Zeroconf Library

The central class of the Zeroconf library is the Poco::Zeroconf::DNSServiceDiscovery singleton class which handles service lookup. To register your own services with Zeroconf use the Poco::Zeroconf::LocalService class.

DNSServiceDiscovery

DNSServiceDiscovery is a singleton object that acts as entry point for the Zeroconf operations browse, resolve, and domain searching. It offers the following methods:

static DNSServiceDiscovery& instance();
    /// Returns the instance of the singleton.

DNSServiceRef browse(const std::string& regType, const std::string& domain);
    /// Browses for a service on all network interfaces restricted by regType 
    /// and domain (domain can be empty).
    /// Results will be sent asnychronously via the EBrowseError, EServiceFound, 
    /// EServiceRemoved events.

DNSServiceRef browse(const std::string& regType, const std::string& domain, 
	const Poco::Net::NetworkInterface& iface);
    /// Browses for a service on the defined network interface restricted by 
    /// regType and domain (domain can be empty).
    /// Results will be sent asnychronously via the EBrowseError, EServiceFound, 
    /// EServiceRemoved events.

DNSServiceRef resolve(const ServiceInfo& info, bool searchOnAllInterfaces);
    /// Resolves a ServiceInfo. Call browse to retrieve the ServiceInfo required 
    /// as first parameter here.
    /// Results will be sent asnychronously via the EResolveError and 
    /// EServiceResolved events.

DNSServiceRef discoverDomainsForBrowsing(const Poco::Net::NetworkInterface& iface);
    /// Discovers domains for browse operations on the defined interface 
    /// (use interface with index 0 to search on all interfaces)
    /// Results will be sent asnychronously via the EBrowseDomainFound, 
    /// EBrowseDomainRemoved and EBrowseDomainError events.

DNSServiceRef discoverDomainsForRegister(const Poco::Net::NetworkInterface& iface);
    /// Discovers domains for service registration on the defined interface (use 
    /// interface with index 0 to search on all interfaces)
    /// Results will be sent asnychronously via the ERegisterDomainFound, 
    /// ERegisterDomainRemoved and ERegisterDomainError events.

DNSServiceRef discoverDomainsForBrowsing();
    /// Discovers domains for browse operations on all network interfaces
    /// Results will be sent asnychronously via the EBrowseDomainFound, 
    /// EBrowseDomainRemoved and EBrowseDomainError events.

DNSServiceRef discoverDomainsForRegister();
    /// Discovers domains for service registration on all network interfaces
    /// Results will be sent asnychronously via the ERegisterDomainFound, 
    /// ERegisterDomainRemoved and ERegisterDomainError events.

void remove(DNSServiceRef ref);
    /// Stops the activity assigned with the DNSServiceRef, 
    /// invalidates the reference

void shutdown();
    /// Stops all activity. Invalidates all DNSServiceRefs.
    /// THIS METHOD MUST BE INVOKED RIGHT BEFORE YOU EXIT YOUR APPLICATION!

The singleton is retrieved via the instance method and must be destroyed before application exit with the shutdown method. Each of the browse, resolve, discoverDomainsForBrowsing and discoverDomainsForRegister starts an asynchronous search that is identified via a DNSServiceRef. The search can be stopped via the remove operation.

Notification of search results is done via the events that are part of the public DNSServiceDiscovery interface:

Poco::BasicEvent<ServiceError>                  EBrowseError;    
    /// An error happend while browsing
Poco::BasicEvent<std::pair<ServiceInfo, bool> > EServiceFound;   
    /// 1st param contains serviceInfo, 2nd param tells if more events will follow immediately
Poco::BasicEvent<std::pair<ServiceInfo, bool> > EServiceRemoved; 
    /// 1st param contains serviceInfo, 2nd param tells if more events will follow immediately

Poco::BasicEvent<ServiceError>                          EResolveError;    
    /// An error happend while trying to resolve a service
Poco::BasicEvent<std::pair<ResolvedServiceInfo, bool> > EServiceResolved; 
    /// 1st param contains serviceInfo, 2nd param tells if more events will follow immediately

Poco::BasicEvent<std::pair<DomainInfo, bool> >  EBrowseDomainFound;   
    /// 1st param contains the detected domain, 2nd param tells if more events will follow immediately
Poco::BasicEvent<std::pair<DomainInfo, bool> >  EBrowseDomainRemoved; 
    /// 1st param contains the detected domain, 2nd param tells if more events will follow immediately
Poco::BasicEvent<DomainError>                   EBrowseDomainError;   
    /// In case an error occurs while parsing for domains, this event signales the errorNumber

Poco::BasicEvent<std::pair<DomainInfo, bool> >  ERegisterDomainFound;   
    /// 1st param contains the detected domain, 2nd param tells if more events will follow immediately
Poco::BasicEvent<std::pair<DomainInfo, bool> >  ERegisterDomainRemoved; 
    /// 1st param contains the detected domain, 2nd param tells if more events will follow immediately
Poco::BasicEvent<DomainError>                   ERegisterDomainError;   
    /// In case an error occurs while parsing for domains, this event signals the errorNum

To simplify registration for these events two listener interfaces were defined:

You can register the listeners via the following DNSServiceDiscovery methods:

void addListener(DiscoveryListener& listener);
    /// Convenience function which registers the listener at all service specific events
    /// Note that you don't need to implement the DiscoveryListener interface
    /// If you have a class that offers some methods with the same signature you 
    /// can register them manually at the individual events.

void removeListener(DiscoveryListener& listener);
    /// Convenience function which removes the listener from all events.

void addListener(DomainListener& listener);
    /// Convenience function which registers the listener at all domain specific events. 
    /// Note that you don't need to implement the DomainListener interface
    /// If you have a class that offers some methods with the same signature you 
    /// can register them manually at the individual events.

void removeListener(DomainListener& listener);
    /// Convenience function which removes the listener from all events

DiscoveryListener

Poco::Zeroconf::DiscoveryListener defines the following interface:

class Zeroconf_API DiscoveryListener
    /// A DiscoveryListener handles DNSServiceDiscovery events for 
    /// browse/resolve commands
{
public:
    DiscoveryListener();
        /// Creates the DiscoveryListener.

    virtual ~DiscoveryListener();
        /// Destroys the DiscoveryListener.

    virtual void onBrowseError(const void* pSender, ServiceError& err) = 0;
        /// Called whenever an error occurred.

    virtual void onNewService(const void* pSender, 
    	std::pair<ServiceInfo, bool>& aService) = 0;
        /// Called whenever a new service is discovered. ServiceInfo contains 
        /// the service description, while the boolean value simply tells if 
        /// more events will follow (can be used as hint for GUI applications 
        /// to minimize redraws).
        /// Note that you will receive for each interface on which the service 
        /// was discovered a notify.

    virtual void onServiceRemoved(const void* pSender, 
    	std::pair<ServiceInfo, bool>& aService) = 0;
        /// A previously found service was removed. Called for each interface on 
        /// which the service was removed.

    virtual void onResolveError(const void* pSender, ServiceError& err) = 0;
        /// Called whenever an error occurred.

    virtual void onServiceResolved(const void* pSender, 
    	std::pair<ResolvedServiceInfo, bool>& aService) = 0;
        /// Called whenever a new service is resolved. ResolvedServiceInfo 
        /// contains an extended service description, while the boolean value
        /// simply tells if more events will follow (can be used as hint for GUI 
        /// applications to minimize redraws).
        /// Note that you will receive for each interface on which the service was 
        /// discovered a notify.

    void enable();
        /// Enables event receiving. Call this method in the code of your 
        /// subclass.

    void disable();
        /// Disables event receiving. You must call this method, before your 
        /// object is destroyed.
};

The enable method simply registers the DiscoveryListener at the DNSServiceDiscovery object, disable unregisters it.

DomainListener

Poco::Zeroconf::DomainListener defines the following interface:

class Zeroconf_API DomainListener
    /// Convenience class to describe to domain specific Events 
    /// at DNSServiceDiscovery.
{
public:
    DomainListener();
        /// Creates the DomainListener.

    virtual ~DomainListener();
        /// Destroys the DomainListener.

    virtual void onBrowseDomainFound(const void* pSender, 
    	std::pair<DomainInfo, bool>& arg) = 0;
        /// Called whenever a new Domain is found, 2nd value of arg, specifies 
        /// if more events will follow immediately

    virtual void onBrowseDomainRemoved(const void* pSender, 
    	std::pair<DomainInfo, bool>& arg) = 0;
        /// Called whenever a Domain is removed, 2nd value of arg, specifies 
        /// if more events will follow immediately

    virtual void onBrowseDomainError(const void* pSender, DomainError& errCode) = 0;
        /// Called whenever an error occurred while searching for BrowseDomains

    virtual void onRegisterDomainFound(const void* pSender, 
    	std::pair<DomainInfo, bool>& arg) = 0;
        /// Called whenever a new Domain is found, 2nd value of arg, specifies 
        /// if more events will follow immediately

    virtual void onRegisterDomainRemoved(const void* pSender, 
    	std::pair<DomainInfo, bool>& arg) = 0;
        /// Called whenever a Domain is removed, 2nd value of arg, specifies 
        /// if more events will follow immediately

    virtual void onRegisterDomainError(const void* pSender, DomainError& errCode) = 0;
        /// Called whenever an error occurred while searching for RegistrationDomains

    void enable(bool browseEvents = true, bool registerEvents = true);
        /// Enables event receiving. Call this method in the code of your subclass.

    void disable(bool browseEvents = true, bool registerEvents = true);
        /// Disables event receiving. You must call this method, before your object is destroyed.
};

Poco::Zeroconf::DomainListener distinguishes between events for domain browsing and domain registration. Simply pass the appropriate flags to the enable/disable methods.

LocalService

LocalService allows to register a local service with Zeroconf:

class Zeroconf_API LocalService
    /// The class LocalService offers functionality for service registration
{
public:
    Poco::FIFOEvent<std::string> EServiceRenamed; 
        /// Thrown whenever the service name changes. Will be thrown for a 
        /// successful registration (change from empty name to new name)
    Poco::FIFOEvent<std::string> EDomainChanged; 
        /// Thrown whenever the domain name changes. Will be thrown for a 
        /// successful registration (change from empty name to new name)
    Poco::FIFOEvent<Poco::Int32> ERegistrationFailed; 
        /// Returns the error code. When this Event is thrown the whole object 
        /// was reset to empty default values, including name and domain.

    LocalService();
        /// Creates the LocalService. 

    void init(bool remote, 
        bool enableAutoRenaming, 
        const Poco::Net::NetworkInterface& iface,
        const std::string& proposedName, 
        const std::string& regType, 
        const std::string& domain, 
        const std::string& hostName, 
        Poco::UInt16 port, 
        const Properties& prop);
        /// Initializes the LocalService, starts registration. Make sure that you are 
        /// registered to the Events before calling this method.
        /// remote specifies if the service is viewable in other sub networks or only on localhost.
        /// If enableAutoRenaming is set to true, and in the network another service is 
        /// found with the same name, your service will be automatically renamed.
        /// If set to false and a conflict is found an ERegistrationFailed event is thrown.
        /// proposedName can be empty, then a default name is chosen.
        /// Use the interface with index 0 to advertise a service on all network interfaces.
        /// Note that a remote value of false will override the interface value and limit it to 
        /// localhost only.
        /// regType is mandatory (see http://www.dns-sd.org/ServiceTypes.html for possible values)
        /// domain is optional and will be set to a default value (most likely: "local.") if empty
        /// hostName can be empty but you must specify a hostName when creating proxy 
        /// registrations for services running on other machines.
        /// prop allows to specify service specific properties

    ~LocalService();
        /// Destroys the LocalService.

    const std::string& getName() const;
        /// Returns the name of the service. Will be empty until the 
        /// service successfully registered.

    const std::string& getRegType() const;
        /// Returns the regtype of the service.

    const std::string& getDomain() const;
        /// Returns the domain the service is running in. Will be empty 
        /// until the service successfully registered.

    const std::string& getHostName() const;
        /// Returns the hostname which was passed to init

    const Properties& getProperties() const;
        /// Returns the properties which were passed to init

    Poco::UInt16 getPort() const;
        /// Returns the port which was passed to init

    DNSRecordRef addRecord(LocalService::RecordType type, Poco::UInt16 size, 
    	const void* pData, Poco::UInt32 ttl);
        /// Adds a record to a service, the maximum size of a record is 65535 bytes, 
        /// which is stored as opaque payload in pData.
        /// The returned value is guaranteed to be not equal 0.

    void updateRecord(DNSRecordRef ref, Poco::UInt16 size, 
    	const void* pData, Poco::UInt32 ttl);
        /// Updates a record, note that an update where ref = 0 will change the 
        /// services's primary txt record!

    void removeRecord(DNSRecordRef ref);
        /// Removes a record. ref must not be 0. This method will invalidate ref.
};

Registration itself happens asynchronously within the init method. Registration notifications are sent via the EServiceRenamed, EDomainChanged and ERegistrationFailed events. For convenience reasons, a listener interface was defined that handles these events.

LocalServiceListener

A Poco::ZerConf::LocalServiceListener handles all events of a Poco::Zeroconf::LocalService:

class Zeroconf_API LocalServiceListener
    /// A LocalServiceListener handles all events of a LocalService object
{
public:
    LocalServiceListener();
        /// Creates the LocalServiceListener.

    virtual ~LocalServiceListener();
        /// Destroys the LocalServiceListener.

    virtual void onServiceRenamed(const void* pSender, std::string& newServiceName) = 0;
        /// Invoked whenever the service name changes of the LocalService. 
        /// Called at least once for successful registration.

    virtual void onDomainChanged(const void* pSender, std::string& newDomainName) = 0;
        /// Invoked whenever the domain changes of the LocalService. 
        /// Called at least once for successful registration.

    virtual void onRegistrationFailed(const void* pSender, Poco::Int32& errorCode) = 0;
        /// Invoked whenever an error occurred in registering your LocalService. 
        /// Note that the LocalService members will already be cleared when this method 
        /// is invoked. Also LocalService is already unregistered.

    void enable(LocalService& aService);
        /// Registers for all events of the service. Never forget to unregister from a 
        /// LocalService via disable(...)

    void disable(LocalService& aService);
        /// Unregisters from all events of the service.

    void disable();
        /// Unregisters from all events for each service we previously registered. 
        /// This method must be called before the LocalServiceListener gets destroyed.
};

Call the enable method to register for the events of a LocalService, call disable to unregister. Note that in case of success you will always get at least one EServiceRenamed event (setting the initial name) and one EDomainChanged event or otherwise one ERegistrationFailed event!

Sample Code

We take the existing HTTPTimeServer sample from the Net samples and extend it with Zeroconf functionality. The full code is in Zeroconf/samples/HTTPTimeServer, we only present a simplified version here:

class HTTPTimeServer: public Poco::Util::ServerApplication
{
public:
    [...]

    int main(const std::vector<std::string>& args)
    {
        [...]

        // get parameters from configuration file
        unsigned short port = (unsigned short) config().getInt("HTTPTimeServer.port", 9980);

        // set-up a server socket
        ServerSocket svs(port);
        // set-up a HTTPServer instance
        HTTPServer srv(new TimeRequestHandlerFactory(format), svs, new HTTPServerParams);
        // start the HTTPServer
        srv.start();

        // Start Zeroconf specific initialization
        Properties none;
        _listener.enable(_service);
        // remote, weAllowRename, bind to all interfaces, proposed name is 
        // HTTPTimeServer, it is http over tcp, use default domain, use the 
        /// hostName as address, port, no properties defined
        _service.init(
        	true, 
        	true, 
        	Poco::Net::NetworkInterface::forIndex(0), 
        	"HTTPTimeServer", 
        	"_http._tcp", 
        	"", 
        	Poco::Environment::nodeName(), 
        	port, 
        	none);

        // wait for CTRL-C or kill
        waitForTerminationRequest();

        // Stop listening for events, shutdown Zeroconf
        _listener.disable();
        DNSServiceDiscovery::instance().shutdown();

        // Stop the HTTPServer
        srv.stop();

        return Application::EXIT_OK;
    }

private:
    LocalService _service;
    TimeServiceListener _listener;
};