iosswiftsocketsnsrunloopnsoutputstream

Use NSOutputstream and NSInputstream to write and read every interval


I'm trying to use NSOutputstream and NSInputstream to write and read every interval, say 5 seconds. This is to communicate with a C# program on windows in P2P fashion.

I want to use NSOutputstream to send the C# program a request every 5 seconds and then NSInputstream will read the response from the C# program.

Currently I have:

class Connection : NSObject, NSStreamDelegate {
var serverAddress: CFString
let serverPort: UInt32 = 11000

private var inputStream: NSInputStream!
private var outputStream: NSOutputStream!

init(serverAddress: CFString) {
    self.serverAddress = serverAddress
}

func connect() {
    print("connecting...")

    var readStream:  Unmanaged<CFReadStream>?
    var writeStream: Unmanaged<CFWriteStream>?

    CFStreamCreatePairWithSocketToHost(nil, self.serverAddress, self.serverPort, &readStream, &writeStream)

    self.inputStream = readStream!.takeRetainedValue()
    self.outputStream = writeStream!.takeRetainedValue()

    self.inputStream.delegate = self
    self.outputStream.delegate = self

    self.inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)
    self.outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode)

    self.inputStream.open()
    self.outputStream.open()

    print("connected...")
}

func disconnect() {
    self.inputStream.close()
    self.outputStream.close()
}

func receive() -> String {
    let data: NSData = "GiveMeCurrentTime".dataUsingEncoding(NSUTF8StringEncoding)!

    let bytesWritten = outputStream.write(UnsafePointer<UInt8>(data.bytes), maxLength: data.length)

    print("wrote \(bytesWritten) bytes")

    var buffer = [UInt8](count: 100, repeatedValue: 0)
    inputStream.read(&buffer,maxLength: buffer.count)

    let currentTime = String.fromCString(UnsafePointer(buffer))
    print("Data received is \(currentTime)")


    return currentTime
}

func stream(stream: NSStream, handleEvent eventCode: NSStreamEvent) {
    //print("stream event")

    if stream === inputStream {
        switch eventCode {
        case NSStreamEvent.ErrorOccurred:
            print("input: ErrorOccurred: \(stream.streamError?.description)")
        case NSStreamEvent.OpenCompleted:
            print("input: OpenCompleted")
        case NSStreamEvent.HasBytesAvailable:
            print("input: HasBytesAvailable")
            break;
        case NSStreamEvent.HasSpaceAvailable:
            break;
        default:
            print("\(eventCode)")
            break
        }
    }
    else if stream === outputStream {
        switch eventCode {
        case NSStreamEvent.ErrorOccurred:
            print("output: ErrorOccurred: \(stream.streamError?.description)")
        case NSStreamEvent.OpenCompleted:
            print("output: OpenCompleted")
        case NSStreamEvent.HasSpaceAvailable:
            print("output: HasSpaceAvailable")
            // Here you can write() to `outputStream`
            break;
        default:
            print("\(eventCode)")
            break
        }
    }
}

The connection class will be used as below with NSTimer and NSRunLoop:

func start(ipAddress ipAddress: String) {
        self.conn = Connection(serverAddress: ipAddress)
        self.conn!.connect()
        self.sendTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "getTimeAndUpdateUI", userInfo: nil, repeats: true)
        NSRunLoop.currentRunLoop().addTimer(self.sendTimer!, forMode: NSDefaultRunLoopMode)
    }
}

func getTimeAndUpdateUI() {
    let time = self.conn!.receive()
    self.timeLabel.text = "Current time is \(time)"
}

The first run is always okay - I can get the current time and display it correctly. But the subsequent runs never succeed. The output stream cannot write any byte out. And the input stream got the following error when reading:

ErrorOccurred: Optional("Error Domain=NSPOSIXErrorDomain Code=32 \"Broken pipe\"")

I've tried searching for a long time but couldn't find a solution. Any help would be much appreciated.


Solution

  • Looks like NSInputStream and NSOutputStream are not supposed to be re-used after a stream finishes. I fix the problem by changing the code to below:

    func start(ipAddress ipAddress: String) {
            self.conn = Connection(serverAddress: ipAddress)
            self.sendTimer = NSTimer.scheduledTimerWithTimeInterval(5.0, target: self, selector: "getTimeAndUpdateUI", userInfo: nil, repeats: true)
            NSRunLoop.currentRunLoop().addTimer(self.sendTimer!, forMode: NSDefaultRunLoopMode)
    }
    
    func getTimeAndUpdateUI() {
        self.conn!.connect()
        let time = self.conn!.receive()
        self.timeLabel.text = "Current time is \(time)"
        self.conn!.disconnect()
    }
    

    Every time iOS poll the server for information, it recreates an instance of NSInputStream and NSOutputStream.

    Any suggestion to improve is welcome. Thanks.