Using CFNetServices
CFNetServices
is an API in the CFNetwork
framework (Core Foundation level) that allows you to publish or search for network services.
At a high level, the CFNetServices
API provides access to Bonjour through three objects:
CFNetService
—An object that represents a single service on the network. ACFNetService
object has a name, a type, a domain, and a port number. Service types used byCFNetServices
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 Using CFNetServices
Publishing a service on the network involves two tasks: creating a service and registering a service. The next two sections describe what is required to perform these two tasks.
Creating a CFNetService Object
To create a CFNetService
object, call CFNetServiceCreate
and provide the following information about the service:
Name. The 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), orlocal.
for the local domain onlyPort. The port number the service listens on
If you are implementing a protocol that relies on data stored in DNS text records, you can associate that information with a CFNetService
object by calling CFNetServiceSetTXTData
).
Associate a callback function with your CFNetService
object by calling CFNetServiceSetClient
. Your callback function is called to report errors that occur while your service is running and to report on the status of publication.
If you want the service to run asynchronously, you must also schedule the service 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, seeAsynchronous and Synchronous Modes.
Listing A-1 shows how to create a CFNetService
object.
Listing A-1 Creating a CFNetService
object
CFStringRef serviceType = CFSTR("_printer._tcp"); |
CFStringRef serviceName = CFSTR("Sales Laser Printer"); |
CFStringRef theDomain = CFSTR(""); |
int chosenPort = 515; |
CFNetServiceRef netService = CFNetServiceCreate(NULL, theDomain, serviceType, serviceName, chosenPort); |
Registering a CFNetService
To make a service available on the network, call CFNetServiceRegisterWithOptions
. (This operation is also known as “publishing” a service.) The service can then be found by clients until you unregister the service by calling CFNetServiceCancel
.
See Listing A-2 for sample code on this subject.
Listing A-2 Registering an asynchronous service
void registerCallback ( |
CFNetServiceRef theService, |
CFStreamError* error, |
void* info) |
{ |
// ... |
} |
void startBonjour (CFNetServiceRef netService) { |
CFStreamError error; |
CFNetServiceClientContext clientContext = { 0, NULL, NULL, NULL, NULL }; |
CFNetServiceSetClient(netService, registerCallback, &clientContext); |
CFNetServiceScheduleWithRunLoop(netService, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); |
CFOptionFlags options = 0; // or kCFNetServiceFlagNoAutoRename |
if (CFNetServiceRegisterWithOptions(netService, options, &error) == false) { |
CFNetServiceUnscheduleFromRunLoop(netService, CFRunLoopGetCurrent(),kCFRunLoopCommonModes); |
CFNetServiceSetClient(netService, NULL, NULL); |
CFRelease(netService); |
fprintf(stderr, "could not register Bonjour service"); |
} |
} |
Browsing for Services using CFNetServices
To browse for services represented by a CFNetService
object, call CFNetServiceBrowserCreate
and provide a pointer to a callback function that will be called as services are found.
If you want searches to be conducted asynchronously, you must also schedule the browser on a run loop by calling CFNetServiceBrowserScheduleWithRunLoop
.
To browse for services, you can call 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 specify a domain to search in. Your callback function will be called and passed a CFNetService
object representing a matching service. The CFNetServiceBrowser
object continues searching until your app stops the search by calling CFNetServiceBrowserStopSearch
.
For each CFNetService
object that your callback function receives, you can call CFNetServiceResolveWithTimeout
to update the service with the IP address for the service. Then call CFNetServiceGetAddressing
to get an array of CFDataRef
objects, one for each IP address associated with the service. Each CFDataRef
object, in turn, contains a sockaddr
structure with an IP address.
A good example of how to browse for services can be seen in Listing A-3.
Listing A-3 Browsing asynchronously for services
void MyBrowseCallBack ( |
CFNetServiceBrowserRef browser, |
CFOptionFlags flags, |
CFTypeRef domainOrService, |
CFStreamError* error, |
void* info) |
{ |
// ... |
} |
static Boolean MyStartBrowsingForServices(CFStringRef type, CFStringRef domain) { |
CFNetServiceClientContext clientContext = { 0, NULL, NULL, NULL, NULL }; |
CFStreamError error; |
Boolean result; |
assert(type != NULL); |
CFNetServiceBrowserRef 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 let's clean up. |
CFNetServiceBrowserUnscheduleFromRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); CFRelease(gServiceBrowserRef); |
gServiceBrowserRef = NULL; |
fprintf(stderr, "CFNetServiceBrowserSearchForServices returned (domain = %ld, error = %d)\n", (long)error.domain, error.error); |
} |
return result; |
} |
Resolving a Service Using CFNetServices
After you have a name, type, and domain, you can resolve the service to retrieve its hostname and port. As with registering a service, resolving a service also requires a CFNetServiceRef
object. If you already have one (typically obtained through a browse operation callback), you don’t need to take any further action. Otherwise, you can create one yourself by calling CFNetServiceCreate
.
If you plan to resolve a service asynchronously, associate the newly created CFNetServiceRef
object with a callback function, which will receive a CFNetServiceRef
object and a pointer to a CFStreamError
object. You can set this callback by calling CFNetServiceSetClient
. Be sure to call CFNetServiceScheduleWithRunLoop
afterward to add the service to a run loop (which is typically the result of a call to CFRunLoopGetCurrent
).
After setting up the run loop, call CFNetServiceResolve
. If this call returns an error, 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 A-4.
Listing A-4 Resolving a service asynchronously
void MyResolveCallback ( |
CFNetServiceRef theService, |
CFStreamError* error, |
void* info) |
{ |
// ... |
} |
static void MyResolveService(CFStringRef name, CFStringRef type, CFStringRef domain) |
{ |
CFNetServiceClientContext context = { 0, NULL, NULL, NULL, NULL }; |
CFTimeInterval duration = 0; // use infinite timeout |
CFStreamError error; |
CFNetServiceRef 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 let's clean up. |
CFNetServiceUnscheduleFromRunLoop(gServiceBeingResolved, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); |
CFNetServiceSetClient(gServiceBeingResolved, NULL, NULL); |
CFRelease(gServiceBeingResolved); |
gServiceBeingResolved = NULL; |
fprintf(stderr, "CFNetServiceResolve returned (domain = %ld, error = %d)\n", (long)error.domain, error.error); |
} |
return; |
} |
Monitoring a Service Using CFNetServices
CFNetServiceMonitor
lets you watch services for changes to TXT
records.
In the app that is publishing a service, you can provide a custom TXT
record by calling CFNetServiceSetTXTData
. The most straightforward way to provide spec-compliant data is by calling CFNetServiceCreateTXTDataWithDictionary
and passing it a dictionary of values to publish.
In the app that needs to monitor a service, perform the following steps:
After you have a
CFNetServiceRef
object for the service you wish to monitor, create a monitor reference (CFNetServiceMonitorRef
) by callingCFNetServiceMonitorCreate
.Schedule the monitor reference on a run loop with
CFNetServiceMonitorScheduleWithRunLoop
. (You can obtain the default run loop by callingCFRunLoopGetCurrent
.)Start the monitor by calling
CFNetServiceMonitorStart
, passing it the monitor reference and a callback function to handle the resulting data. Be sure to check the return value to ensure that the monitor started successfully.In the callback function, the most straightforward way to handle the
TXT
data is by callingCFNetServiceCreateDictionaryWithTXTData
to obtain a dictionary containing the original key-value pairs.
When you no longer need to monitor a service, call CFNetServiceMonitorStop
(to stop the monitoring), CFNetServiceMonitorUnscheduleFromRunLoop
(to unschedule your monitor from its run loop), and CFNetServiceMonitorInvalidate
(to destroy the monitor reference).
Asynchronous and Synchronous Modes
Several CFNetServices
functions can operate in asynchronous or synchronous mode. Scheduling a CFNetService
or CFNetServiceBrowser
object on a run loop causes the service or browser to operate in asynchronous mode. If a CFNetService
or CFNetServiceBrowser
object is not scheduled on a run loop, it operates in synchronous mode. Operating in asynchronous mode changes the behavior of its functions.
Although it is possible to use the synchronous modes of these functions, 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. Because network operations may take an arbitrary amount of time to complete, it is highly recommended that you use the asynchronous modes of each function.
Table A-1 describes the differences in behavior between synchronous and asynchronous modes.
Function | Asynchronous mode | Synchronous mode |
---|---|---|
| Starts the registration and returns. The callback function for the | Blocks until your app cancels the service from another thread or until an error occurs, at which point the function returns. If an error occurs, the error is returned through the provided error structure. The service is available on the network until your app cancels the registration or an error occurs. |
| Starts the resolution and returns. The callback function for the | 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 app cancels the resolution, at which point the function returns. If an error occurs, the error is returned through the provided error structure. The resolution operation continues to run until your app cancels it or an error occurs. |
| Starts the search and returns. The callback function for the | Blocks until an error occurs or your app calls |
| Starts the search and returns. The callback function for the | Blocks until an error occurs or until your app calls |
Shutting Down Services and Searches
To shut down a service that is running in asynchronous mode, your app unschedules the service from all run loops that it may be scheduled on. The app then calls CFNetServiceSetClient
with the clientCB
parameter set to NULL
to disassociate your callback function from the CFNetService
object. Finally, the app calls CFNetServiceCancel
to stop the service.
To shut down a service that is running in synchronous mode, your app only needs to call CFNetServiceCancel
from another thread.
Listing A-5 shows a good example of shutting down an asynchronous CFNetService
resolve operation that has not timed out.
Listing A-5 Canceling an asynchronous CFNetService
resolve operation
void MyCancelResolve(CFNetServiceRef gServiceBeingResolved) |
{ |
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 app unschedules the browser from all run loops that it may be scheduled on and then calls CFNetServiceBrowserInvalidate
. Then your app 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 A-6.
Listing A-6 Stop browsing for services
static void MyStopBrowsingForServices(CFNetServiceBrowserRef gServiceBrowserRef) |
{ |
CFStreamError streamerror; |
assert(gServiceBrowserRef != NULL); |
CFNetServiceBrowserStopSearch(gServiceBrowserRef, &streamerror); |
CFNetServiceBrowserUnscheduleFromRunLoop(gServiceBrowserRef, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); |
CFNetServiceBrowserInvalidate(gServiceBrowserRef); |
CFRelease(gServiceBrowserRef); |
gServiceBrowserRef = NULL; |
return; |
} |
Copyright © 2013 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2013-08-08