BSD Socket Library

Exclusive offer: get 50% off this eBook here
iOS and OS X Network Programming Cookbook

iOS and OS X Network Programming Cookbook — Save 50%

Over 50 recipes to develop network applications in both the iOS and OS X environment with this book and ebook

$29.99    $15.00
by Jon Hoffman | January 2014 | Cookbooks

In this article by Jon Hoffman, author of iOS and OS X Network Programming Cookbook, we will cover:

  • Finding the byte order of your device
  • Retrieving network address information
  • Performing network address resolution
  • Creating an echo server

(For more resources related to this topic, see here.)

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.

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. 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.

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.

Finding the byte order of your device

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

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 article, we will put the C code within an Objective-C wrapper to make it easy to port to your projects.

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; }

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.

iOS and OS X Network Programming Cookbook Over 50 recipes to develop network applications in both the iOS and OS X environment with this book and ebook
Published: January 2014
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

Retrieving network address information

Many programs will need to know the network information about the available interfaces on the device they are running on. Here you will learn 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.

We will also be introduced to 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.

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 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-gt;ifa_addr-gt;sa_family == AF_INET) { NSLog(@"IPv4"); ipversion = AF_INET; } else if(temp_addr-gt;ifa_addr-gt;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-gt;ifa_addr)-gt;sin_addr,
    naddr,INET_ADDRSTRLEN); NSLog(@"Address: %@",[NSString stringWithUTF8String:naddr]); if ((struct sockaddr_in6 *)
    temp_addr-gt;ifa_netmask != NULL) { inet_ntop(ipversion,&((struct sockaddr_in *)temp_addr-gt;
    ifa_netmask)-gt;sin_addr,nmask,INET_ADDRSTRLEN); NSLog(@"Netmask: %@",
    [NSString stringWithUTF8String:nmask]); } if ((struct sockaddr_in6 *)temp_addr-gt;ifa_dstaddr != NULL) { inet_ntop(ipversion,&((struct sockaddr_in *)
    temp_addr-gt;ifa_dstaddr)-gt;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.

Sample projects use a NetworkAddressStore class to store the information returned by the getifaddrs() functions.

Performing 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.

Here 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.

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 @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 of 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 through 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…

Here 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.

iOS and OS X Network Programming Cookbook Over 50 recipes to develop network applications in both the iOS and OS X environment with this book and ebook
Published: January 2014
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

Creating an echo server

Here 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.

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.

How to do it….

Let's get started by creating a BSDSocketServer class that will greatly simplify the creation of a BSD socket server.

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.

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.

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 of 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.

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.

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.

Summary

In this article, we used the BSD Socket Library in their iOS and OS X applications. While this article will show how to obtain network address information and also how to check the network status, the primary focus will be on creating client/server applications for both iOS and OS X devices.

Resources for Article:


Further resources on this subject:


About the Author :


Jon Hoffman

Jon Hoffman  has close to 20 years of experience in the field of Information Technology. Over these 20 years, Jon has worked in the areas of system administration, network administration, network security, development and architecture. Currently, he works as a software engineer at Syn-Tech Systems. He has started a network development blog at http://network-development.blogspot.com that will enhance and expand on the material covered in this book.

Over the past five years, he has developed numerous applications for the iOS platform. These include several apps that he has published in the App Store, apps that he has written for third parties, and numerous enterprise applications.

What really drives Jon are the challenges in Information Technology; there is nothing more exhilarating for him than overcoming a challenge. Some of Jon's other interests are watching baseball (Go Sox!) and basketball (Go Celtics!). Jon also really enjoys Taekwondo; he and his eldest daughter Kailey are on pace to get their black belts together in the spring of 2014.

Books From Packt


Xamarin Mobile Application Development for iOS
Xamarin Mobile Application Development for iOS

iOS7 Game Development
iOS7 Game Development

Developing AR Games for iOS and Android
Developing AR Games for iOS and Android

iWork for Mac OS X Cookbook
iWork for Mac OS X Cookbook

Application Development with Parse using iOS SDK
Application Development with Parse using iOS SDK

Xcode 4 iOS Development Beginner's Guide
Xcode 4 iOS Development Beginner's Guide

Xcode 4 Cookbook
Xcode 4 Cookbook

iPhone User Interface Cookbook
iPhone User Interface Cookbook

Your rating: None Average: 1.8 (4 votes)

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
B
m
U
G
Q
W
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software