Bonjour Discovery sample (Delphi)

Overview

This sample demonstrates how to use the Bonjour service discovery mechanism for both publishing and discovering services on the network. Bonjour is a facility similar to and compatible with ROZeroConf shipped with the Remoting SDK.


Prerequisites

The sample requires ZeroConf facilities Bonjour for Windows running on the host.

Getting Started

  • Build and launch the server application and click Start Server on its form to activate it.
  • Build and launch the client application. If your server is discoverable, you will see the corresponding record in the services table view. It may take a couple of seconds for the client to discover the service.
  • Once the service is discovered, select it and click the Use service button. Check the Communication log window below to see how the client communicates with the service.
  • Click Service Info to get information about the discovered service.
  • Shut down the sample server and the corresponding service record should disappear from the list.

Examine the code

Server side

The Bonjour Discovery server is a plain RemObjects SDK server with two operations that return a welcome message and information about the service. TROZeroConfRegistration is a mandatory component to register the service in Bonjour/ZeroConf service providers. To get this component to work, it is necessary to change its properties: Domain to local. and Server to the appropriate server channel. Also notice the following RegisterForZeroConf call (RODL-based) or ROZeroConfService attribute (CodeFirst-based) in the service implementation file.

RegisterForZeroConf(fClassFactory,'_bonjourdiscoverableservice_rosdk._tcp.');
[ROZeroConfService('_bonjourdiscoverableservice')] // `_rosdk._tcp.` will be added automatically 

This line must be added manually to specify the desired (and correct) service type, _bonjourdiscoverableservice_rosdk._tcp.

Client side

The Bonjour Discovery client contains the TROZeroConfBrowser component that is responsible for adding and removing discovered services.
First, notice the two properties of the ROZeroConfBrowser component that must be set properly to start the discovery:

  • Domain should be set to local.
  • ServiceType should contain the proper service type description matching the corresponding line from the server side service implementation file, _bonjourdiscoverableservice_rosdk._tcp.

There are two main events: OnServiceAdded is raised when a new service is found, OnServiceRemoved is raised when a service is no longer available. Please be aware that those events are executed on a background thread so no GUI operations are allowed from those handlers. To work around this, we use Windows messages:

procedure TClientForm.ROZeroConfBrowserServiceAdded(
  Sender: TROZeroConfBrowser; aRecord: TROZeroConfService);
begin
     SendMessage(Handle, UM_SYNCHRONIZE, integer(@ProcessServiceAdded), integer(aRecord));
end;

procedure TClientForm.ROZeroConfBrowserServiceRemoved(
  Sender: TROZeroConfBrowser; aRecord: TROZeroConfService);
begin
     SendMessage(Handle, UM_SYNCHRONIZE, integer(@ProcessServiceRemoved), integer(aRecord));
end;

Finally, the events processing ends up in calling one of two methods:

procedure ProcessServiceAdded(Param: TObject);
var aRecord: TROZeroConfService;
    newItem: TListItem;
    host: string;
begin
     aRecord := TROZeroConfService(Param);
     with ClientForm do begin
          Cursor := crHourGlass;
          LogMessage('Discovered new service: ' + aRecord.ServiceName);

          // ...

          // Resolving is an important step, it must be completed before the service can be used
          if aRecord.TryResolve then begin
             host := StringReplace(aRecord.HostTarget, LOCAL_SUFFIX, '', [rfIgnoreCase]);
             LogMessage(Format('Service resolved. Host name: %s Port number: %d',
                               [host, aRecord.Port]));
             newItem.SubItems.Add(host);
             newItem.SubItems.Add(IntToStr(aRecord.Port));
          end
          else begin
             LogMessage('Service resolve failed!');
             newItem.SubItems.Add(NOT_RESOLVED);
             newItem.SubItems.Add(NOT_RESOLVED);
          end;
          Cursor := crDefault;
     end;
end;

procedure ProcessServiceRemoved(Param: TObject);
var i: integer;
    aRecord, oldRec: TROZeroConfService;
begin
     aRecord := TROZeroConfService(Param);
     with ClientForm do begin
          LogMessage(Format('Service has gone down: %s (at host %s:%d)',
                            [aRecord.ServiceName,
                             StringReplace(aRecord.HostTarget, LOCAL_SUFFIX, '', [rfIgnoreCase]),
                             aRecord.Port]));
          for i := 0 to lvServices.Items.Count - 1 do begin
              oldRec := TROZeroConfService(lvServices.Items[i].Data);
              if (aRecord.ServiceName = oldRec.ServiceName) and
                 (aRecord.HostTarget = oldRec.HostTarget) and
                 (aRecord.Port = oldRec.Port)
              then begin
                  // Notice: oldRec is not Freed here, library will do it itself.
                  lvServices.Items.Delete(i);
                  SetButtonsEnable;
                  Break;
              end;
          end;
     end;
end;

The following code shows how the client can create and use the selected service instance:

procedure TClientForm.btUseServiceClick(Sender: TObject);
var aRecord: TROZeroConfService;
    serv: IBonjourDiscoverableService;
begin
     aRecord := TROZeroConfService(lvServices.Items[lvServices.ItemIndex].Data);
     if aRecord.Resolved then begin
        ROChannel.TargetURL := Format('http://%s:%d/bin', [aRecord.Address, aRecord.Port]);
        serv := RORemoteService as IBonjourDiscoverableService;
        try
           LogMessage('---');
           LogMessage('Calling IntroduceMyself at URL ' + ROChannel.TargetURL);
           LogMessage('Got responce: ' + string(serv.introduceMyself('Delphi client')));
           LogMessage('Calling WhoAreYou. Server responds: ' + string(serv.WhoAreYou));
           LogMessage('Communication finished.');
           LogMessage('---');
        except
           on e: Exception do LogMessage('Exception was raised: ' + e.Message);
        end;
     end
     else LogMessage('Cannot connect to server - service was not resolved.');
end;