Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.
Showing posts with label Xcode 8 beta 3. Show all posts
Showing posts with label Xcode 8 beta 3. Show all posts

2016-03-02

Socket Programming in Swift: Part 9 - SwifterSockets

Updated on 2016-08-12 for Swift 3 Xcode 8 beta 3

I admit it, the name kind-a-sucks. But after having published SwifterJSON, SwifterLog it seems that SwifterSockets is a logical choice. Besides, calling it SocketUtils (used as working name) creates naming conflicts with the thousands of other SocketUtils out there. So SwifterSockets it is.

What is it? It is a collection of APIs that use the Unix socket calls to implement network transfers.

There are few general purpose APIs:

Some socket related helper functions:

enum SocketAddress {...}
func isValidIpAddress(_ address: String) -> Bool
func sockaddrDescription(_ addr: UnsafePointer<sockaddr>) -> (ipAddress: String?, portNumber: String?) { ... }
func fdZero(_ set: inout fd_set) { ... }
func fdSet(_ fd: Int32, set: inout fd_set) { ... }
func fdClr(_ fd: Int32, set: inout fd_set) { ... }
func fdIsSet(_ fd: Int32, set: inout fd_set) -> Bool { ... }
func logAddrInfoIPAddresses(_ infoPtr: UnsafeMutablePointer<addrinfo>) -> String { ... }
func logSocketOptions(_ socket: Int32) -> String { ... }
func closeSocket(_ socket: Int32?) -> Bool?


Then we get to the meat of the matter, initializing a server socket:

func setupServer(
        onPort port: String,
        maxPendingConnectionRequest: Int32) -> SetupServerReturn { ... }
func setupServerOrThrow(
        onPort port: String,
        maxPendingConnectionRequest: Int32) throws -> Int32 { ... }
func setupServerOrThrowAsync(
        onPort port: String,
        maxPendingConnectionRequest: Int32,
        postProcessingQueue: DispatchQueue,
        postProcessor: SetupServerPostProcessing) throws { ... }

Initializing a client socket:

func connectToServer(atAddress address: String, atPort port: String) -> ClientResult { ... }
func connectToServerOrThrow(atAddress address: String, atPort port: String) throws -> Int32 { ... }
func connectToServerOrThrowAsync(
        atAddress address: String,
        atPort port: String,
        onQueue queue: DispatchQueue,
        postProcessor: ClientPostProcessing) throws { ... }
func connectToServerOrThrowTransmitAsync(
        atAddress address: String,
        atPort port: String,
        transmitQueue: DispatchQueue,
        transmitData: String,
        transmitTimeout: TimeInterval,
        transmitTelemetry: TransmitTelemetry?,
        transmitPostProcessor: TransmitPostProcessing?) throws { ... }
func connectToServerOrThrowTransmitAsync(
        atAddress address: String,
        atPort port: String,
        transmitQueue: DispatchQueue,
        transmitData: Data,
        transmitTimeout: TimeInterval,
        transmitTelemetry: TransmitTelemetry?,

        transmitPostProcessor: TransmitPostProcessing?) throws { ... }
func connectToServerOrThrowTransmitAsync(
        atAddress address: String,
        atPort port: String,
        queue: DispatchQueue,
        transmitData: UnsafeBufferPointer<UInt8>,
        transmitTimeout: TimeInterval,
        transmitTelemetry: TransmitTelemetry?,
        transmitPostProcessor: TransmitPostProcessing?) throws { ... }

Setting up a data transfer:

func transmit(
        toSocket socket: Int32,
        fromBuffer buffer: UnsafeBufferPointer<UInt8>,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) -> TransmitResult { ... }
func transmit(
        toSocket socket: Int32,
        data: Data,
        timeout: TimeInterval,

        telemetry: TransmitTelemetry?) -> TransmitResult { ... }
func transmit(
        toSocket socket: Int32,
        string: String,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) -> TransmitResult { ... }
func transmitOrThrow(
        toSocket socket: Int32,
        fromBuffer buffer: UnsafeBufferPointer<UInt8>,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) throws { ... }
func transmitOrThrow(
        toSocket socket: Int32,
        data: Data,
        timeout: TimeInterval,

        telemetry: TransmitTelemetry?) throws { ... }
func transmitOrThrow(
        toSocket socket: Int32,
        string: String,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?) throws { ... }
func transmitAsync(
        onQueue queue: DispatchQueue,
        toSocket socket: Int32,
        fromBuffer buffer: UnsafeBufferPointer<UInt8>,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?,
        postProcessor: TransmitPostProcessing?) { ... }
func transmitAsync(
        onQueue queue: DispatchQueue,
        toSocket socket: Int32,
        data: Data,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?,

        postProcessor: TransmitPostProcessing?) { ... }
func transmitAsync(
        onQueue queue: DispatchQueue,
        toSocket socket: Int32,
        string: String,
        timeout: TimeInterval,
        telemetry: TransmitTelemetry?,
        postProcessor: TransmitPostProcessing?) { ... }

Setting up the receiving end:

func receiveBytes(
        fromSocket socket: Int32,
        intoBuffer buffer: UnsafeMutableBufferPointer<UInt8>,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?) -> ReceiveResult { ... }
func receiveData(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,

        telemetry: ReceiveTelemetry?) -> ReceiveResult { ... }
func receiveString(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?) -> ReceiveResult { ... }
func receiveBytesOrThrow(
        fromSocket socket: Int32,
        intoBuffer buffer: UnsafeMutableBufferPointer<UInt8>,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?) throws -> Int { ... }
func receiveNSDataOrThrow(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,

        telemetry: ReceiveTelemetry?) throws -> Data { ... }
func receiveStringOrThrow(
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,

        telemetry: ReceiveTelemetry?) throws -> String { ... }
func receiveAsync(
        onQueue queue: DispatchQueue,
        fromSocket socket: Int32,
        timeout: TimeInterval,
        dataEndDetector: DataEndDetector,
        telemetry: ReceiveTelemetry?,
        postProcessor: ReceivePostProcessing?) { ... }

And to accept a connection request:

func acceptNoThrow(
        onSocket socket: Int32,
        abortFlag: inout Bool,
        abortFlagPollInterval: TimeInterval?,
        timeout: TimeInterval? = nil,
        telemetry: AcceptTelemetry? = nil)
        -> AcceptResult { ... }
func acceptOrThrow(
        onSocket socket: Int32,
        abortFlag: inout Bool,
        abortFlagPollInterval: TimeInterval?,
        timeout: TimeInterval? = nil,
        telemetry: AcceptTelemetry?) throws -> Int32 { ... }

Here is how to setup a server that accepts connection requests and processes the received data:

func serverSetup_oldSchool() {
    
    
    // Assume that incoming data ends when a 0x00 byte is received
    
    class DataEndsOnZeroByte: DataEndDetector {
        func endReached(buffer: UnsafeBufferPointer<UInt8>) -> Bool {
            for byte in buffer {
                if byte == 0x00 { return true }
            }
            return false
        }
    }

    
    // Setup a socket for usage as the server socket
    
    let setupResult = SwifterSockets.setupServer(onPort: "80", maxPendingConnectionRequest: 10)
    
    guard case let SwifterSockets.SetupServerReturn.socket(serverSocket) = setupResult else { return }
    
    
    
    // Start the accept loop (ends on accept errors only)
    
    var neverAborts: Bool = false
    
    while true {
        
        
        // Accept a (the next) connection request
        
        let acceptResult = SwifterSockets.acceptNoThrow(onSocket: serverSocket, abortFlag: &neverAborts, abortFlagPollInterval: 10.0, timeout: nil, telemetry: nil)
        
        guard case let SwifterSockets.AcceptResult.accepted(socket: receiveSocket) = acceptResult else { break }
        
        
        // Receive the incoming data
        
        let dataEndsOnZeroByte = DataEndsOnZeroByte()
        
        let receiveResult = SwifterSockets.receiveData(fromSocket: receiveSocket, timeout: 10.0, dataEndDetector: dataEndsOnZeroByte, telemetry: nil)
        
        guard case let SwifterSockets.ReceiveResult.ready(data: receivedData) = receiveResult else { break }
        
        
        // Process the data that was received
        
        processReceivedData(data: receivedData as? Data)
        
        
        // Close the socket
        
        SwifterSockets.closeSocket(receiveSocket)
    }
    
    SwifterSockets.closeSocket(serverSocket)
}

SwifterSockets is available from Github
The project homepage is at Balancingrock

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.

2015-11-09

Socket Programming in Swift: Part 6 - select and recv

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

In the previous post we have progressed to the point where a new thread was started to receive the actual data that is being send to the application.

While the accept call can in fact 'hang' when the other side messes something up, that is an unlikely scenario. The accept call is part of the TCP/IP stack that is implemented in the OS. The recv call however depends on "the other application" doing things right. Which under some circumstances cannot always be assumed.

Hence we will use this opportunity to introduce the select call.

It is easiest to see in code:

(PS: This code does compile in playground assuming the fdSet is present in a resource called "SwifterSockets". But it is unlikely that it is usable as is. For example the 'processData' function should be defined someplace else, not inside this function. If it must be kept inside this function, it would probably be better not to spin off the 'processData' into its own thread.)

func receiveAndDispatch(socket: Int32) {
    
    let dataProcessingQueue = DispatchQueue.main
    
    let bufferSize = 100*1024 // Application dependant
    var requestBuffer: Array<UInt8> = Array(repeating: 0, count: bufferSize)
    var requestLength: Int = 0

    func requestIsComplete() -> Bool {
        // This function should find out if all expected data was received and return 'true' if it did.
        return true
    }
    
    func processData(data: Data) {
        // This function should do something with the received data
    }
    
    // =========================================================================================
    // This loop stays active as long as there is data left to receive, or until an error occurs
    // =========================================================================================
    
    RECEIVER_LOOP: while true {
        
        
        // =====================================================================================
        // Use the select API to wait for anything to happen on our client socket only within
        // the timeout period
        // =====================================================================================
        
        let numOfFd:Int32 = socket + 1
        var readSet:fd_set = fd_set(fds_bits: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))
        var timeout:timeval = timeval(tv_sec: 10, tv_usec: 0)
        
        SwifterSockets.fdSet(socket, set: &readSet)
        let status = select(numOfFd, &readSet, nil, nil, &timeout)
        
        // Because we only specified 1 FD, we do not need to check on which FD the event was received
        
        
        // =====================================================================================
        // In case of a timeout, close the connection
        // =====================================================================================
        
        if status == 0 {
            
            let message = "Timeout during select."
            print(message)
            close(socket)
            break RECEIVER_LOOP
        }
        
        
        // =====================================================================================
        // In case of an error, close the connection
        // =====================================================================================
        
        if status == -1 {
            
            let errString = String(utf8String: strerror(errno)) ?? "Unknown error code"
            let message = "Error during select, message = \(errno) (\(errString))"
            print(message)
            close(socket)
            break RECEIVER_LOOP
        }
        
        
        // =====================================================================================
        // Use the recv API to see what happened
        // =====================================================================================
        
        let bytesRead = recv(
            socket,
            &requestBuffer[requestLength],
            bufferSize,
            0)
        
        
        // =====================================================================================
        // In case of an error, close the connection
        // =====================================================================================
        
        if bytesRead == -1 {
            
            let errString = String(utf8String: strerror(errno)) ?? "Unknown error code"
            let message = "Recv error = \(errno) (\(errString))"
            print(message)
            
            // The connection might still support a transfer, it could be tried to get a message to the client. Not in this example though.
            
            close(socket)
            break RECEIVER_LOOP
        }
        
        
        // =====================================================================================
        // If the client closed the connection, close our end too
        // =====================================================================================
        
        if bytesRead == 0 {
            
            let message = "Client closed connection"
            print(message)
            close(socket)
            break RECEIVER_LOOP
        }
        
        
        // =====================================================================================
        // If the request is completely received, dispatch it to the dispatchQueue
        // =====================================================================================
        
        let message = "Received \(bytesRead) bytes from the client"
        print(message)
        
        requestLength = requestLength + bytesRead
        
        if requestIsComplete() {
            
            let receivedData = Data(bytes: requestBuffer[0 ... requestLength])
            dataProcessingQueue.async() { processData(data: receivedData) }
            
            close(socket)
            break RECEIVER_LOOP
        }
    }

}

A whole lot of stuff going on here. First off, the select call waits until the time-out to see if there is any activity on the given file descriptor. Yes, file descriptor. However a socket descriptor is a kind of file descriptor thus we can use them interchangeably.

The select call needs a set of file descriptors that specify which file descriptors must be monitored. The C examples you will find all use FD_SET, FD_CLR etc to do the heavy lifting. These are C-macro's. However in Swift these are not available. I have implemented replacement functions that mimic these macro's. You can find them here.

When select returns there are three possibilities: something happened, timeout or error. If something was received the select call will tell us on which socket it was. But since in the above example only one file descriptor is specified, the code does not have to check for this. But it does check if there was an error or a timeout.

Once the recv call receives data a check is needed to see if all data was received. Even for the shortest of data transfers (> 1 byte) it is not guaranteed that all data is received in a single call to recv. Every application will have a different way of knowing when the data is complete. For example if the app is expecting a JSON message it can count the number of '{' and '}'. When the numbers are equal that will signify the completion of the message. (Assuming only JSON objects are allowed at the top level)

When the message is complete the RECEIVER_LOOP will start processing the data on a different thread while terminating itself.

2016-01-09: Part 7 - Client side considerations

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.