Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-10-30

Socket Programming in Swift: Part 1 - getaddrinfo

Updated on 2016-08-08 for Swift 3 in Xcode 8 beta 3 & use in playground.

The first thing to mention when blogging about socket programming is that you really, really should read Beej's Guide to Network Programming it's free, so you don't have an excuse...

In fact, I created this series while working through this guide. Thus much of the code in this series is more or less the Swift version of the code in that book. That said, I will not discuss the general nature of socket programming. I could not do a better job that Beej did anyway. This series will focus on the Swift side of things and some Cocoa aspects.

Still I do want to give some perspective first. So here is the sequence of calls to be made to implement the server side of things:

1) getaddrinfo(): To retrieve the information needed to create a socket descriptor.
2) socket(): To create a socket descriptor
3) setsockopt(): To set the options on the socket
4) bind(): To connect the socket descriptor to a local port
5) freeaddrinfo(): To deallocate used resources
6) listen(): To listen for connection requests
7) -- do some cocoa magic
8) accept(): To accept connection requests
9) recv(): To read the data that is received from the client
10) close()

The client side of things will follow in a later series of posts.

In this post: getaddrinfo

This is the code for Swift 3 Xcode 8, copy & paste into Playground:

// Defs for playground purposes

let servicePortNumber = "3333"
let applicationInDebugMode = true

/// Returns the (host, service) tuple for a given sockaddr

func sockaddrDescription(addr: UnsafePointer<sockaddr>) -> (String?, String?) {
    
    var host : String?
    var service : String?
    
    var hostBuffer = [CChar](repeating: 0, count: Int(NI_MAXHOST))
    var serviceBuffer = [CChar](repeating: 0, count: Int(NI_MAXSERV))
    
    if getnameinfo(
        addr,
        socklen_t(addr.pointee.sa_len),
        &hostBuffer,
        socklen_t(hostBuffer.count),
        &serviceBuffer,
        socklen_t(serviceBuffer.count),
        NI_NUMERICHOST | NI_NUMERICSERV)
        
        == 0 {
        
        host = String(cString: hostBuffer)
        service = String(cString: serviceBuffer)
    }
    return (host, service)
    
}


// General purpose status variable, used to detect error returns from socket functions

var status: Int32 = 0


// ==================================================================
// Retrieve the information necessary to create the socket descriptor
// ==================================================================

// Protocol configuration, used to retrieve the data needed to create the socket descriptor

var hints = addrinfo(
    ai_flags: AI_PASSIVE,       // Assign the address of the local host to the socket structures
    ai_family: AF_UNSPEC,       // Either IPv4 or IPv6
    ai_socktype: SOCK_STREAM,   // TCP
    ai_protocol: 0,
    ai_addrlen: 0,
    ai_canonname: nil,
    ai_addr: nil,
    ai_next: nil)


// For the information needed to create a socket (result from the getaddrinfo)

var servinfo: UnsafeMutablePointer<addrinfo>? = nil


// Get the info we need to create our socket descriptor

status = getaddrinfo(
    nil,                        // Any interface
    servicePortNumber,          // The port on which will be listenend
    &hints,                     // Protocol configuration as per above
    &servinfo)                  // The created information


// Cop out if there is an error

if status != 0 {
    var strError: String
    if status == EAI_SYSTEM {
        strError = String(validatingUTF8: strerror(errno)) ?? "Unknown error code"
    } else {
        strError = String(validatingUTF8: gai_strerror(status)) ?? "Unknown error code"
    }
    print(strError)

else {

   // Print a list of the found IP addresses

   if applicationInDebugMode {
       var info = servinfo
       while info != nil {
           let (clientIp, service) = sockaddrDescription(addr: info!.pointee.ai_addr)
           let message = "HostIp: " + (clientIp ?? "?") + " at port: " + (service ?? "?")
           print(message)
           info = info!.pointee.ai_next
       }
   }   
}

There are a few noteworthy things:
a) I would like to keep the code agnostic with respect to IPv4 or IPv6, thus the use of AF_UNSPEC in the hints structure.
b) Notice that the definition of servinfo only defines a pointer, not the actual structure. It only allocates memory for the pointer, not the structure it points at.
c) The getaddrinfo call is given a pointer to servinfo i.e. a pointer to a pointer. getaddrinfo itself will allocate memory for the structure that servinfo will point at. However after the call to getaddrinfo we "own" the memory that was allocated. And thus must dispose of it later when we no longer need it. (See step 5) (Note: This has nothing to do with ARC, we really need to dispose of the memory ourself otherwise it will create a "memory leak")
d) The servicePortNumber is the port we want the server to listen on for connection requests. A HTTP server would normally listen on port 80.
e) The call to gai_strerror will produce a readable error message, still it is always useful to also have the original error code if we want to google for possible reasons.
f) The call to  uses our (free) SwifterLog error logging mechanism. See the link on the left hand side.
g) The addrinfo structure is recursive, hence when the global variable applicationInDebugMode indicates that the application is in debug mode, it will log a list of all addresses the server is using. These can be IPv4 and IPv6 addresses on multiple network interfaces (multiple ethernet connections, wifi connection, etc)

Socket Programming in Swift: part 2 - socket and setsockopt

Check out my Port Spy app in the App Store. A utility that helps you debug your socket based application and includes its own source code. So you can see first hand how to implement socket based io in Swift. And you will be helping this blog!

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

2 comments:

  1. if status != 0 {
    var strError: String
    if status == EAI_SYSTEM {
    strError = String(validatingUTF8: strerror(errno)) ?? "Unknown error code"
    } else {
    strError = String(validatingUTF8: gai_strerror(status)) ?? "Unknown error code"
    }
    print(strError)
    return
    }

    This does not compile as return is outside a function

    ReplyDelete
    Replies
    1. Thanks for catching that Matt. I have updated the code.

      Delete