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

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.

No comments:

Post a Comment