[Cocoa] exploring cocoa's bonjour Network Programming

Source: Internet
Author: User

Bonjour network programming for cocoa

Luo chaohui (http://blog.csdn.net/kesalin)

CC license. For more information, see Source

This article is highly reference from Tutorial: Networking And bonjour on iPhone. In that post, the iPhone version uses the MIT open source protocol, so the Mac version in this example also uses the MIT open source protocol. We recommend that you read the original article.

This article uses bonjour to implement a simple server/client chat program, demonstrating the usage of cfsocket, nsnetservice/nsnetservicebrowser, and nsinstream/nsoutstream.

Download Code: click here
As follows:

Cocoa network framework:
The cocoa network framework has three layers. The bottom layer is the BSD socket library, followed by the C-based cfnetwork in cocoa, and the top layer is bonjour in cocoa. Usually we don't need to deal with socket. We will use cfnetwork And bonjour encapsulated by cocoa to do most of the work. Note: Many cocoa components have two implementations. One is a C-based class starting with CF (cf = core Foundation), which is relatively low-level; the other is an obj-C class that starts with NS (NS = Next Step). This class has a higher abstraction level and is easy to use. The same applies to network frameworks. Bonjour
Nsnetservice also has the cfnetservice, and nsinputstream has the cfinputstream.

Sockets vs streams:
A socket is equivalent to a communication channel. An application creates a socket and connects it to other applications for data exchange. We can use the same socket to send or receive data. Each socket has an IP address and port (Communication Port, ranging from 1 ~ Between 65535 ).

Stream is a one-way channel for data transmission. Because stream is one-way, we have two types of input/output: instream/outstream. Stream only temporarily caches data. We need to bind it to a file or memory to read/write data from/to the file or memory. In this tutorial, we use stream and socket to transmit and receive data on the network.

Bonjour introduction:
Bonjour (Hello in french) is a protocol that can automatically query devices or applications connected to the network. Bonjour abstracts the concepts of IP and port, allowing us to focus on services that are easier to understand for human thinking. Through bonjour, an application publish is a network service, and other programs in the network can automatically discover this service, so that they can query its IP address and port from this service, then, establish a socket link through the obtained IP address and port to communicate. Generally, we use nsnetservice and nsnetservicebrowser
To use bonjour. The former is used to establish and publish services, and the latter is used to listen to and query services on the network.

Synchronous and asynchronous operations:
Most network operations are blocked, such as connection establishment, waiting to receive data, or sending data to the other end of the network. Therefore, if we do not implement asynchronous processing, our UI will be blocked during network communication. There are two ways to deal with blocking: enable multiple threads or use the current thread more effectively. In this example, we use the next method. We use the run loop provided by cocoa to do this. The working principle is: the network messages are thrown into the current run loop as common events, so that we can process them asynchronously.

Run loops introduction:
Run loop is the message processing cycle in the thread. If there is an event, it will be processed. If there is no event, nothing will be done. The run loop in cocoa can process user UI messages, network connection messages, and timer messages. We can also add other message sources, such as socket and stream, so that run loop can process them as well.

Program Framework:
The theory has been introduced almost. For more details, please refer to the official document. Let's take a look at the Framework Design of the entire program:

It can be clearly seen that the program is divided into three main modules: Ui module, logic module, and network module. Next let's open the project and see the code implementation:

From the engineering drawing, we can see that the code structure is quite clear, and all classes are divided into four groups:
Networking: Network-related code, including socket creation, connection establishment, Service publish and browser;
Business Logic: Business logic-related code. In this example, we use room service to provide chat services. We create a localroom to create a server and publish a room service. The client (remoteroom) can connect to an existing room service to join the room for dialog activity.
UI: In this example, the UI is very simple. There are only two views. One shows the service list in the current network, the other shows the room and the conversation on the room service.
Misc: A helper class used to store user-defined names.

Network Type:
Server class: Creates a server and releases a service;
Connection class: Resolution Service; establish a connection with the server; exchange data through socket stream;
Serverbrowser class: queries available services;

Room class:
Room class: Room base class
Localroom class: Create a server, publish a service, and request the connection from the client.
Remoteroom: connect to an existing service on the server,

Network data transmission process:

It can be seen that the data is written to the write stream through the outgoing buffer on the logic layer of A, and then transmitted to the network layer through the socket, and then the read stream on the B side reads data from the socket, write the incoming buffer and display it on the logic layer and UI of B.

User interaction is performed on the UI Layer. When a user sends a chat message through broadcastchatmessage: fromuser: The logic layer determines that the message is sent to the server (processed by the remote room ), or to all clients connected to the server (processed by the local room ). When a chat message is received from a network connection, the logic layer receives a notification, and the client simply displays the message on the UI, the server first forwards the received chat information to all clients connected to it, and then displays the information on the UI.

Socket + streams + buffers = connection
The connection class encapsulates some interactions:
Two socket streams, one for writing and the other for reading; two data buffers, each socket stream corresponds to a data buffer; and various control flags and values

Because stream is unidirectional, we need to create two streams for each socket, one for reading data from the socket and the other for writing data to the socket. We initialize them in connect and setupsocketstreams.

In this example, we create a socket in two ways:
1. The (client) creates a socket to connect to the server with the specified IP address and port;
2. (server) receives connection requests from clients. In this case, the OS will automatically create a socket for response and pass it to us through Native socket handle;

No matter which method the socket is created, we initialize stream through the same code setupsocketstreams.

Create a server
A chat must run at least two macchatty terminals at the same time. At least one of them is used as the server, and other terminals can be used as clients to connect to the server for dialog. As a server terminal, you need to create a socket to listen to (Listen) connection requests from other terminals (see listeningsocket in sever class ). This work is done in createserver in the server class.

How does the client know how to connect to the server? Each network terminal must have unique IP addresses and ports. IP addresses are dynamically obtained or set by users. Therefore, we do not need to worry about IP addresses here, therefore, we use inaddr_any in the code. Then how can we set the port we want to listen? Some services can work only when listening for the agreed port, such as ports 80, 20, and 21. Here we will hand over the port setting problem to the OS for processing, and the OS will set a port for us that is not occupied. For this purpose, the input port is 0. To allow other clients to connect to the server, we need to inform other clients of the actual use of
Therefore, we can obtain the actual port used in Part 3 of the createserver method.

    //// PART 3: Find out what port kernel assigned to our socket    //    // We need it to advertise our service via Bonjour    NSData *socketAddressActualData = [(NSData *)CFSocketCopyAddress(listeningSocket) autorelease];        // Convert socket data into a usable structure    struct sockaddr_in socketAddressActual;    memcpy(&socketAddressActual, [socketAddressActualData bytes], [socketAddressActualData length]);        self.port = ntohs(socketAddressActual.sin_port);

In Part 4, we register the listening socket as the message source of the application run loop. When a new connection arrives, the OS will call the serveracceptcallback callback function to notify us.

    //// PART 4: Hook up our socket to the current run loop    //    CFRunLoopRef currentRunLoop = CFRunLoopGetCurrent();    CFRunLoopSourceRef runLoopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, listeningSocket, 0);    CFRunLoopAddSource(currentRunLoop, runLoopSource, kCFRunLoopCommonModes);    CFRelease(runLoopSource);

In the serveracceptcallback callback processing, we create a new connection object and bind it with the socket automatically created by the OS to respond to the new connection. Then pass the connection object to the server delegate.

// Handle new connections- (void) handleNewNativeSocket:(CFSocketNativeHandle)nativeSocketHandle{    Connection* connection = [[[Connection alloc] initWithNativeSocketHandle:nativeSocketHandle] autorelease];        // In case of errors, close native socket handle    if ( connection == nil ) {        close(nativeSocketHandle);        return;    }        // finish connecting    BOOL succeed = [connection connect];    if ( !succeed ) {        [connection close];        return;    }        // Pass this on to our delegate    [delegate handleNewConnection:connection];}// This function will be used as a callback while creating our listening socket via 'CFSocketCreate'static void serverAcceptCallback(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info){    // We can only process "connection accepted" calls here    if ( type != kCFSocketAcceptCallBack ) {        return;    }        // for an AcceptCallBack, the data parameter is a pointer to a CFSocketNativeHandle    CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;        Server *server = (Server *)info;    [server handleNewNativeSocket:nativeSocketHandle];        NSLog(@" >> server accepted connection with socket %d", nativeSocketHandle);}

Publish a service through Bonjour
Bonjour is not the only way to find a service on the network, but it is one of the easiest ways to use. Create an nsnetservice object in the publishservice method to publish the service. We can search for services that interest you on the network based on the service type. This chat service uses "_ chatty. _ TCP." As the service type. In the same network, the service type name must be unique in order to precisely locate the service without causing conflict.

Bonjour operations, like socket operations, need to be performed asynchronously to avoid long-time blocking of the main thread. Therefore, when the service is actually released, we will hand over the release task to the current run loop for scheduling, and then set its delegate for the delegate to handle related events: "publishing succeeded ", "publishing failed" and so on.

- (BOOL) publishService{    // come up with a name for our chat room    NSString* chatRoomName = [NSString stringWithFormat:@"%@'s chat room", [[AppConfig sharedInstance] name]];        // create new instance of netService self.netService = [[NSNetService alloc] initWithDomain:@"" type:@"_chatty._tcp." name:chatRoomName port:self.port];if (self.netService == nil)return NO;        // Add service to current run loop[self.netService scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];        // NetService will let us know about what's happening via delegate methods[self.netService setDelegate:self];        // Publish the service[self.netService publish];        return YES;}

Bonjour query service
The bonjour network service query function is implemented in the serverbrowser class. We create an nsnetservicebrowser object to query services with the type "_ chatty. _ TCP. When a service is added or removed from the current network, nsnetservicebrowser's delegate means our serverbrowser will be notified for corresponding logic processing: Updating the service list and refreshing the UI.

// New service was found- (void) netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser            didFindService:(NSNetService *)netService                moreComing:(BOOL)moreServicesComing{    // Make sure that we don't have such service already (why would this happen? not sure)    if ( ! [servers containsObject:netService] ) {        // Add it to our list        [servers addObject:netService];    }        // If more entries are coming, no need to update UI just yet    if ( moreServicesComing ) {        return;    }        // Sort alphabetically and let our delegate know    [self sortServers];    [delegate updateServerList];}// Service was removed- (void)netServiceBrowser:(NSNetServiceBrowser *)netServiceBrowser         didRemoveService:(NSNetService *)netService               moreComing:(BOOL)moreServicesComing{    // Remove from list    [servers removeObject:netService];        // If more entries are coming, no need to update UI just yet    if ( moreServicesComing ) {        return;    }        // Sort alphabetically and let our delegate know    [self sortServers];    [delegate updateServerList];}

Service by bonjour resolution
When you select a chat room and add it, the client will connect to the server that publishes the chat room service. This connection is implemented in the joinchatroom: Method of the chattyviewcontroller class. We recommend that you use the selected nsnetservice to send a resolvewithtimeout message to determine which server to connect to (refer to the last case in the connection method of the connection class ), meanwhile, set the nsnetservice's delegate to respond to the resolution-related events: didnotresolve:
And netservicedidresolveaddress :. After the resolution is complete, in the netservicedidresolveaddress: method, we can establish a socket connection to the service and create a stream for data transmission.

// Called when net service has been successfully resolved- (void)netServiceDidResolveAddress:(NSNetService *)sender{    if ( sender != netService ) {        return;    }        // Save connection info    self.host = netService.hostName;    self.port = netService.port;        // Don't need the service anymore    self.netService = nil;        // Connect!    if ( ![self connect] ) {        [delegate connectionAttemptFailed:self];        [self close];    }}

So far, the introduction to bonjour network programming is over. The comments in the Code are quite detailed, and the details are not too long.

To demonstrate the effect, we need to run two instances of the program. You can find the executable file in the following path:
/Users/username/library/developer/xcode/deriveddata/macchatty-xxxx/build/products/debug

References:
Tutorial: Networking And bonjour on iPhone: http://mobileorchard.com/tutorial-networking-and-bonjour-on-iphone/
Introduction to bonjour Overview: http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/NetServices/Introduction.html
Introduction to nsnetservices and cfnetservices programming guide: http://developer.apple.com/library/mac/#documentation/Networking/Conceptual/NSNetServiceProgGuide/Introduction.html#//apple_ref/doc/uid/TP40002736

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.