Section 9.1. Using the CFNetServices API


9.1. Using the CFNetServices API

You will access Bonjour functionality in CFNetwork using instances of CFNetService and CFNetServiceBrowser objects. There are nearly 40 functions in the API for working with Network Services objects, with descriptive names that convey their purpose. For example, CFNetServiceCreate and CFNetServiceRegister are used respectively to create an instance of CFNetService and then use it to register a particular service. Rather than repeat the details provided in the API documentation for each function, this section provides a summary and a quick example for how you use the CFNetServices API to register, browse, and resolve.

CFNetServices is a component of the CFNetwork framework, and, despite the "CF" prefix, these frameworks actually live within the CoreServices umbrella framework, not the Core Foundation umbrella framework. When creating a new project in Xcode, if you want to use the CFNetServices API, you can either use the "new CoreServices tool" project template or you can manually add the CoreServices framework to your project. CoreServices implicitly includes all of Core Foundation, so you don't need to add both.

9.1.1. Advertising a Service in CFNetServices

To publish a service using the CFNetServices API, you need to create and register a CFNetService. In the code in Example 9-1 (shown later), you create a service like this:

 registeredService = CFNetServiceCreate(kCFAllocatorDefault,     CFSTR(""),                    // Domain     CFSTR("_example._tcp"),       // Type     CFSTR("CF Example"),          // Name     thePort);                     // Port number in host byte order 

More generally, you call the function CFNetServiceCreate( ) and pass in the CFAllocator to use when allocating memory for the service you are creating. In the example, we use kCFAllocatorDefault. You also pass in a CFStringRef for the domain. Although you cannot pass in NULL, you can and should pass in the empty string unless there is a particular domain on which you want to register the service. You also pass in CFStringRef instances representing the type and name of the service. In this example, they are _example._tcp and CF Example, respectively. Finally, you pass in a UInt32 giving the port number in host byte order. Since the port you get from a sockaddr structure is an opaque identifier in network byte order, this means you have to swap it to host byte order before passing it to CFNetServiceCreate( ). When testing your code on a PowerPC machine, it's easy to get this wrong and not notice, because on a PowerPC, network byte order is the same as host byte order, so the distinction is a purely abstract one. If you have a bug, you will probably discover it when you compile your code for Intel, where network byte order and host byte order are different.

Another common mistake is to get the byte order wrong at both ends of the connection. Suppose you have a service listening on port 123. If you forget to convert the sockaddr's sin_port to host byte order before calling CFNetServiceCreate( ), then when you compile your code for Intel, you'll actually be advertising port 31488. If the client at the resolving end also gets the byte order backward, then it will swap 31488 back to 123 and successfully connect, and everything will appear to be working correctly. It's only when a PowerPC client tries to resolve and connect to a service on Intel, an Intel client tries to resolve and connect to a service on PowerPC, or you try to interoperate with someone else's client or service that doesn't have your byte order bug, that you'll discover problems. The easiest way to find these problems is using the dns-sd tool. After you compile your program for Intel, run it on an Intel-based machine and use dns-sd -L to verify that you're really advertising the port number you intend, not the byte-swap of the port number. This kind of byte order mismatch problem is not specific to advertising services using the DNS-SD APIsit can happen any time you're passing 16-bit or larger integer values on the wire between machines.

Once you have a CFNetServiceRef returned by CFNetServiceCreate( ), you then call CFNetServiceSetClient( ) to associate a callback function, which is used to report errors that may arise. In the MyRegisterCallBack( ) function below, you would put in any code needed to handle and report errors and follow it with a call to CFNetServiceCancel( ) to cancel the registration of the service instance. You will usually want the service to run asynchronously and should schedule the CFNetService on a run loop using CFNetServiceScheduleWithRunLoop( ).

The CFNetServiceRegister( ) routine only calls your callback if an error occurs. If you also want to receive a callback on success too (so you can find out what name was registered), you need to use the newer CFNetServiceRegisterWithOptions( ) call, which is available on Mac OS X 10.4 and later (or you can use the lower-level DNSServiceDiscovery C API, if your product needs to be able to run on 10.3).

The steps described above are used to create and configure a CFNetService. Registration is accomplished with a call to the function CFNetServiceRegister( ). All of this is shown in Example 9-1.

Example 9-1. Publishing a service with CFNetServices
 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <CoreServices/CoreServices.h> CFNetServiceRef gRegisteredService; static void MyCancelRegistration(void)     {     CFNetServiceUnscheduleFromRunLoop(gRegisteredService,         CFRunLoopGetCurrent(  ), kCFRunLoopCommonModes);     CFNetServiceSetClient(gRegisteredService, NULL, NULL);     CFRelease(gRegisteredService);     gRegisteredService = NULL;     } static void MyRegisterCallBack(CFNetServiceRef theService, CFStreamError* error, void* info)     {     if (error->domain == kCFStreamErrorDomainNetServices)         {         switch(error->error)             {             case kCFNetServicesErrorCollision:                 MyCancelRegistration(  );                 fprintf(stderr, "kCFNetServicesErrorCollision occurred\n");                 break;             default:                 MyCancelRegistration(  );                 fprintf(stderr, "MyRegisterCallBack (domain = %d, error = %ld)\n",                     error->domain, error->error);                 break;             }         }     } static Boolean MyRegisterService(u_short thePort)     {     CFNetServiceClientContext context = { 0, NULL, NULL, NULL, NULL };     CFStreamError error;     Boolean result;     printf("MyRegisterService advertising service on port %d\n", htons(thePort));     gRegisteredService = CFNetServiceCreate(kCFAllocatorDefault,         CFSTR(""), CFSTR("_example._tcp"), CFSTR("CF Example"), // Domain, type, name         thePort);     assert(gRegisteredService != NULL);     CFNetServiceSetClient(gRegisteredService, MyRegisterCallBack, &context);     CFNetServiceScheduleWithRunLoop(gRegisteredService,         CFRunLoopGetCurrent(  ), kCFRunLoopCommonModes);     result = CFNetServiceRegister(gRegisteredService, &error);     if (result == false) //clean up         {         MyCancelRegistration(  );         fprintf(stderr, "CFNetServiceRegister returned (domain = %d, error = %ld)\n",             error.domain, error.error);         }     return result;     } int main(int argc, char* argv[])     {     CFSocketRef s = CFSocketCreate(kCFAllocatorDefault,         PF_INET, SOCK_STREAM, IPPROTO_TCP,         kCFSocketNoCallBack, NULL, NULL);     struct sockaddr_in sa = { sizeof(sa), AF_INET };     CFDataRef addr = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,         (const UInt8*)&sa, sizeof(sa), kCFAllocatorNull);     CFSocketSetAddress(s, addr);     CFRelease(addr);     addr = CFSocketCopyAddress(s);     memmove(&sa, CFDataGetBytePtr(addr), sizeof(sa));     CFRelease(addr);     MyRegisterService(ntohs(sa.sin_port));     CFRunLoopRun(  );     return 0;     } 

This code can easily be built in Xcode by selecting File New Project... and choosing the option to make a new CoreServices Tool (near the bottom of the list of options). Open main.c, paste in Example 9-1s source code, and click the button to build and run the project. Your program should now be publishing a service of type _example._tcp with name CF Example. You should be able to discover your registered service using the code listed in Example 9-2, or by running dns-sd -B _example._tcp.

9.1.2. Browsing in CFNetServices

To browse for DNS-SD services, you will need to create a CFNetServiceBrowser . A CFNetServiceBrowserRef is returned by a call to the function CFNetServiceBrowserCre-ate( ). As was the case when programming with the socket-based DNSServiceDiscovery C API, one of the parameters you will need to pass into this function is the callback function. In Example 9-2, the callback function is the MyBrowseCallBack( ) function.

Example 9-2. Browsing for services using CFNetServices
 #include <CoreServices/CoreServices.h> CFNetServiceBrowserRef gBrowserService; CFMutableDictionaryRef gServiceDictionary; typedef struct     {     int refCount;     char name[64];     char type[24];     char domain[1005];     } MyService; CFStringRef MyCreateDictionaryKey(CFNetServiceRef service)     {     return CFStringCreateWithFormat(kCFAllocatorDefault, 0, CFSTR("%@.%@%@"),         CFNetServiceGetName(service),         CFNetServiceGetType(service),         CFNetServiceGetDomain(service));     } static void MyAddService(CFNetServiceRef service, CFOptionFlags flags)     {     CFStringRef dictKey = MyCreateDictionaryKey(service);     MyService *s;     // We need to do reference counting of each service because if the computer     // has two network interfaces set up, like Ethernet and AirPort, you may     // get notified about the same service twice, once from each interface.     // You probably don't want both items to be shown to the user.     // On Mac OS X 10.4 and later, the CFNetServices code does this reference     // counting for you, so you'll get at most one "add" event for a given     // name/type/domain, but if your code is also going to run on Mac OS X     // 10.3, you'll want to implement the reference counting as shown here.     if (CFDictionaryGetValueIfPresent(gServiceDictionary, dictKey, (const void **)&s)     == false)         {         s = malloc(sizeof(MyService));         assert(s != NULL);         s->refCount = 0;         CFStringGetCString(CFNetServiceGetName  (service), s->name,   sizeof(s->name),             kCFStringEncodingUTF8);         CFStringGetCString(CFNetServiceGetType  (service), s->type,   sizeof(s->type),             kCFStringEncodingUTF8);         CFStringGetCString(CFNetServiceGetDomain(service), s->domain, sizeof(s->domain),             kCFStringEncodingUTF8);         CFDictionarySetValue(gServiceDictionary, dictKey, (const void **)s);         printf("ADD %s.%s%s\n", s->name, s->type, s->domain);         }     s->refCount++;     CFRelease(dictKey);     } static void MyRemoveService(CFNetServiceRef service, CFOptionFlags flags)     {     CFStringRef dictKey = MyCreateDictionaryKey(service);     MyService *s;     if (CFDictionaryGetValueIfPresent(gServiceDictionary, dictKey, (const void **)&s))         {         s->refCount--;         if (s->refCount == 0)             {             CFDictionaryRemoveValue(gServiceDictionary, dictKey);             printf("RMV %s.%s%s\n", s->name, s->type, s->domain);             free(s);             }         }     CFRelease(dictKey);     } static void MyBrowseCallBack(CFNetServiceBrowserRef theService,     CFOptionFlags flags, CFTypeRef service, CFStreamError* err, void* info)     {     if (err->error)         fprintf(stderr, "MyBrowseCallBack %d,%ld\n", err->domain, err->error);     else if (flags & kCFNetServiceFlagRemove)         MyRemoveService((CFNetServiceRef)service, flags);     else         MyAddService((CFNetServiceRef)service, flags);     } static Boolean MyBrowseService(  )     {     CFNetServiceClientContext context = { 0, NULL, NULL, NULL, NULL };     CFStreamError error;     Boolean result;     gServiceDictionary = CFDictionaryCreateMutable(kCFAllocatorDefault,         0, &kCFCopyStringDictionaryKeyCallBacks, NULL);     assert(gServiceDictionary != NULL);     gBrowserService = CFNetServiceBrowserCreate(kCFAllocatorDefault,         MyBrowseCallBack, &context);     assert(gBrowserService != NULL);     CFNetServiceBrowserScheduleWithRunLoop(gBrowserService,         CFRunLoopGetCurrent(  ), kCFRunLoopCommonModes);     result = CFNetServiceBrowserSearchForServices(gBrowserService,         CFSTR(""), CFSTR("_example._tcp"), &error);     if (result == false) //clean up         {         CFNetServiceBrowserUnscheduleFromRunLoop(gBrowserService,             CFRunLoopGetCurrent(  ), kCFRunLoopCommonModes);         CFRelease(gBrowserService);         gBrowserService = NULL;         fprintf(stderr, "CFNetServiceBrowserSearchForServices returned %d, %ld)\n",             error.domain, error.error);         }     return result;     } int main(int argc, char* argv[])     {     MyBrowseService(  );     CFRunLoopRun(  );     return 0;     } 

For the most part, you will want to perform your searches asynchronously so that your application is not blocked while the search is in progress. You use the returned CFNetServiceBrowser in the asynchronous mode by calling the function CFNetSer-viceBrowserScheduleWithRunLoop( ). Without making this call before searching for domains and services, you will be searching in the synchronous mode and the functions used to search will block until there are search results. The callback function is called when there are search results, but in the synchronous mode you need to stop the search by calling CFNetServiceBrowserStopSearch( ) from a separate thread.

After starting the browser in asynchronous mode, you search for domains using CFNetServiceBrowserSearchForDomains( ) and for services using CFNetServiceBrows-erSearchForServices( ). Be sure to perform the appropriate cleanup of resources. So, for example, if either of these functions returns false, you should call CFNetService-BrowserUnScheduleFromRunLoop( ) and release the memory for the CFNetServiceBrowserRef.

Example 9-2 shows how you can search for services of type _example._tcp. The MyBrowseService( ) function follows the steps outlined for browsing for services. The callback function MyBrowseCallBack( ) uses the functions CFNetServiceGetName( ), CFNetServiceGetType( ), and CFNetServiceGetDomain( ) to retrieve the name, type, and domain of the discovered service.

To run the code given in Example 9-2, run Xcode, select File New Project... and then choose CoreServices Tool. Open main.c, paste in the source code in Example 9-2, and click the button to build and run the project. Your program should now be browsing for services of type _example._tcp. To test it, use the code given in Example 9-1 to advertise a service, or else use dns-sd:

 dns-sd -R "CF Example" _example._tcp "" 123 

Your browser should discover the advertised service and print out:

 ADD CF Example._example._tcp.local. 

Kill off the command-line process and your browser should report:

 RMV CF Example._example._tcp.local. 

You'll see that this code adds discovered services to a dictionary. If it discovers a service already in its dictionary, then instead of showing it twice, it just increments a reference count. The reason for this is that if your machine has more than one active interface, you may discover the same service via both interfaces. The CFNetServices API doesn't indicate upon which interface a service was discovered, so although you can't show this information to the user, you can use reference counting to avoid showing the same thing twice. If you want to be able to display the interface index, name, or icon to the user, you can do that using the lower-level DNSServiceDiscovery C API.

You'll also see that this code allows up to 1,005 bytes for a domain name. This is the maximum possible length that a legal domain name can be after escaping non-printable characters using the normal DNS escaping rules. Newer versions of the /usr/include/dns_sd.h header file define a constant kDNSServiceMaxDomainName for this value.

9.1.3. Resolving in CFNetServices

To resolve a service, first create a CFNetService object that contains the name, type, and domain of the service you wish to resolve. Unlike registering and browsing, this time you do need to give a specific domain. In a real program, you'd be resolving a service you discovered as a result of browsing, so you'd use the name, type, and domain for the service you learned in your browse callback.

Use CFNetServiceSetClient( ) to assign the callback function and use CFNetService-ScheduleWithRunLoop( ) to perform the resolution asynchronously. Pass in the reference to the service to be resolved to the function CFNetServiceResolve( ). When the answer(s) are available, the MyResolveCallBack( ) function is called.

Note that you are very likely to receive IPv6 addresses as well as IPv4 addresses in your callback function. There's no need to be afraid of IPv6 addresses or to take special steps to filter them out. Just pass the sockaddr structure unchanged to the connect( ) system call (or equivalent) and you'll get back a working TCP connection to that address and port, just as with IPv4.

After you've got the information in the callback(s) and used that information to establish a successful TCP connection, remember to cancel your resolve call. If you don't, it will continue to transmit queries on the network, trying to find alternate IP addresses for the target without realizing that you've already succeeded in connecting to it.

The entire process of resolving a service is shown in Example 9-3.

Example 9-3. Resolving a service with CFNetServices
 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <CoreServices/CoreServices.h> static void MyResolveCallBack(CFNetServiceRef service, CFStreamError* error, void* info)     {     int count;     CFArrayRef addresses = CFNetServiceGetAddressing(service);     assert(addresses != NULL);     assert(CFArrayGetCount(addresses) > 0);     // May get more than one reply     for (count = 0; count < CFArrayGetCount(addresses); count++)         {         char addr[256];         struct sockaddr_in *sa = (struct sockaddr_in *)             CFDataGetBytePtr(CFArrayGetValueAtIndex(addresses, count));         // inet_ntop will correctly display both IPv4 and IPv6 addresses         if (inet_ntop(sa->sin_family, &sa->sin_addr, addr, sizeof(addr)))             printf("%s:%d \n", addr, ntohs(sa->sin_port));         }     } static void MyResolveService(  )     {     CFNetServiceClientContext context = { 0, NULL, NULL, NULL, NULL };     CFStreamError error;     CFNetServiceRef serviceBeingResolved = CFNetServiceCreate(kCFAllocatorDefault,         CFSTR("local."), CFSTR("_example._tcp"), CFSTR("CF Example"), 0);     assert(serviceBeingResolved != NULL);     CFNetServiceSetClient(serviceBeingResolved, MyResolveCallBack, &context);     CFNetServiceScheduleWithRunLoop(serviceBeingResolved,         CFRunLoopGetCurrent(  ), kCFRunLoopCommonModes);     if (CFNetServiceResolve(serviceBeingResolved, &error) == false)         { // Something went wrong so lets clean up.         CFNetServiceUnscheduleFromRunLoop(serviceBeingResolved,             CFRunLoopGetCurrent(  ), kCFRunLoopCommonModes);         CFNetServiceSetClient(serviceBeingResolved, NULL, NULL);         CFRelease(serviceBeingResolved);         serviceBeingResolved = NULL;         fprintf(stderr, "CFNetServiceResolve returned %d, %ld\n",             error.domain, error.error);         }     } int main(int argc, char* argv[])     {     MyResolveService(  );     CFRunLoopRun(  );     return 0;     } 

Just as with the other examples, make a new CoreServices Tool, paste in the code, and then compile and run it.

Now advertise a service called CF Example of type _example._tcp:

 dns-sd -R "CF Example" _example._tcp "" 123 

As you do, you'll see that your resolve call succeeds and prints out the list of possible addresses for this service. It may appear that CFNetServices is giving you duplicate addresses. In fact, what's happening is that each time CFNetServices gets new results for you, it will call you back again, giving the entire array of addresses, including both the old ones you've seen before and the new ones you haven't. For this reason, you may see the same addresses multiple times as the array grows bigger and bigger with each callback.

There's no guarantee that all the addresses you see will be reachable. Also, some may offer faster performance than otherse.g., an address on Gigabit Ethernet is likely to give a lot faster connection than an address on AirPort. In an ideal program, you'd attempt connections to all of the possible addresses simultaneously to see which one succeeds fastest, and then as soon as one succeeds, cancel the other outstanding attempts.




Zero Configuration Networking. The Definitive Guide
Zero Configuration Networking: The Definitive Guide
ISBN: 0596101007
EAN: 2147483647
Year: 2004
Pages: 97

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net