Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
iOS and OS X Network Programming Cookbook
iOS and OS X Network Programming Cookbook

iOS and OS X Network Programming Cookbook: If you want to develop network applications for iOS and OS X, this is one of the few books written specifically for those systems. With over 50 recipes and in-depth explanations, it's an essential guide.

eBook
£7.99 £32.99
Paperback
£41.99
Subscription
Free Trial
Renews at £16.99p/m

What do you get with a Packt Subscription?

Free for first 7 days. £16.99 p/m after that. Cancel any time!
Product feature icon Unlimited ad-free access to the largest independent learning library in tech. Access this title and thousands more!
Product feature icon 50+ new titles added per month, including many first-to-market concepts and exclusive early access to books as they are being written.
Product feature icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Product feature icon Thousands of reference materials covering every tech concept you need to stay up to date.
Subscribe now
View plans & pricing
Table of content icon View table of contents Preview book icon Preview Book

iOS and OS X Network Programming Cookbook

Chapter 1. BSD Socket Library

In this chapter, we will cover:

  • Finding the byte order of your device

  • Retrieving network address information

  • Performing network address resolution

  • Creating an echo server

  • Creating an echo client

  • Creating a data server

  • Creating a data client

Introduction


The Berkeley Socket API (where API stands for Application Programming Interface) is a set of standard functions used for inter-process network communications. Other socket APIs also exist; however, the Berkeley socket is generally regarded as the standard.

The Berkeley Socket API was originally introduced in 1983 when 4.2 BSD was released. The API has evolved with very few modifications into a part of the Portable Operating System Interface for Unix (POSIX) specification. All modern operating systems have some implementation of the Berkeley Socket Interface for connecting devices to the Internet. Even Winsock, which is MS Window's socket implementation, closely follows the Berkeley standards.

BSD sockets generally rely on client/server architecture when they establish their connections. Client/server architecture is a networking approach where a device is assigned one of the two following roles:

  • Server: A server is a device that selectively shares resources with other devices on the network

  • Client: A client is a device that connects to a server to make use of the shared resources

Great examples of the client/server architecture are web pages. When you open a web page in your favorite browser, for example https://www.packtpub.com, your browser (and therefore your computer) becomes the client and Packt Publishing's web servers become the servers.

One very important concept to keep in mind is that any device can be a server, a client, or both. For example, you may be visiting the Packt Publishing website, which makes you a client, and at the same time you have file sharing enabled, which also makes your device a server.

The Socket API generally uses one of the following two core protocols:

  • Transmission Control Protocol (TCP): TCP provides a reliable, ordered, and error-checked delivery of a stream of data between two devices on the same network. TCP is generally used when you need to ensure that all packets are correctly received and are in the correct order (for example, web pages).

  • User Datagram Protocol (UDP): UDP does not provide any of the error-checking or reliability features of TCP, but offers much less overhead. UDP is generally used when providing information to the client quickly is more important than missing packets (for example, a streaming video).

Darwin, which is an open source POSIX compliant operating system, forms the core set of components upon which Mac OS X and iOS are based. This means that both OS X and iOS contain the BSD Socket Library.

Tip

The last paragraph is very important to understand when you begin thinking about creating network applications for the iOS platform, because almost any code example that uses the BSD Socket Library will work on the iOS platform. The biggest difference between using the BSD Socket API on any standard Unix platform and the iOS platform is that the iOS platform does not support forking of processes. You will need to use multiple threads rather than multiple processes.

The BSD Socket API can be used to build both client and server applications; in this chapter, we will be building both types of applications. In the downloadable code, you will find server/client applications for both the iOS and OS X platforms. Before we begin with our recipes, there are a few networking concepts that you should understand:

  • IP address: Any device on an Internet Protocol (IP) network, whether it is a client or server, has a unique identifier known as an IP address. The IP address serves two basic purposes: host identification and location identification.

    There are currently two IP address formats:

    • IPv4: This is currently the standard for the Internet and most internal intranets. This is an example of an IPv4 address: 83.166.169.231.

    • IPv6: This is the latest revision of the Internet Protocol (IP). It was developed to eventually replace IPv4 and to address the long-anticipated problem of running out of IPv4 addresses. This is an example of an IPv6 address: 2001:0db8:0000:0000:0000:ff00:0042:8329. An IPv6 can be shortened by replacing all the consecutive zero fields with two colons. The previous address could be rewritten as 2001:0db8::ff00:0042:8329.

  • Ports: A port is an application or process-specific software construct serving as a communications endpoint on a device connected to an IP network, where the IP address identifies the device to connect to, and the port number identifies the application to connect to.

    The best way to think of network addressing is to think about how you mail a letter. For a letter to reach its destination, you must put the complete address on the envelope. For example, if you were going to send a letter to friend who lived at the following address:

    Your Friend

    123 Main St

    Apt. 223

    San Francisco CA, 94123

    If I were to translate that into network addressing, the IP address would be equal to the street address, city, state, and zip code (123 Main St, San Francisco CA, 94123), and the apartment number would be equal to the port number (223). So the IP address gets you to the exact location, and the port number will tell you which door to knock on.

    A device has 65,536 available ports with the first 1024 being reserved for common protocols such as HTTP, HTTPS, SSH, and SMTP.

  • Fully Qualified Domain Name (FQDN): As humans, we are not very good at remembering numbers; for example, if your friend tells you that he found a really excellent website for books and the address was 83.166.169.231, you probably would not remember that two minutes from now. However, if he tells you that the address was www.packtpub.com, you would probably remember it. FQDN is the name that can be used to refer to a device on an IP network.

    So now you may be asking yourself, how does the name get translated to the IP address? The Domain Name Server (DNS) would do that.

  • Domain Name System Servers: A Domain Name System Server translates a fully qualified domain name to an IP address. When you use an FQDN of www.packtpub.com, your computer must get the IP address of the device from the DNS configured in your system. To find out what the primary DNS is for your machine, open a terminal window and type the following command:

    cat /etc/resolv.conf
  • Byte order: As humans, when we look at a number, we put the most significant number first and the least significant number last; for example, in number 123, 1 represents 100, so it is the most significant number, while 3 is the least significant number. For computers, the byte order refers to the order in which data (not only integers) is stored into memory. Some computers store the most significant bytes first (at the lowest byte address), while others store the most significant bytes last.

    If a device stores the most significant bytes first, it is known as big-endian, while a device that stores the most significant bytes last is known as little-endian.

    The order of how data is stored in memory is of great importance when developing network applications, where you may have two devices that use different byte-ordering communication. You will need to account for this by using the Network-to-Host and Host-to-Network functions to convert between the byte order of your device and the byte order of the network.

Tip

The byte order of the device is commonly referred to as the host byte order, and the byte order of the network is commonly referred to as the network byte order.

The discussion on byte order does lead us directly to the first recipe of this chapter, Finding the byte order of your device.

Finding the byte order of your device


In the Introduction section of this chapter, one of the concepts that was briefly discussed was how devices store information in memory (byte order). After that discussion, you may be wondering what the byte order of your device is.

Tip

The byte order of a device depends on the Microprocessor architecture being used by the device. You can pretty easily go on to the Internet and search for "Mac OS X i386 byte order" and find out what the byte order is, but where is the fun in that? We are developers, so let's see if we can figure it out with code.

We can determine the byte order of our devices with a few lines of C code; however, like most of the code in this book, we will put the C code within an Objective-C wrapper to make it easy to port to your projects. The downloadable code for this chapter contains the Objective-C classes within an application to test your system.

Getting ready

This recipe is compatible with both iOS and OS X. No extra frameworks or libraries are required.

How to do it…

Let's get started by defining an ENUM in our header file:

  1. We create an ENUM that will be used to identify the byte order of the system as shown in the following code:

     typedef NS_ENUM(NSUInteger, EndianType) {
        ENDIAN_UNKNOWN,
        ENDIAN_LITTLE,
        ENDIAN_BIG
    };
  2. To determine the byte order of the device, we will use the byteOrder method as shown in the following code:

    -( EndianType)byteOrder {
        union {
            short sNum;
            char cNum[sizeof(short)];
        } un;
        un.sNum = 0x0102;
        if (sizeof(short) == 2) {
            if(un.cNum[0] == 1 && un.cNum[1] == 2)
                return ENDIAN_BIG;
            else if (un.cNum[0] == 2 && un.cNum[1] == 1)
                return ENDIAN_LITTLE;
            else
                return ENDIAN_UNKNOWN;
        } else
            return ENDIAN_UNKNOWN;
    }

Tip

Downloading the example code

You can download the example code files for all Packt Publishing books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.

How it works…

In the ByteOrder header file, we defined an ENUM with three constants. The constants are as follows:

  • ENDIAN_UNKNOWN: We are unable to determine the byte order of the device

  • ENDIAN_LITTLE: This specifies that the most significant bytes are last (little-endian)

  • ENDIAN_BIG: This specifies that the most significant bytes are first (big-endian)

The byteOrder method determines the byte order of our device and returns an integer that can be translated using the constants defined in the header file. To determine the byte order of our device, we begin by creating a union of short int and char[]. We then store the value 0x0102 in the union. Finally, we look at the character array to determine the order in which the integer was stored in the character array. If the number one was stored first, it means that the device uses big-endian; if the number two was stored first, it means that the device uses little-endian.

The downloadable code contains projects for both the Mac OS X and iOS devices, so you can see how to use this class and also test the byte order of your devices.

Retrieving network address information


Many programs will need to know the network information about the available interfaces on the device they are running on. This recipe will show you how to retrieve the network information for all the active network interfaces on your device. The information that we will be retrieving is the interface name, IP version, IP address, netmask, and default gateway.

We will start off by creating a NetworkAddressStore class that can be used to store the information for a given network interface. We will then get a list of active network interfaces and create an instance of the NetworkAddressStore class for each interface. These objects will then be stored in NSMutableArray.

This recipe will also introduce several new functions and two new structures, including the very important sockaddr family of structures. We will discuss these new functions and structures as we describe the code.

Getting ready

This recipe is compatible with both iOS and OS X. No extra frameworks or libraries are required.

How to do it…

Let's retrieve the network address information for our device as follows:

  1. To retrieve the network address information, we will use the getifaddrs() function. This function will store a reference to a linked list of ifaddrs structures. Each ifaddrs structure will represent a physical or virtual network interface. The getifaddrs() function will return 0 if it was successful, or -1 if there was a problem.

    The getifaddrs(struct ifaddrs **ifad) function is not a part of the POSIX standard, but it is a part of most BSD systems; therefore, it is on both OS X and iOS. Refer to the following code:

    struct ifaddrs *interfaces = NULL;
    int success = 0;
    success = getifaddrs(&interfaces);
  2. Once we have the linked list of ifaddrs, we will need to loop through the list and retrieve the information about each network interface as shown in the following code:

    struct ifaddrs *temp_addr = interfaces;
    for (temp_addr = interfaces; temp_addr != NULL; temp_addr =  temp_addr->ifa_next) {
    
          int ipversion;
          NSLog(@"************************");
          if(temp_addr->ifa_addr->sa_family == AF_INET) {
              NSLog(@"IPv4");
              ipversion = AF_INET;
          } else if(temp_addr->ifa_addr->sa_family == AF_INET6) {
              NSLog(@"IPv6");
              ipversion = AF_INET6;
          } else {
              NSLog(@"Unknown IP version");
              ipversion = 0;
          }

    The temp_addr ifaddrs structure is a temporary structure that will be used as we loop through the linked list. We will need to keep a pointer pointing to the first ifaddrs structure so we can properly release the structure using the freeifaddrs() function when we are done with it.

    We then create a for loop to loop through our ifaddrs linked list.

    We check the IP address version being used by checking sa_family; if it is IPv4, we set ipversion to AF_INET; if it is IPv6, we set ipversion to AF_INET6. We will use this variable later in our inet_ntop() functions.

    If the IP address version is neither IPv4 nor IPv6, we set ipversion to 0.

  3. We need to define three character arrays to hold our network address, netmask, and gateway information for the network interfaces. In the following code snippet, three character arrays are defined:

          char naddr[INET6_ADDRSTRLEN];
          char nmask[INET6_ADDRSTRLEN];
          char ngate[INET6_ADDRSTRLEN];

    We set the size of the array to INET6_ADDRSTRLEN because it is larger than INET_ADDRSTRLEN, so it will hold either IPv4 or IPv6 addresses. INET6_ADDRSTRLEN is defined as 46, and INET_ADDRSTRLEN as 16.

  4. Now we need to show the result, for which we will use the following code:

          NSLog(@"Name:  %@",[NSString stringWithUTF8String:temp_addr->ifa_name]);
          inet_ntop(ipversion,&((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr,naddr,INET_ADDRSTRLEN);
          NSLog(@"Address:  %@",[NSString stringWithUTF8String:naddr]);
          if ((struct sockaddr_in6 *)temp_addr->ifa_netmask != NULL) {
              inet_ntop(ipversion,&((struct sockaddr_in *)temp_addr->ifa_netmask)->sin_addr,nmask,INET_ADDRSTRLEN);
              NSLog(@"Netmask:  %@", [NSString stringWithUTF8String:nmask]);
          }
          if ((struct sockaddr_in6 *)temp_addr->ifa_dstaddr != NULL) {
              inet_ntop(ipversion,&((struct sockaddr_in *)temp_addr->ifa_dstaddr)->sin_addr,ngate,INET_ADDRSTRLEN);
              NSLog(@"Gateway:  ", [NSString stringWithUTF8String:ngate]);
          }
      }
    freeifaddrs(interfaces);
    

    The ifa_name character array of the ifaddr structure contains the name of the interface; therefore, we convert ifa_name to NSString and log it.

    We then use the inet_ntop function to populate the naddr, nmask, and ngate character arrays, convert them to NSStrings, and log them.

    The data returned from the getifaddrs() function is dynamically allocated and should be released using the freeifaddrs() function when it is no longer needed to avoid any memory leaks.

How it works…

The getifaddrs() function will store a reference to a linked list of ifaddrs structures. The ifaddrs structure looks like the following:

struct ifaddrs  {  *ifa_next;    /* Pointer to next struct */
    char             *ifa_name;    /*Interface name */
    u_int             ifa_flags;   /*Interface flags */
    struct sockaddr  *ifa_addr;    /*Interface address */
    struct sockaddr  *ifa_netmask; /*Interface netmask */
    struct sockaddr  *ifa_dstaddr; /*P2P interface destination or Broadcast address */
    void             *ifa_data;    /*Address specific data */
}

We use ifa_next in our for loop because it points to the next element in our linked list. If ifa_next equals NULL, we have reached the end of our linked list.

If you look closely, you will notice that the ifaddrs structure contains three sockaddr structures. The sockaddr structure is a generic structure that pointers are cast to. The sockaddr structure looks like the following code snippet:

struct sockaddr {
  uint8_t sa_len;
  sa_family_t sa_family;
  char sa_data[14];
}

Depending on the value of sa_family, we can cast the sockaddr structure as sockaddr_in (for IPv4 addresses) or sockaddr_in6 (for IPv6 addresses) before retrieving the address information. We use sa_family to determine the IP address version of the structure. The sa_family values contain one of the following listed values:

  • AF_UNIX: Local to host (pipes)

  • AF_INET: The IPv4 address family

  • AF_INET6: The IPv6 address family

  • AF_NS: Xerox NS protocols

  • AF_CCITT: CCITT protocols, X.25

  • AF_HYLINK: NSC Hyperchannel

  • AF_ISO: ISO protocols

We use ifa_name of the ifaddrs structure to determine the name of the interface.

We used the inet_ntop function to convert the binary representation of the network address that is stored in the sockaddr structure to a character array. If you look at the ntop part of the function name, n stands for network and p stands for the presentation, so you can read the function name as the "inet network to presentation" function. There is a corresponding inet_pton function that converts an ASCII string to binary, which you can think of as inet presentation to network.

The downloadable code contains projects for both the Mac OS X and iOS devices. Sample projects use a NetworkAddressStore class to store the information returned by the getifaddrs() functions. This will make it easier to integrate this recipe with your project.

Performing a network address resolution


Most applications will eventually need to convert host/service names to sockaddr structures and sockaddr structures to host/service names. The BSD Socket Library has two functions to assist with these conversions:

  • Getaddrinfo(): This is a function that will return information about a given host/service name. The results are returned in an addrinfo structure.

  • Getnameinfo(): This is a function that will return the host and service names, given a sockaddr structure.

The getaddrinfo() and getnameinfo() functions make the gethostbyname(), gethostbyaddr(), and getservbyport() functions obsolete. One of the main advantages that the getaddrinfo() and getnameinfo() functions has over the obsolete functions is that they are compatible with both IPv4 and IPv6 addresses.

In this recipe, we will encapsulate getaddrinfo() and getnameinfo() into an Objective-C class. This class will not hide most of the complexity of the two functions; however, it will save you from having to worry about NSString to character array conversions and will also handle the memory management of the addrinfo structures for you.

Getting ready

This recipe is compatible with both iOS and OS X. No extra frameworks or libraries are required.

How to do it…

Let's get started with the AddrInfo class.

Creating the AddrInfo header file

The header file for the AddrInfo class looks like the following:

 #import <Foundation/Foundation.h>
 
 @interface AddrInfo : NSObject 
 
 @property (nonatomic, strong) NSString *hostname, *service;
 @property (nonatomic) struct addrinfo *results;
 @property (nonatomic) struct sockaddr *sa;
 @property (nonatomic, readonly) int errorCode;
 
 -(void)addrWithHostname:(NSString*)lHostname Service:(NSString *)lService andHints:(struct addrinfo*)lHints;
 -(void)nameWithSockaddr:(struct sockaddr *)saddr;
 
 -(NSString *)errorString;
 
@end

The addrinfo header file defines four properties. The hostname, service, and results properties will contain the results of the address resolution queries, and the errorCode property will contain any error code that is returned.

We are also defining three methods in our header file. The addrWithHostname:Service:andHints: method, which takes supplied hostname, service, and hints (we will discuss the hints structure when we discuss how to use the AddrInfo class) and populates the results property using the getaddrinfo() function. The nameWithSockaddr: method, which takes supplied sockaddr and populates the hostname and service properties using the getnameinfo() function. If there is an error with either of the methods, the errorCode property is set to the returned error code.

The errorString method takes the error code from the errorCode property and returns a string that tells what the error code is.

Creating the AddrInfo implementation file

To create the AddrInfo implementation file, we use the following code:

  #import "AddrInfo.h"
  #import <netdb.h>
  #import <netinet/in.h>
  #import <netinet6/in6.h>
  
  @implementation AddrInfo
 
  -(instancetype)init {
      self = [super init];
      if (self) {
          [self setVars];
      }
      return self;
  }

We begin the implementation file by importing the headers that are needed. We also define an init constructor for our class that uses the setVars method to reset our properties to default values. Let's look at the addrWithHostname:Service:andHints: method:

-(void)addrWithHostname:(NSString*)lHostname Service:(NSString *)lService andHints:(struct addrinfo*)lHints {
    
    [self setVars];
    self.hostname = lHostname;
    self.service = lService;
    
    struct addrinfo *res;
    
    _errorCode = getaddrinfo([_hostname UTF8String], [_service UTF8String], lHints, &res);
    self.results = res;
    
}

The addrWithHostname:Service:andHints: method will retrieve the addresses for a given hostname. We start off by resetting the properties to default values using the setVars method. We then set the hostname and service properties with the values passed to the method.

Since the getaddrinfo() function expects character arrays for hostname and service, we need to convert our NSString values to character arrays. This is done by using the UTF8String method of the NSString class. We also pass the addrinfo hints structure and the address of the res addrinfo structure. The results of the getaddrinfo() function are put into the errorCode property. If the getaddrinfo() function call was successful, errorCode will be equal to 0.

When the getaddrinfo() function returns, the res structure contains the results that we use to set the results property:

-(void)nameWithSockaddr:(struct sockaddr *)saddr {
    
    [self setVars];
    char host[1024];
    char serv[20];
    

    _errorCode = getnameinfo(saddr, sizeof saddr, host, sizeof host, serv, sizeof serv, 0);
    
    self.hostname = [NSString stringWithUTF8String:host];
    self.service = [NSString stringWithUTF8String:serv];
    
}

The nameWithSockaddr: method will retrieve the names associated with a given IP address. We start this method by calling the setVars method to initialize the object's properties. We then define the two character arrays that will contain the results of the getnameinfo() function call.

The getnameinfo() function will take the address information from the saddr sockaddr structure, perform a lookup for the host/service name, and put the results into the host and serv character arrays. If the getnameinfo() function was successful, it will return 0, otherwise it will return -1.

Finally, we convert the host and serv character arrays to NSStrings and put the values into the hostname and service properties:

-(void)setVars {
    self.hostname = @"";
    self.service = @"";
    self.results = @"";
    _errorCode = 0;
}

The setVars method simply sets all the method's NSString properties to empty strings and the errorcode property to 0. This gives us a well-defined starting point for the method properties to make sure they do not contain stale information. Let's look at the errorString: method:

-(NSString *)errorString {
    return [NSString stringWithCString:gai_strerror(_errorCode) encoding:NSASCIIStringEncoding];
}

The errorStiring method uses the gai_strerror() function to convert the error code from either the getnameinfo() or getaddrinfo() function calls to an actual error method that can tell us what went wrong; let's look at the setResults: method:

-(void)setResults:(struct addrinfo *)lResults {
    freeaddrinfo(self.results);
    _results = lResults;
}

We create the setResults: method because we need to call the freeaddrinfo() function to release the results before setting the new results. This will avoid memory leaks in our application.

Using the AddrInfo class to perform the address/hostname resolution

In the following sample code, we will show how to get the hostname www.packtpub.com to list the IP addresses and then convert those IP addresses back to the hostnames:

  struct addrinfo *res;
  struct addrinfo hints;
  
  memset(&hints, 0, sizeof hints);
  hints.ai_family = AF_UNSPEC;   
  hints.ai_socktype = SOCK_STREAM;

We begin our address/hostname resolution code by setting up two addrinfo structures. The res structure will be used as a temporary store when we loop though the linked list of results that are returned to us from the addrWithHostname:Service:andHints: method. The hints structure will store the hints that we are going to pass to the addrWithHostname:Service:andHints: method to let the method know what type of addresses we are looking for.

Whenever you create a new structure that you plan on setting the values for, you should always use the memset() function to clear the memory of the structure. This will ensure that there is nothing in the memory that will corrupt the structure.

We set ai_family to AF_UNSPEC and ai_socktype to SOCK_STREAM. This tells the getaddrinfo() function that we are looking for any IP version (IPv4 or IPv6) but limiting our socket type to socket streams (these settings are used when we want to make a TCP connection). We could set the ai_family to AF_INET4 to limit the results to only IPv4 results, or set it to AF_INET6 for only IPv6 results. Let's look at how we would initiate the AddrInfo object:

AddrInfo *ai = [[AddrInfo alloc] init];
[ai addrWithHostname:@"www.packtpub.com" Service:@"443" andHints:&hints];
if (ai.errorCode != 0) {
     NSLog(@"Error in getaddrinfo():  %@",[ai getErrorString]);
     return -1;
 }

We now initiate our AddrInfo object and call the addrWithHostname:Service:andHints: method. For our example, we are requesting an address lookup for the www.packtpub.com hostname. The service we are requesting is port 443, which is HTTPS, and we are also supplying our hints structure, which specifies the type of addresses we are looking for.

The code then checks to see if we have any errors; if so, it logs them and exits. Depending on what your application does, you will probably want to catch the error and display a message to the user. Let's loop though the addresses and display the results:

  struct addrinfo *results = ai.results;
  for (res = results; res!= NULL; res = res->ai_next) {
      void *addr;
      NSString *ipver = @"";
      char ipstr[INET6_ADDRSTRLEN];

      if (res->ai_family == AF_INET) {
          struct sockaddr_in *ipv4 = (struct sockaddr_in *)res->ai_addr;
          addr = &(ipv4->sin_addr);
          ipver = @"IPv4";
      } else if (res->ai_family == AF_INET6){
          struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)res->ai_addr;
          addr = &(ipv6->sin6_addr);
          ipver = @"IPv6";
      } else {
          continue;
      }
    inet_ntop(res->ai_family, addr, ipstr,sizeof ipstr);
      NSLog(@"     %@  %s", ipver, ipstr);
      AddrInfo *ai2 = [[AddrInfo alloc] init];
      [ai2 getNameWithSockaddr:res->ai_addr];
      if (ai2.errorCode ==0)
          NSLog(@"--%@ %@",ai2.hostname, ai2.service);
  }
  freeaddrinfo(results);

If there are no errors, we loop though the results. After we initialize the variables, we check to see if the address family is AF_INET (IPv4 address). If so, we create a sockaddr_in structure, retrieve the address from the sin_addr variable, and set ipver to IPv4.

If the address family was not AF_INET, we check to see if the address family is AF_INET6 (IPv6 address). If so, we create a sockaddr_in6 structure, retrieve the address from the sin_addr6 variable, and set ipver to IPv6.

If the address family is neither AF_INET nor AF_INET6, we continue the for loop without logging the address.

The inet_ntop() function converts the address from binary to text form so that we can display it. The NSLog line will display the IP version followed by the IP address.

Now that we have retrieved the IP address, we will need to send it back to the hostname. For this, we take the sockaddr from our results structure and send it to the nameWithSockaddr: method of the AddrInfo class. When the nameWithSockaddr: method completes, it will populate the hostname and service properties of the AddrInfo object.

Finally, we use the freeaddrinfo()function to release the results in order to prevent any memory leaks.

How it works…

In this recipe, we used the getaddrinfo() and getnameinfo() functions to get the IP address and hostname. These functions are provided as part of the standard POSIX API.

While these functions are black-box functions, there is really nothing magical about them. Internally, these functions call lower-level functions to send our requests to the appropriate DNS server to perform the resolution.

Creating an echo server


In this recipe, we will be creating an echo server that will listen on port 2004. Once the connection is established, the server will echo the text received back to the client.

As we did in the earlier recipes, we will encapsulate the socket, bind, and listen steps into an Objective-C class, complete with error checking to make it easy for you to add this code to your project.

Getting ready

This recipe is compatible with both iOS and OS X. No extra frameworks or libraries are required.

How to do it….

Let's get started by creating a BSDSocketServer class that will greatly simplify the creation of a BSD socket server. While this recipe is focused on setting up an echo server, in the Creating a data server recipe of this chapter, you will see that the code can be modified very easily to create other types of servers.

Creating the BSDSocketServer header file

The BSDSocketServer header file looks like the following code:

#import <Foundation/Foundation.h>
   
#define LISTENQ 1024
#define MAXLINE 4096
   
typedef NS_ENUM(NSUInteger, BSDServerErrorCode) {
    NOERROR,
    SOCKETERROR,
    BINDERROR,
    LISTENERROR,
    ACCEPTINGERROR
};

@interface BSDSocketServer : NSObject

@property (nonatomic) int errorCode, listenfd;
-(id)initOnPort:(int)port;
-(void)echoServerListenWithDescriptor:(int)lfd;

@end

The header file of the BSDSocketServer class starts off by defining the LISTENQ constant as 1024. This constant will be the maximum number of pending connections that can be queued up at any given time before the sockets stop accepting new connection requests.

We also define the maximum length of the inbound string for the echo server, which we will set as 4096 characters.

We then define an ENUM with our five error conditions:

  • NOERROR: This determines that no errors occurred while performing the socket, bind, and listen steps

  • SOCKETERROR: This determines that the error occurred while creating the socket

  • BINDERROR: This determines that the error occurred while binding the sockaddr family of structures with the socket

  • LISTENERROR: This determines that the error occurred while preparing to listen on the socket

  • ACCEPTINGERROR: This determines that the error occurred while accepting a connection

The BSDSocketServer has two properties. The errorCode property will contain the error code if any of the functions fails, while the listenfd property will contain the socket descriptor. This descriptor can be used outside the BSDSocketServer object to create your server if you want to have your server code outside the BSDSocketServer class.

The header defines one constructor called initWithPort:, which has one parameter to define the port number to listen on. The header file also defines one method that sets up the echo server once we initialize the server within the initWithPort: constructor. As you build your own servers, you will want to add separate methods such as the echoServerListenWithDescriptor: method, to handle them while using the initWithPort: constructor to initialize the server.

Creating the BSDSocketServer implementation file

Now let's look at the BSDSocketServer implementation file. The code for this implementation file is as follows:

  #import "BSDSocketServer.h"
  #import <sys/types.h>
  #import <arpa/inet.h>
 @implementation BSDSocketServer

We begin the implementation file by importing the header files needed to implement our echo server. Let's look at the initOnPort: constructor:

-(instancetype)initOnPort:(int)port {
       self = [super init];
       if (self) {
           struct sockaddr_in servaddr;
           
           self.errorCode = NOERRROR;
           if ( (self.listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
               self.errorCode = SOCKETERROR;
           else {
memset(&servaddr, 0, sizeof(servaddr));  
servaddr.sin_family = AF_INET;
               servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
               servaddr.sin_port = htons(port);
               
               if (bind(self.listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) <0) {
                   self.errorCode = BINDERROR;
               } else {
                   
                   if ((listen (self.listenfd, LISTENQ)) <0) {
                       self.errorCode = LISTENERROR;
                   }
               }
           }
       }
       return self;
   }

The BSDSocketSever.m class has a single constructor called initWithPort:. This constructor will take a single parameter named port of type int. This port parameter is the port number that we want our server to bind to. This number can range from 0-65535; however, you will need to have the root access to bind to ports below 1024, so I recommend you to use port numbers greater than 1024.

We define a sockaddr_in structure (remember, sockaddr_in is for IPv4 and sockaddr_in6 is for IPv6) named servaddr. To begin with, we set the errorCode variable to NOERROR.

To set up a socket, we will need to call the socket(), bind(), and listen() functions. If any of these functions fail, we will want to set the errorCode variable and skip the rest of the initialization.

We use the socket() function to create our socket using the AF_INET (IPv4) and SOCK_STREAM (TCP) parameters. If you would like to use IPv6, you would change AF_INET to AF_INET6. If you would like to use UDP instead of TCP, you would change SOCK_STREAM to SOCK_DGRAM.

Prior to calling the bind() function, we need to set up a sockaddr structure that contains the IP version, interface, and port number that we will be binding the socket to. Before populating the sockaddr structure with the information, we would want to clear the memory to make sure there is no stale information that may cause our bind function to fail. We do this using the memset() function.

After we clear the memory of the sockaddr structure, we set the values. The sin_family address family is set to AF_INET, which sets the IP version to IPv4. The sin_addr.s_addr address is set using htonl(INADDR_ANY) to let the socket bind to any interface on the device. The sin_port number is set to the port number using the htons(port) function.

The htonl() and htons() functions convert the byte order of the values passed in from the host byte order to the network byte order, so the values can be properly interpreted when making network calls. If you are unsure what byte order is, you can refer to the Finding the byte order of your device recipe of this chapter.

After we have our sockaddr structure set, we use it to bind the socket to the address specified in the servaddr structure.

If our bind() function call is successful, we attempt to listen to the socket for new connections. We set the maximum number of backlog connection attempts to the LISTENQ constant, which is defined as 1024.

After we initiate the BSDSocketServer object using the initOnPort: constructor, we will have a server that is actively listening for new connections on the port, but now we need to do something when the connection comes in. That is where the echoServerListenWithDescriptor: method comes in. The echoServerListenWithDescriptor: method will listen for new connections and when one comes in, it will start a new thread to handle the connection, as shown in the following code:

   -(void)echoServerListenWithDescriptor:(int)lfd {
       int connfd;
       socklen_t clilen;
       struct sockaddr_in cliaddr;
       char buf[MAXLINE];
       
       for (;;) {
           clilen = sizeof(cliaddr);
           if ((connfd = accept(lfd, (struct sockaddr *)&cliaddr, &clilen))<0) {
               if (errno != EINTR) {
                   self.errorCode = ACCEPTINGERROR;
                   NSLog(@"Error accepting connection");
               }
           } else {
               self.errorCode = NOERRROR;
               NSString *connStr = [NSString stringWithFormat:@"Connection from %s, port %d", inet_ntop(AF_INET, &cliaddr.sin_addr,buf, sizeof(buf)),ntohs(cliaddr.sin_port)];
               NSLog(@"%@", connStr);
               
               //Multi-threaded
                    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                [self strEchoServer:@(connfd)];
            });               
           }
       }
   }

The echoServerListenWithDescriptor: method will use the accept() function to accept incoming connections on the supplied socket descriptor.

Within the echoServerListenWithDescriptor: method, we create a for loop that will loop forever because each time a new connection is accepted, we will want to pass the control of that connection to a separate thread and then come back and wait for the next connection.

The accept() function detects and initializes incoming connections on the listening socket. When a new connection is made, it will return a new socket descriptor. If there is a problem initializing the connection, the accept() function will return -1. If the connection is successfully initialized, we determine the IP address and port number from where the client is connecting and log it.

Finally, we use dispatch_async() to add our strEchoServer() method to the dispatch queue. If we simply called the method directly without dispatch_async(), the server would only be able to handle one incoming connection at a time. With dispatch_async(), each time a new connection comes in, the strEchoServer() method gets passed to the queue and then the server can go back to listening for new connections. The strEchoServer() method listens to establish connections for incoming text and then echoes that text back to the client. Refer to the following code:

  -(void)strEchoServer:(NSNumber *) sockfdNum {
      ssize_t n;
      char buf[MAXLINE];
      
      int sockfd = [sockfdNum intValue];
      while ((n=recv(sockfd, buf, MAXLINE -1,0)) > 0) {
          [self written:sockfd char:buf size:n];
          buf[n]='\0';
          NSLog(@"%s",buf);
          [[NSNotificationCenter defaultCenter] postNotificationName:@"posttext" object:[NSString stringWithCString:buf encoding:NSUTF8StringEncoding]];
          
      }
      NSLog(@"Closing Socket");
      close(sockfdNum);
  }

The strEchoServer: method has one parameter that is a socket descriptor to read from. We set up the while loop that will loop each time data comes in on the socket. When the data is received, the recv() function will put the incoming bytes into the buffer pointed to by buf. The recv() function will then return the number of bytes that are read. If the number of bytes is zero, the client is disconnected; if it is less than zero, there is an error. For the purpose of this recipe, we will close the socket if the number of bytes returned is zero or less.

As soon as the data is read from the socket, we call the written:char:size: function to write the data back to the client. This essentially is our echo server; however, we want to perform some additional steps so we can see when the data is received.

We will want to terminate the buf character array with a NULL terminator prior to converting it to NSString, so we do not get any additional garbage in our string. After we terminate the character array, we post a notification named posttext with the text from the socket. This will allow us to set an observer within our program that will receive all incoming text from the socket. In our example code, this notification will be used to display the incoming text to the screen, but it can also be used for logging or anything else we think of. If you do not want to do anything with the text that is sent, you can safely ignore the notification.

Once the client closes the connection, we will want to close the socket on our end. The close() function at the end of the strEchoServer: method does this for us if the number of bytes returned from the recv() function is zero or less:

   -(ssize_t) written:(int)sockfdNum char:(const void *)vptr size:(size_t)n {
       
     size_t    nleft;
     ssize_t    nwritten;
     const char  *ptr;
       
     ptr = vptr;
     nleft = n;
     while (nleft > 0) {
       if ( (nwritten = write(sockfdNum, ptr, nleft)) <= 0) {
         if (nwritten < 0 && errno == EINTR)
           nwritten = 0;    /* and call write() again */
         else
           return -1;      /* error */
       }
           
       nleft -= nwritten;
       ptr   += nwritten;
     }
     return(n);
   }
   
   @end

The written:char:size: method is used to write the text back to the client and has three parameters. These parameters are: sockfdNum, which is the socket descriptor to write to; the vptr pointer, which points to the text to be written; and n, which is the length of the text to be written.

The written:char:size: method uses the write() function to write the text back to the client. This method returns the number of bytes written, which may be less than the total number of bytes you told it to write. When that happens, we will need to make multiple write calls until everything is written back to the client.

We set ptr to point to the beginning of the text to send back and then set nleft to the size of the text to write. If the write function does not send all the text to the client, ptr will be moved to point to where we will begin the next write from and nleft will be set to the number of remaining bytes to write. The while loop will continue to loop until all text is written back to the client. If the write function returns a number less than 0, it means that there was a problem writing to the socket, so we return -1.

Using the BSDSocketServer class to start the echo server

The following code will start our server and can be used on both the iOS and OS X platforms:

 BSDSocketServer *bsdServ = [[BSDSocketServer alloc] initOnPort:2004];
 if (bsdServ.errorCode == NOERRROR) {
     [bsdServ echoServerListenWithDescriptor:bsdServ.listenfd];
         
 } else {
     NSLog(@"%@",NSString stringWithFormat:@"Error code %d recieved.  Server was not started", bsdServ.errorCode]);
 }

We begin by initializing our BSDSocketServer object by setting the port number for our server. In this example, we use port 2004. We then verify that we did not have any issues initializing our server and if everything was good, we call the echo server listener method.

When you create your own server, you will want to keep the initWithPort: constructor to establish the connection and then create your protocol in a separate method such as the echoServerListenWithDescriptor: method shown in this recipe. You will see an example of this in the Creating a data server recipe of this chapter.

The downloadable code contains sample projects for both iOS and OS X.

Tip

Once you download the code, you can start the server and test it using the following telnet command:

telnet localhost 2004

Once telnet makes the connection, type any text and press the Enter key. Once you press the Enter key, the text you typed in will be echoed back to you.

The following screenshot shows how the telnet session will work with our echo server:

How it works…

When you create a server using BSD sockets, you need to call the socket(), bind(), and listen() methods in that order:

  • int socket(int domain, int type, int protocol): This function returns an integer descriptor that can be used to identify the socket in all future function calls.

  • int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen): This function will bind the network interface and port combination to the socket. We will need to create a sockaddr structure with the IP version, network interfaces, and the port number to bind the socket prior to calling the bind() function.

  • int listen(int sockfd, int backlog): This function begins listening to the socket for any incoming connections.

The socket, bind, and listen steps described are the normal steps needed to prepare a TCP server and to create a listening descriptor. The listening descriptor will be used to accept incoming connections. Once we have the listening descriptor, we can then wait for incoming connections and respond to them.

When you create your own servers, you will want to use the initOnPort: constructor to initiate the server, but write separate functions to handle the incoming requests. You will see this in the Creating a data server recipe when we create a data server to receive images from a client.

Once we have our socket created, we can call the method that will listen on the socket (the echoServerListenWithDescriptor: method). This method uses the accept() function to listen for incoming connections. The accept() function will create a new socket for each incoming connection and then remove the connection from the listen queue. If you recall, we defined that the listen queue can contain up to 1024 connections before it stops accepting new ones.

The strEchoServer: function is where we actually implement our echo server. This method uses the recv() function to receive the incoming data (in our case, incoming text) from an open socket. Once the text is received, we call the written:char:size: method to write the data back to the client.

Creating an echo client


In the Creating an echo server recipe of this chapter, we created an echo server and then tested it using telnet. Creating the server was pretty fun, but testing with telnet was a kind of anti-climax; so in this recipe, we will be creating a client that we can use to connect to our echo server.

When we created the echo server, we created a BSDSocketServer class to help with the creation of our server applications. In this recipe, we will be creating a BSDSocketClient class to help with the creation of our client applications.

Getting ready

This recipe is compatible with both iOS and OS X. No extra frameworks or libraries are required.

How to do it…

Now let's create an echo client that will communicate with our echo server:

Creating the BSDSocketClient header file

We will begin by creating the BSDSocketClient header file, as shown in the following code:

#import <Foundation/Foundation.h>
  
typedef NS_ENUM(NSUInteger, BSDClientErrorCode) {
    NOERRROR,
    SOCKETERROR,
    CONNECTERROR,
    READERROR,
    WRITEERROR
};

#define MAXLINE 4096

@interface BSDSocketClient : NSObject

@property (nonatomic) int errorCode, sockfd;

-(instancetype)initWithAddress:(NSString *)addr andPort:(int)port;
-(ssize_t) writtenToSocket:(int)sockfdNum withChar:(NSString *)vptr;
-(NSString *) recvFromSocket:(int)lsockfd withMaxChar:(int)max;

We begin the header file by defining the five error conditions that may occur while we are connecting to the server. If an error occurs, we will set the errorCode property with the appropriate code.

We then define the maximum size of the text that we can send to our server. This is really used strictly for this example; on production servers, you will not want to put a limit such as this.

The BSDSocketClient header defines two properties, errorCode and sockfd. We expose the errorCode property, so classes that use the BSDSocketClient class can check for errors, and we expose the sockfd socket descriptor in case we want to create the client protocol outside the BSDSocketClient class.

The header file also defines one constructor and two methods, which we will be exposing in the BSDSocketClient class.

The initWithAddress:andPort: constructor creates the BSDSocketClient object with the IP address and port combination for connection. The writtenToSocket:withChar: method will write data to the socket that we are connected to, and the recvFromSocket:withMaxChar: method will receive characters from the socket.

Creating the BSDSocketClient implementation file

Now we need to create the BSDSocketClient implementation file, as shown in the following code:

 #import "BSDSocketClient.h"
 #import <sys/types.h>
 #import <arpa/inet.h>
 
 @implementation BSDSocketClient

We begin the BSDSocketClient implementation file by importing the headers needed to create our client. Let's look at the initWithAddress:andPort: constructor:

 -(id)initWithAddress:(NSString *)addr andPort:(int)port {
     self = [super init];
     if (self) {
         struct sockaddr_in	servaddr;
         
         self.errorCode = NOERRROR;
         if ( (self.sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
             self.errorCode = SOCKETERROR;
         else {
             memset(&servaddr,0, sizeof(servaddr));
             servaddr.sin_family = AF_INET;
             servaddr.sin_port = htons(port);
             inet_pton(AF_INET, [addr cStringUsingEncoding:NSUTF8StringEncoding], &servaddr.sin_addr);
         
             if (connect(self.sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0) {
                 self.errorCode = CONNECTERROR;
             }
         }
      }
     return self;
  }

The initWithAddress:andPort: constructor is used to set up the connection with the server. We define a sockaddr_in structure named servaddr. This structure will be used to define the address, port, and IP version of our connection.

If you recall, we initialized the server for the echo server by making the socket(), bind(), and listen() function calls. To initialize a client, you only need to make two function calls. These are the same socket() call you made for the server followed by a new function called connect().

We make the socket() function call using the AF_INET (IPv4) and SOCK_STREAM (TCP) parameters. If you would like to use IPv6, you would change AF_INET to AF_INET6. If you would like to use UDP instead of TCP, you would change SOCK_STREAM to SOCK_DGRAM. If there is an issue creating the socket, we will set the errorCode variable to SOCKETERROR and skip the rest of the code.

Prior to calling the connect function, we need to set up a sockaddr structure that contains the IP version, address, and port number we will be connecting to. Before populating the sockaddr structure with the information, we will want to clear the memory to make sure that there is no stale information that may cause our bind function to fail. We do this using the memset() function.

After we clear the memory for the sockaddr structure, we set the values. We set the IP version to IPv4 by setting the sin_family address to AF_INET. The sin_port number is set to the port number by using the htons() function. We convert the IP address that we are connecting to from NSString to cString and use the inet_pton() function to convert the address to a network address structure that is put into servaddr.sin_addr.

After we have our sockaddr structure set, we attempt to connect to the server using the connect() function. If the connection fails, the connect() function returns -1. Let's look at the writtenToSocket:withChar: method:

-(ssize_t) writtenToSocket:(int)sockfdNum withChar:(NSString *)vptr {
      
    size_t    nleft;
    ssize_t  nwritten;
    const char  *ptr = [vptr cStringUsingEncoding:NSUTF8StringEncoding];
      
    nleft = sizeof(ptr);
    size_t n=nleft;
    while (nleft > 0) {
      if ( (nwritten = write(sockfdNum, ptr, nleft)) <= 0) {
        if (nwritten < 0 && errno == EINTR)
          nwritten = 0;
        else {
                  self.errorCode = WRITEERROR;
          return(-1);
              }
      }
          
      nleft -= nwritten;
    ptr   += nwritten;
   }
   return(n);
 }

The writtenToSocket:withChar: method is used to write the text to the server. This method has two parameters: sockfdNum, which is the socket descriptor to write to, and vptr NSString, which contains the text to send to the server.

The writtenToSocket:withChar: method uses the write() function to write the text to the client. This method returns the number of bytes written, which may be less than the total number of bytes you told it to write. When that happens, we will need to make multiple write calls until everything is written back to the client.

We convert vptr to cString pointed to by the ptr pointer using the cStringUsingEncoding: method.

If the write() function does not send all the text to the client, the ptr pointer will be moved to point where we will begin the next write from, and nleft will be set to the number of remaining bytes to write. The while loop will continue to loop until all the text is written. If the write function returns 0 or less, we check for errorsLet's look at the recvFromSocket:withMaxChar: method:

 -(NSString *) recvFromSocket:(int)lsockfd withMaxChar:(int)max {
     char recvline[max];
     ssize_t n;
     
     if ((n=recv(lsockfd, recvline, max -1,0)) > 0) {
         recvline[n]='\0';
         return [NSString stringWithCString:recvline encoding:NSUTF8StringEncoding];
     } else {
         self.errorCode = READERROR;
         return @"Server Terminated Prematurely";
     }
 }
   
 @end

The recvFromSocket:withMaxChar: method is used to receive characters from the server and returns an NSString representing the characters received.

When the data comes in, the recv() function will put the incoming text into the buffer pointed to by the recvline pointer. The recv() function will return the number of bytes read. If the number of bytes is zero, the client is disconnected; if it is less than zero, it means there was an error.

If we successfully received text from the client, we put a NULL terminator at the end of the text, convert it to NSString, and return it.

Using the BSDSocketClient to connect to our echo server

The downloadable code contains examples for both iOS and OS X. If you run the iOS example in the iPhone simulator, the app looks like the following screenshot:

You will type the text you wish to send in the UITextField and then click on the Send button. The text that is received back from the server, in our case Hello from Packt, is displayed below the Text Received: label.

We will look at the sendPressed: method in the iOS sample code as an example of how to use the BSDSocketClient method. This method is called when you click on the Send button. Refer to the following code:

 -(IBAction)sendPressed:(id)sender {
     NSString *str = textField.text;
     BSDSocketClient *bsdCli = [[BSDSocketClient alloc] initWithAddress:@"127.0.0.1" andPort:2004];
     if (bsdCli.errorCode == NOERRROR) {
         [bsdCli writtenToSocket:bsdCli.sockfd withChar:str];
         
         NSString *recv = [bsdCli recvFromSocket:bsdCli.sockfd withMaxChar:MAXLINE];
         textRecvLabel.text = recv;
         textField.text = @"";
         
     } else {
         NSLog(@"%@",[NSString stringWithFormat:@"Error code %d recieved.  Server was not started", bsdCli.errorCode]);
     }
 }

We begin by retrieving the text that was entered in the UITextField. This is the text that we will be sending to the echo server.

We then initialize the BSDSocketClient object with an IP address of 127.0.0.1, which is the local loopback adapter, and a port number of 2004 (this needs to be the same port that your server is listening on). If you run this on an iPhone, you will need to set the IP address to the address of the computer that is running the echo server.

Once the connection with the server is established, we call the writtenToSocket:withChar: method to write the text entered in the UITextField to the server.

Now that we have sent the text, we need to retrieve what comes back. This is done by calling the recvFromSocket:withMaxChar: method to listen to the socket and retrieve any text that comes back.

Finally, we display the text that was received from the server to the screen and clear the UITextField so that we can enter in the next text.

How it works…

When we created the BSD echo server in the Creating an echo server recipe of this chapter, we went through a three-step process to prepare the TCP server. These were the socket (create a socket), bind (bind the socket to the interface), and listen (listen for incoming connections) steps.

When we create the BSD echo client, we make the connection in a two-step process. These are the socket (create a socket just like the echo server) and connect (this connects to the server) steps. The client calls the connect() function to establish a connection with the server. If no errors occur, it means we have successfully created a connection between the server and the client.

When you create your own clients, you will want to use the initWithAddress:andPort: constructor to initiate the connection and then write your own code to handle your protocol. You can see the Create a data client recipe of this chapter when we create a data client to send an image to the server.

Creating a data server


In the Creating an echo server recipe, we created a server that accepted incoming text and echoed it back to the client. That recipe demonstrated how to send and receive text through a socket connection. Now you may be asking yourself, how do I send and receive datafiles, such as images or PDF files, through a socket connection?

Sending and receiving data over a socket connection is really not that different from sending and receiving text. You go through all the same steps to set up your sockets for sending or receiving, but at the end you get NSData instead of a character array.

For this recipe, we will be using the same BSDSocketServer class that we used in the Creating an echo server recipe of this chapter, since we can reuse the initOnPort: constructor and just add the methods to implement the protocol.

Getting ready

This recipe is compatible with both iOS and OS X. No extra frameworks or libraries are required.

How to do it…

Let's start creating our data server.

Updating the BSDSocketServer header file

We will be updating the BSDSocketServer header file that we created in the Creating an echo server recipe of this chapter. The new header file looks like the following code:

 #import <Foundation/Foundation.h>
 
 #define LISTENQ 1024
 #define MAXLINE 4096
 
 typedef NS_ENUM(NSUInteger, BSDServerErrorCode) {
    NOERROR,
    SOCKETERROR,
    BINDERROR,
    LISTENERROR,
    ACCEPTINGERROR
}; 
 @interface BSDSocketServer : NSObject
 
 @property int errorCode, listenfd;
 
 -(id)initOnPort:(int)port;
 -(void)echoServerListenWithDescriptor:(int)lfd;
 -(void)dataServerListenWithDescriptor:(int)lfd;
 
 @end

The only addition to the header file is where we added the new method that will be used to listen and process new requests for our data server. As we create new types of servers, we can reuse the initOnPort: constructor since all the sockets are set up the same way. How each type of server handles the incoming request will vary; therefore, you will need a separate method to handle each of the protocols.

Updating the BSDSocketServer implementation file

Even though we only define one new method in our header file, we really need two new methods in our implementation file. The first one is the dataServerListenWithDescriptor: method we defined in the header file; refer to the following code:

  -(void)dataServerListenWithDescriptor:(int)lfd {
      int connfd;
      socklen_t clilen;
      struct sockaddr_in cliaddr;
      char buf[MAXLINE];
      
      for (;;) {
          clilen = sizeof(cliaddr);
          if ((connfd = accept(lfd, (struct sockaddr *)&cliaddr, &clilen))<0) {
              if (errno != EINTR) {
                  self.errorCode = ACCEPTINGERROR;
                  NSLog(@"Error accepting connection");
              }
          } else {
              self.errorCode = NOERRROR;
              NSString *connStr = [NSString stringWithFormat:@"Connection from %s, port %d", inet_ntop(AF_INET, &cliaddr.sin_addr,buf, sizeof(buf)),ntohs(cliaddr.sin_port)];
              NSLog(@"%@", connStr);
      
              //Multi-threaded
                dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
                [self getData:@(connfd)];
            });
      
          }
      }
  }

The dataServerListenWithDescriptor: method is almost an exact duplicate of the echoServerListenWithDescriptor: method. The dataServerListenWithDescriptor: method uses the accept() function to accept incoming connections on the supplied socket descriptor.

Within the dataServerListenWithDescriptor: method, we create a forever loop because each time a new connection is accepted, we will want to pass control of that connection to a separate thread and then come back and wait for the next connection.

The accept() function detects and initializes incoming connections on the listening socket. When a new connection is made, the accept() function will return a new socket descriptor. If there is a problem in initializing the connection, the accept function will return -1. If the connection is successfully initialized, we determine the IP address and port number that the client is connecting from and log them to the screen.

Finally, we use dispatch_async to add our getData() method to the queue. If we simply called the method directly without dispatch_async, the server would only be able to handle one incoming connection at a time. With dispatch_async, each time a new connection is established, the getData() method gets passed to the queue and the server can go back to listening for new connections.

The getData() method listens to establish connections for incoming data:

  -(void)getData:(NSNumber *) sockfdNum {
      ssize_t n;
      UInt8 buf[MAXLINE];
      NSMutableData *data = [[NSMutableData alloc] init];
      
      int sockfd = [sockfdNum intValue];
      while ((n=recv(sockfd, buf, MAXLINE -1,0)) > 0) {
          
          [data appendBytes:buf length:n];
      }
      close(sockfd);
      
      [[NSNotificationCenter defaultCenter] postNotificationName:@"postdata" object:data];
      
      NSLog(@"Done");
  }

In the strEchoServer: method that was used to retrieve text for our echo server, we used a char buf[MAXLINE] buffer to store the characters that we received. In the getData: method, we will use a UInt8 buf[MAXLINE] buffer to store our data as it comes in. We also define a NSMutableData object that holds all the data that is received.

Keep in mind that the MAXLINE constant limits the amount of data retrieved at a time and does not limit the total data. Where the MAXLINE constant is defined to be 4096, if we were receiving a file of 8000 bytes, we would receive the first 4096 bytes chunks. These first 4096 bytes would be appended to the NSMutableData object and then we would receive the next 3904 bytes, which would also be appended to the NSMutableData object, thus forming the entire file.

Once we receive all the data, we close the socket and post a notification with the name postdata. This notification can then be captured in our code so that we can do something with the incoming data once all the data is received. The iOS example expects the incoming data to be an image, so it displays the incoming data in a UIImageView.

Using the BSDSocketServer to create our data server

The downloadable code for this chapter contains samples for both iOS and OS X. Let's take a quick look at how we start the server in the iOS sample, by referring to the following code:

 -(void)startServer {
      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(newDataRecieved:) name:@"postdata" object:nil ] ;
      
      BSDSocketServer *bsdServ = [[BSDSocketServer alloc] initOnPort:2006];
      if (bsdServ.errorCode == NOERRROR) {
          [bsdServ dataServerListenWithDescriptor:bsdServ.listenfd];
          
      } else {
          NSLog(@"%@",[NSString stringWithFormat:@"Error code %d recieved.  Server was not started", bsdServ.errorCode]);
      }
  
  }
  
  -(void)newDataRecieved:(NSNotification *)notification {
      NSData *data = notification.object;
      imageView.image = [UIImage imageWithData:data];
  }

In the startSvr method, the first thing we do is set up a notification that will listen for the postdata notification. When the postdata notification is received, the listener will send the data to the newDataReceived: method to update our imageView with the data.

We initialize the BSDSocketServer object and tell it to listen on port 2006. If there are no errors while initializing the server, we call the dataServerListenWithDescriptor: method, which will listen for incoming data and process it.

How it works…

When we created the data server, we used the same initOnPort: constructor that we used for the echo server. This is because the same socket, bind, and listen steps are required for both. What we had to change were the methods that listened and processed incoming connections. When you create your own servers, you will also want to use the initOnPort: constructor and then write your own methods to handle the incoming connections.

Once we have our socket created, we can call the method that will listen on the socket. This is the dataServerListenWithDescriptor: method. This method uses the accept() function to listen for incoming connections. The accept() function will create a new socket for each incoming connection and then remove the connection from the listen queue. If you recall, we defined that the listen queue can contain up to 1024 connections before it stops accepting new ones.

The getData: method is where we actually implement our server. This method uses the recv() function to receive the incoming data. As the data comes in, we append it to the NSMutableData object until all the data is received.

Creating a data client


In the Creating a data server recipe of this chapter, we updated our BSDSocketServer class so we could set up a server that could receive data. In this recipe, we will be updating our BSDSocketClient class so we can set up a client to upload data to our data server.

Getting ready

This recipe is compatible with both iOS and OS X. No extra frameworks or libraries are required.

How to do it…

Let's update the BSDSocketClient class to include our data client.

Updating the BSDSocketClient header file

Since we will be able to use the same constructor (initWithAddress:andPort:) that we used when we connected to the echo server, all we need to do is to add a method to send the data itself. This method will be called sendData:toSocket:. The following is the new BSDSocketClient header file:

  #import <Foundation/Foundation.h>
  
  typedef NS_ENUM(NSUInteger, BSDClientErrorCode) {
    NOERRROR,
    SOCKETERROR,
    CONNECTERROR,
    READERROR,
    WRITEERROR
};  
  #define MAXLINE 4096
  
  @interface BSDSocketClient : NSObject
  
  @property int errorCode, sockfd;
  
  -(id)initWithAddress:(NSString *)addr andPort:(int)port;
  -(ssize_t) writtenToSocket:(int)sockfdNum withChar:(NSString *)vptr;
  -(NSString *) recvFromSocket:(int)lsockfd withMaxChar:(int)max;
  -(ssize_t)sendData:(NSData *)data toSocket:(int)lsockfd;
  
  @end

Updating the BSDSocketClient implementation file

We now need to add the sendData:toSocket: method to our BSDSocketClient class:

-(ssize_t)sendData:(NSData *)data toSocket:(int)lsockfd
 {
     NSLog(@"sending");
     ssize_t n;
     const UInt8 *buf = (const UInt8 *)[data bytes];
 
     if ((n = send(lsockfd, buf,[data length],0)) <=0) {
         errorCode = WRITEERROR;
         return -1;
     } else {
         errorCode = NOERRROR;
         return n;
     }
 }

The sendData:toSocket: method accepts two parameters: the data to send to the server and the socket descriptor to which we want to send the data. Since the BSD Socket Library does not recognize the NSData objects, we will need to convert the data to bytes and then to a UInt8 buffer prior to sending it.

Once we have our UInt8 buffer, we use the send() function to send the data to the server. The send() function will return the number of bytes sent to the server; if that number is less than 0, it means there is a problem and we return an error.

Using the BSDSocketClient to connect to our data server

Let's take a look at the sample code that uses the sendData:toSocket method:

  BSDSocketClient *bsdCli = [[BSDSocketClient alloc] initWithAddress:@"127.0.0.1" andPort:2006];
  if (bsdCli.errorCode == NOERRROR) {
      NSData *data = [NSData dataWithContentsOfFile:@"/Users/hoffmanjon/Documents/GreenGuyLarge.png"];
      [bsdCli sendData:data toSocket:bsdCli.sockfd];
  } else {
      NSLog(@"%@",[NSString stringWithFormat:@"Error code %d recieved. ", bsdCli.errorCode]);
  }

We start off by initializing the BSDSocketClient object with the IP address 127.0.0.1 and with a port of 2006. If you are running the sample server on another device, you will need to change the IP address. If there are no issues initializing the client, we load an image and convert it to an NSData object. You will need to change the location of the file to the location on your machine that contains an image.

We then pass the NSData object that contains the image to the sendData:toSocket method.

How it works…

When we created the BSD data server, we went through a three-step process to prepare the TCP server and to create listen on the socket. These were socket (create a socket), bind (bind the socket to the interface), and listen (listen for incoming connections).

When we create the BSD data client, we make the connection in a two-step process. These steps are socket (create a socket just like the echo server) and connect (this connects to the server). The client calls the connect() function to establish a connection with the server. If no error occurs, we have a connection between the server and the client. This connection process is the same code we used to establish a connection with the echo server.

Once we have the connection established with the server, we need to send our data to the server. In our example, we will be sending an image file over; however, this same code can be used to send any binary file. The client and the server just need to agree on what type of file is to be sent.

The first thing we need to do is to convert the file to an NSData object and pass that to the sendData:toScoket: method. When the sendData:toSocket: method has the NSData object, it converts it to a Uint8 buffer. We then use the send() function to send the Uint8 buffer to the server.

Left arrow icon Right arrow icon

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Jan 22, 2014
Length: 300 pages
Edition :
Language : English
ISBN-13 : 9781849698085
Category :
Languages :
Tools :

What do you get with a Packt Subscription?

Free for first 7 days. £16.99 p/m after that. Cancel any time!
Product feature icon Unlimited ad-free access to the largest independent learning library in tech. Access this title and thousands more!
Product feature icon 50+ new titles added per month, including many first-to-market concepts and exclusive early access to books as they are being written.
Product feature icon Innovative learning tools, including AI book assistants, code context explainers, and text-to-speech.
Product feature icon Thousands of reference materials covering every tech concept you need to stay up to date.
Subscribe now
View plans & pricing

Product Details

Publication date : Jan 22, 2014
Length: 300 pages
Edition :
Language : English
ISBN-13 : 9781849698085
Category :
Languages :
Tools :

Packt Subscriptions

See our plans and pricing
Modal Close icon
£16.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
£169.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just £5 each
Feature tick icon Exclusive print discounts
£234.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just £5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total £ 79.98
iOS and OS X Network Programming Cookbook
£41.99
Core Data iOS Essentials
£37.99
Total £ 79.98 Stars icon
Banner background image

Table of Contents

8 Chapters
BSD Socket Library Chevron down icon Chevron up icon
Apple Low-level Networking Chevron down icon Chevron up icon
Using Libnet Chevron down icon Chevron up icon
Using Libpcap Chevron down icon Chevron up icon
Apple High-level Networking Chevron down icon Chevron up icon
Bonjour Chevron down icon Chevron up icon
AFNetworking 2.0 Library Chevron down icon Chevron up icon
MKNetworkKit Chevron down icon Chevron up icon

Customer reviews

Top Reviews
Rating distribution
Full star icon Full star icon Full star icon Full star icon Half star icon 4.5
(6 Ratings)
5 star 50%
4 star 50%
3 star 0%
2 star 0%
1 star 0%
Filter icon Filter
Top Reviews

Filter reviews by




Amazon Customer Jun 21, 2016
Full star icon Full star icon Full star icon Full star icon Full star icon 5
lots of help
Amazon Verified review Amazon
RV Jul 30, 2014
Full star icon Full star icon Full star icon Full star icon Full star icon 5
I just read a new book for iOS and OS X programming i.e "iOS and OS X network programming cookbook".I have always liked cookbooks and this one is not an exception. Author has covered all the topics with practical examples. I have gone through few iOS books and this one is best so far.All in all, this is a cookbook I'm happy to have added to my collection.
Amazon Verified review Amazon
Amazon Customer May 02, 2014
Full star icon Full star icon Full star icon Full star icon Full star icon 5
The book certainly is a good refresher on networking basics tcp/ip udp, posix and how it fits in with osx and ios. There is loads plenty of nice beginner stuff like building echo servers and constructing ping packets so readers with no networking knowledge at all can be broken in gently.There is a comprehensive chapter on Libnet that starts with a refresher on network layers and steadily builds in complexity, again making it suitable for all skill levels. It's hard to exaggerate the thrill of sending yourself messages even if it is just across the living room. It is just so much fun being Vint Cerf just for an hour or two.Perhaps the book could have had even more in it. I loved working through the tutorials, especially as I had never done anything remotely network orientated on Mac before. But the book weighs in over 270 pages so it would be unfair to criticise really.Overall the information was extremely compelling and definitely a candidate for cookbook 2 . I would read it for sure.
Amazon Verified review Amazon
Eric Jenkinson Apr 09, 2014
Full star icon Full star icon Full star icon Full star icon Empty star icon 4
The author starts with the BSD socket library to build up to Apple's CFNetworking and continues on to Apple's high level networking API such NSXMLParser, NSURL and NSData.The book covers open source libraries as well such as AFNetworking, MKNetworkKit,libnet and Libpcap, thus providing the reader with a full arsenal of networkingknowledge for Mac OS X and iOS.The recipes included were well written and extensive. My favorites are the Peer to Peer Bluetooth and Bonjour recipes. You can choose to read the book by picking a section that is of interest or simply start at the beginning and go cover to cover. While the book is a "cookbook" there is continuity between the chapters unlike many other cookbooks on the market.
Amazon Verified review Amazon
rodrigobirriel Apr 07, 2014
Full star icon Full star icon Full star icon Full star icon Empty star icon 4
This book is great, very useful. It talks about everything about networking from the low level (function in C) to top level with popular framework as AFNetworking. Specially great for clarifying concepts. The example code is very useful to bring ideas to the land.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

What is included in a Packt subscription? Chevron down icon Chevron up icon

A subscription provides you with full access to view all Packt and licnesed content online, this includes exclusive access to Early Access titles. Depending on the tier chosen you can also earn credits and discounts to use for owning content

How can I cancel my subscription? Chevron down icon Chevron up icon

To cancel your subscription with us simply go to the account page - found in the top right of the page or at https://subscription.packtpub.com/my-account/subscription - From here you will see the ‘cancel subscription’ button in the grey box with your subscription information in.

What are credits? Chevron down icon Chevron up icon

Credits can be earned from reading 40 section of any title within the payment cycle - a month starting from the day of subscription payment. You also earn a Credit every month if you subscribe to our annual or 18 month plans. Credits can be used to buy books DRM free, the same way that you would pay for a book. Your credits can be found in the subscription homepage - subscription.packtpub.com - clicking on ‘the my’ library dropdown and selecting ‘credits’.

What happens if an Early Access Course is cancelled? Chevron down icon Chevron up icon

Projects are rarely cancelled, but sometimes it's unavoidable. If an Early Access course is cancelled or excessively delayed, you can exchange your purchase for another course. For further details, please contact us here.

Where can I send feedback about an Early Access title? Chevron down icon Chevron up icon

If you have any feedback about the product you're reading, or Early Access in general, then please fill out a contact form here and we'll make sure the feedback gets to the right team. 

Can I download the code files for Early Access titles? Chevron down icon Chevron up icon

We try to ensure that all books in Early Access have code available to use, download, and fork on GitHub. This helps us be more agile in the development of the book, and helps keep the often changing code base of new versions and new technologies as up to date as possible. Unfortunately, however, there will be rare cases when it is not possible for us to have downloadable code samples available until publication.

When we publish the book, the code files will also be available to download from the Packt website.

How accurate is the publication date? Chevron down icon Chevron up icon

The publication date is as accurate as we can be at any point in the project. Unfortunately, delays can happen. Often those delays are out of our control, such as changes to the technology code base or delays in the tech release. We do our best to give you an accurate estimate of the publication date at any given time, and as more chapters are delivered, the more accurate the delivery date will become.

How will I know when new chapters are ready? Chevron down icon Chevron up icon

We'll let you know every time there has been an update to a course that you've bought in Early Access. You'll get an email to let you know there has been a new chapter, or a change to a previous chapter. The new chapters are automatically added to your account, so you can also check back there any time you're ready and download or read them online.

I am a Packt subscriber, do I get Early Access? Chevron down icon Chevron up icon

Yes, all Early Access content is fully available through your subscription. You will need to have a paid for or active trial subscription in order to access all titles.

How is Early Access delivered? Chevron down icon Chevron up icon

Early Access is currently only available as a PDF or through our online reader. As we make changes or add new chapters, the files in your Packt account will be updated so you can download them again or view them online immediately.

How do I buy Early Access content? Chevron down icon Chevron up icon

Early Access is a way of us getting our content to you quicker, but the method of buying the Early Access course is still the same. Just find the course you want to buy, go through the check-out steps, and you’ll get a confirmation email from us with information and a link to the relevant Early Access courses.

What is Early Access? Chevron down icon Chevron up icon

Keeping up to date with the latest technology is difficult; new versions, new frameworks, new techniques. This feature gives you a head-start to our content, as it's being created. With Early Access you'll receive each chapter as it's written, and get regular updates throughout the product's development, as well as the final course as soon as it's ready.We created Early Access as a means of giving you the information you need, as soon as it's available. As we go through the process of developing a course, 99% of it can be ready but we can't publish until that last 1% falls in to place. Early Access helps to unlock the potential of our content early, to help you start your learning when you need it most. You not only get access to every chapter as it's delivered, edited, and updated, but you'll also get the finalized, DRM-free product to download in any format you want when it's published. As a member of Packt, you'll also be eligible for our exclusive offers, including a free course every day, and discounts on new and popular titles.