CFNetServices is a Core Services API that allows you to register a network service, such as a printer or file server. By registering a service, it can be found by name or browsed for by service type and domain. Applications can use the CFNetServices API to discover the services that are available on the network and to find all access information — such as name, IP address, and port number — needed to use each service.
The CFNetServices API uses the Core Services framework to provide access to Bonjour. It should be used when programming in C or C++ to write applications that need to discover services. If your C or C++ program uses a run loop, use this API.
Version 10.2 or later of Mac OS X is required.
The CFNetServices API provides access to Bonjour through three objects:
CFNetService
- An object that represents a single service on the network. A CFNetService has a name, a type, a domain, and a port number. Service types used by CFNetServices are maintained at http://www.dns-sd.org/servicetypes.html.
CFNetServiceBrowser
- An object used to discover domains and discover network services within domains.
CFNetServiceMonitor
- An object used to monitor services for changes to their TXT records.
Publishing a service on the network involves two tasks: creating a service, and registering a service. The next two sections will describe what is required to perform these two tasks.
The function that creates a CFNetService (CFNetServiceCreate
) requires you to provide the following parameters that describe the service:
Name — human-readable name of the service (such as “Sales Laser Printer
”)
Service Type — the type of service, such as “_printer._tcp
“;
Domain — the domain for the service, typically the empty string (CFSTR("")
)for default domain(s), or local.
for the local domain only
Port — the port number the service listens on
Note: The dot in “local.
“ is part of the domain name. It signifies that the domain is fully qualified, which prevents anything from being added to the end of the domain (for example, local.com
).
If you are implementing a protocol that relies on data stored in DNS text records, you can associate that information with a CFNetService by calling a separate CFNetServices function (CFNetServiceSetTXTData
). Prior to Mac OS X v10.4, use the deprecated function CFNetServiceSetProtocolSpecificInformation
instead.
Associate a callback function with your CFNetService by calling CFNetServices function CFNetServiceSetClient
. Your callback function will be called to report errors that occur while your service is running.
If you want the service to run asynchronously, you must also schedule the CFNetService on a run loop by calling CFNetServiceScheduleWithRunLoop
; otherwise, the service will run synchronously. For more information about the modes in which a service can run, see“Asynchronous and Synchronous Modes.”
Below is some example code for how to create a CFNetService.
Listing 1 Creating a CFNetService
CFNetService netService = CFNetServiceCreate(NULL, CFSTR(""), serviceType, serviceName, chosenPort); |
To make a service available on the network, call the CFNetServices function CFNetServiceRegisterWithOptions
that registers a CFNetService. (This process is also known as “publishing” a service.) A CFNetServiceBrowser will be able to find the service until the service is stopped by the CFNetServices function CFNetServiceCancel
that terminates services.
Please see Listing 2 for sample code on this subject.
Listing 2 Registering an Asynchronous Service
void startBonjour (CFNetServiceRef netService) { |
CFStreamError error; |
CFNetServiceClientContext clientContext = { 0, NULL, NULL, NULL, NULL }; |
CFNetServiceSetClient(netService, registerCallback, &clientContext); |
CFNetServiceScheduleWithRunLoop(netService, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); |
CFNetServiceRegister(netService, NULL); |
if (CFNetServiceRegister(netService, &error) == false) { |
CFNetServiceUnscheduleFromRunLoop(netService, CFRunLoopGetCurrent(),kCFRunLoopCommonModes); |
CFNetServiceSetClient(netService, NULL, NULL); |
CFRelease(netService); |
fprintf(stderr, "could not register Bonjour service"); |
} |
} |
To browse for services represented by a CFNetService, call the CFNetServices function that creates CFNetServiceBrowsers, CFNetServiceBrowserCreate
. When you create a CFNetServiceBrowser, you need to provide a pointer to your callback function, which will be called when services are found.
If you want searches to be conducted asynchronously, you must also schedule the CFNetServiceBrowser on a run loop withCFNetServiceBrowserScheduleWithRunLoop
.
To browse for services you can call the CFNetServices function CFNetServiceBrowserSearchForServices
and specify the services to search for. For the domain parameter, you have two options. It is recommended that you pass the empty string (CFSTR("")
) as the domain, allowing you to discover services in any domain on which your system is registered. Alternatively, you can specifying a domain to search in. Your callback function will be called and passed a CFNetService representing a matching service. The CFNetServiceBrowser will continue searching until your application stops the search by calling CFNetServiceBrowserStopSearch
.
For each CFNetService that your callback function receives, you can call a CFNetServices function CFNetServiceResolveWithTimeout
to update the CFNetService with the IP address for the service. Prior to Mac OS X v10.4, use the deprecated function CFNetServiceResolve
instead of CFNetServiceResolveWithTimeout
. Then call the CFNetService function CFNetServiceGetAddressing
to get a CFArray containing a CFDataRef for each IP address associated with the service. Each CFDataRef consists of a sockaddr
structure containing an IP address.
A good example of how to browse for services can be seen in Listing 3.
Listing 3 Browsing Asynchronously for Services
static Boolean MyStartBrowsingForServices(CFStringRef type, CFStringRef domain) { |
CFNetServiceClientContext clientContext = { 0, NULL, NULL, NULL, NULL }; |
CFStreamError error; |
Boolean result; |
assert(type != NULL); |
gServiceBrowserRef = CFNetServiceBrowserCreate(kCFAllocatorDefault, MyBrowseCallBack, &clientContext); |
assert(gServiceBrowserRef != NULL); |
CFNetServiceBrowserScheduleWithRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); |
result = CFNetServiceBrowserSearchForServices(gServiceBrowserRef, domain, type, &error); |
if (result == false) { |
// Something went wrong so lets clean up. |
CFNetServiceBrowserUnscheduleFromRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFRelease(gServiceBrowserRef); |
gServiceBrowserRef = NULL; |
fprintf(stderr, "CFNetServiceBrowserSearchForServices returned (domain = %d, error = %ld)\n", error.domain, error.error); |
} |
return result; |
} |
Once you have a name, type and domain, you can resolve the service to retrieve its host name and port. Like registering a service, resolving a service also needs to create a CFNetService reference by calling CFNetServiceCreate
. Beginning in Mac OS X v10.4, When you call CFNetServiceCreate
it is important to pass a valid domain that was obtained when the service was first detected. Starting with Mac OS X v10.4, if you pass an empty CFString for the domain argument, Bonjour will not equate it with the domain local.
as it did in releases of Mac OS X prior to v10.4.
If you plan to resolve a service asynchronously, you should then associate the newly created CFNetServiceRef
with a callback function, which will receive a CFNetServiceRef
, and a pointer to a CFStreamError
. The callback association is performed by calling CFNetServiceSetClient
and following it with CFNetServiceScheduleWithRunLoop
to add the service to a run loop. To use the current run loop, pass CFRunLoopGetCurrent()
as a parameter.
After setting up the run loop, call the function CFNetServiceResolve
, ensuring that it does not return an error. If an error is returned, then you should clean up all the references you created. Otherwise just wait for your callback functions to be called.
An example of resolving a service with CFNetService is in Listing 4.
Listing 4 Resolving a Service Asynchronously
static void MyResolveService(CFStringRef name, CFStringRef type, CFStringRef domain) |
{ |
CFNetServiceClientContext context = { 0, NULL, NULL, NULL, NULL }; |
CFTimeInterval duration = 0; // use infinite timeout |
CFStreamError error; |
gServiceBeingResolved = CFNetServiceCreate(kCFAllocatorDefault, domain, type, name, 0); |
assert(gServiceBeingResolved != NULL); |
CFNetServiceSetClient(gServiceBeingResolved, MyResolveCallback, &context); |
CFNetServiceScheduleWithRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); |
if (CFNetServiceResolveWithTimeout(gServiceBeingResolved, duration, &error) == false) { |
// Something went wrong so lets clean up. |
CFNetServiceUnscheduleFromRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); |
CFNetServiceSetClient(gServiceBeingResolved, NULL, NULL); |
CFRelease(gServiceBeingResolved); |
gServiceBeingResolved = NULL; |
fprintf(stderr, "CFNetServiceResolve returned (domain = %d, error = %ld)\n", error.domain, error.error); |
} |
return; |
} |
CFNetServiceMonitor
, which debuted in Mac OS X v10.4, gives developers the ability to watch services for changes to their TXT records. In order to monitor a service asynchronously, you need to follow the same general formula that is used for resolving a service.
Once you have a CFNetServiceRef
for a service, you need to create a monitor reference (CFNetServiceMonitorRef
using the function CFNetServiceMonitorCreate
. Like resolving a service, the next step is to schedule the monitor reference on a run loop with CFNetServiceMonitorScheduleWithRunLoop
. The default run loop can be obtained by calling CFRunLoopGetCurrent()
.
After the monitor reference has been added to a run loop, you can start the monitor with the function CFNetServiceMonitorStart
passing it the monitor reference. Make sure to check the return value to ensure that the monitor has started.
When you have finished monitoring a service, call CFNetServiceMonitorStop
to stop the monitoring, CFNetServiceMonitorUnscheduleFromRunLoop
to unschedule your monitor from its run loop and CFNetServiceMonitorInvalidate
to destroy the monitor reference.
Several CFNetServices functions can operate in asynchronous or synchronous mode. Scheduling a CFNetService or a CFNetServiceBrowser on a run loop causes the service or browser to operate in asynchronous mode. If a CFNetService or a CFNetServiceBrowser is not scheduled on a run loop, it operates in synchronous mode. Operating in asynchronous mode changes the behavior of functions.
While it is possible to use the synchronous modes of these functions, please keep in mind that it is unacceptable to block the user interface or other functions of your program while you wait for synchronous functions to return. Due to the arbitrary amount of time network operations may last, it is highly recommended that you use the asynchronous modes of each function.
Function | Asynchronous mode | Synchronous mode |
---|---|---|
| Starts the registration and returns. The callback function for the CFNetService will be called to report any errors that occur while the service is running. The service is available on the network until your application cancels the registration. | Blocks until your application cancels the service from another thread or until an error occurs, at which point the function returns. The error is returned in an error structure pointed to by a parameter of the |
| Starts the resolution and returns. The callback function for the CFNetService will be called to report any errors that occur during resolution. The resolution process runs until the specified timeout is reached, or, if the timeout was specified as zero, until it is canceled. | Blocks until at least one IP address is found for the service, an error occurs, the time specified as the timeout parameter is reached or your application cancels the resolution, at which point the function returns. If an error occurs, the error is returned in an error structure pointed to by a parameter to the |
Starts the search and returns. The callback function for the CFNetServiceBrowser will be called for each domain that is found and to report any errors that occur while browsing. Browsing continues to run until your application stops the browsing. | Blocks until an error occurs or your application calls | |
Starts the search and returns. The callback function for the CFNetServiceBrowser will be called for each CFNetService that is found and to report any errors that occur while browsing. Browsing continues to run until your application stops the browsing. | Blocks until an error occurs or until your application calls |
To shut down a service that is running in asynchronous mode, your application unschedules the service from all run loops it may be scheduled on and then calls CFNetServiceSetClient
with the clientCB
parameter set to NULL
to disassociate your callback function from the CFNetService. Then call CFNetServiceCancel
to stop the service. If the service is running in synchronous mode, you only need to call CFNetServiceCancel
from another thread.
Listing 5 shows a good example of how to shut down an asynchronous CFNetService Resolve process which has not timed out.
Listing 5 Canceling an Asynchronous CFNetService Resolve Process
void MyCancelResolve() |
{ |
assert(gServiceBeingResolved != NULL); |
CFNetServiceUnscheduleFromRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); |
CFNetServiceSetClient(gServiceBeingResolved, NULL, NULL); |
CFNetServiceCancel(gServiceBeingResolved); |
CFRelease(gServiceBeingResolved); |
gServiceBeingResolved = NULL; |
return; |
} |
To shut down a browser that is running in asynchronous mode, your application unschedules the browser from all run loops it may be scheduled on and then calls CFNetServiceBrowserInvalidate
. Then your application calls CFNetServiceBrowserStopSearch
. If the browser is running in synchronous mode, you only need to call CFNetServiceBrowserStopSearch
. An example of these functions can be seen in Listing 6.
Listing 6 Stop Browsing for Services
static void MyStopBrowsingForServices() |
{ |
assert(gServiceBrowserRef != NULL); |
CFNetServiceBrowserStopSearch(gServiceBrowserRef, &streamerror); |
CFNetServiceBrowserUnscheduleFromRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); |
CFNetServiceBrowserInvalidate(gServiceBrowserRef); |
CFRelease(gServiceBrowserRef); |
gServiceBrowserRef = NULL; |
return; |
} |
Last updated: 2010-03-24