CFStreamCreatePairWithSocketToHost - How to handle the network disconnect? - ios

We have created socket for streaming using the API "CFStreamCreatePairWithSocketToHost".
func openStream() {
if let aUrl = url {
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(
kCFAllocatorDefault,
aUrl as CFString,
port!,
&readStream,
&writeStream)
iStream = readStream!.takeRetainedValue()
oStream = writeStream!.takeRetainedValue()
if ssl == true {
let dict = [
kCFStreamSSLValidatesCertificateChain: kCFBooleanFalse, // allow self-signed certificate
kCFStreamSSLLevel: kCFStreamSSLValidatesCertificateChain // don't understand, why there isn't a constant for version 1.2
] as CFDictionary
let sslSetRead = CFReadStreamSetProperty(iStream, CFStreamPropertyKey(kCFStreamPropertySSLSettings), dict)
let sslSetWrite = CFWriteStreamSetProperty(oStream, CFStreamPropertyKey(kCFStreamPropertySSLSettings), dict)
if sslSetRead == false || sslSetWrite == false {
//print("SSL Configuration Failed")
}
}
commonStr = ""
open(iStream)
open(oStream)
print("Open Stream")
}
}
Used the below delegate for getting events.
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
// print("Stream : \(aStream.debugDescription) Event : \(eventCode.rawValue)")
switch eventCode {
case .endEncountered, .errorOccurred:
streamError()
break
case .hasBytesAvailable:
if let iStream = iStream {
if aStream == iStream {
//read data
let bufferSize = 1024
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
var len: Int
while iStream.hasBytesAvailable {
len = iStream.read(buffer, maxLength: bufferSize)
if len > 0 {
let output = Data.init(bytes: buffer, count: len)
bufferTokenize(output: output)
}else {
break
}
}
buffer.deallocate()
}
}
break
case .hasSpaceAvailable:
if aStream == oStream! {
streamReady()
}
break
case .openCompleted:
if aStream == oStream! {
if let del = delegate {
del.connectionCompleted()
}
}
break
default:
break
}
}
Other Supporting functions are
func streamError() {
stopStream()
NSObject.cancelPreviousPerformRequests(withTarget:self)
perform(#selector(callReconnect), with: nil, afterDelay: 2)
}
func open(_ stream: Stream?) {
if let aStream = stream {
aStream.delegate = self
aStream.schedule(in: RunLoop.main, forMode: .common)
aStream.open()
}
}
func stopStream() {
print("Stop Stream")
close(iStream)
close(oStream)
}
func close(_ stream: Stream?) {
if nil != stream {
stream?.close()
stream?.remove(from: RunLoop.main, forMode: .common)
}
}
func resetStream() {
stopStream()
openStream()
}
It is working fine for below steps
Create connection - Open stream
get "openCompleted" event - then Create request
get "hasSpaceAvailable" event - then Send Request
get "hasBytesAvailable" event - then Handle Data
Disconnect connection from server for testing purpose
get "endEncountered" event - then Close the connection and Recreate the connection from step 1
But the below scenario not working properly
Follow the steps 1 - 4 then
Disconnect Internet connection for testing purpose - the event "hasBytesAvailable" stopped
Again reconnect the Internet connection within 30 seconds - now we get "hasBytesAvailable" event
But the reconnect time extent to 5 minute - We don't have any event, and don't get "endEncountered" or "errorOccurred" also. Then how to move to next stop.
Please Help, Thanks
Regards,
Elamvazhuthi K

Related

How to convert UnsafeMutablePointer<UInt8> to UIImage in Swift

I have been trying to send image data from android and receive at iOS using Socket,But the problem is that I am not able to show Image from data which is received from stream.
Here is the complete code:-
Setting Up the network communication
var readStream: Unmanaged<CFReadStream>?
var writeStream: Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
ip as CFString,
3003,
&readStream,
&writeStream)
inputStream = readStream!.takeRetainedValue()
outputStream = writeStream!.takeRetainedValue()
inputStream.delegate = self
// outputStream.delegate = self
inputStream.schedule(in: .current, forMode: RunLoop.Mode.common)
outputStream.schedule(in: .current, forMode: RunLoop.Mode.common)
inputStream.open()
outputStream.open()
Receive data through the delegates as shown below:-
extension ChatRoom: StreamDelegate {
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
switch eventCode {
case Stream.Event.hasBytesAvailable:
print("new message received")
readAvailableBytes(stream: aStream as! InputStream)
case Stream.Event.endEncountered:
print("end received")
stopStreamSession()
case Stream.Event.errorOccurred:
print("error occurred")
case Stream.Event.hasSpaceAvailable:
print("has space available")
case Stream.Event.openCompleted:
print("Stream opened")
default:
print("some other event...")
break
}
}
private func readAvailableBytes(stream: InputStream) {
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: maxReadLength)
while stream.hasBytesAvailable {
let numberOfBytesRead = inputStream.read(buffer, maxLength: maxReadLength)
if numberOfBytesRead < 0 {
if let _ = stream.streamError {
break
}
}
NSLog("number of bytes read = %i", numberOfBytesRead)
if numberOfBytesRead > 0 {
dataOfImage.append(buffer, count: numberOfBytesRead)
}
}
}
}
I have been trying to show image from data received using UIImage(data:dataOfImage), But it is showing nil , when I inspect the dataOfImage it consists of all the data received from stream.
Can anyone suggest a way to get the image out of UnsafeMutablePointer<UInt8> Data.
Also if there is any better approach for data transfer through sockets, Please do Suggest..
Rather than allocate memory declare a buffer variable with the capacity of maxReadLength.
And stream(_:handle:) is called multiple times. You have to check the result of the read function
private var dataOfImage = Data()
private func readAvailableBytes(stream: InputStream) {
var buffer = [UInt8](repeating: 0, count: maxReadLength)
let bytesRead = inputStream.read(&buffer, maxLength: maxReadLength)
switch bytesRead {
case 0: // success, end of file
case -1: // error
default: // append data
dataOfImage.append(contentsOf: buffer)
}
NSLog("number of bytes read = %i", bytesRead)
}

iOS / Swift: Set timeout for opening Streams

I am working on an iOS app (Swift 4) that connects to a server via
Stream.getStreamsToHost(withName: self.host!, port: self.port!, inputStream: &self.inputStream, outputStream: &self.outputStream)
if let instream = self.inputStream, let outstream = self.outputStream {
instream.delegate = self
outstream.delegate = self
instream.schedule(in: .current, forMode: .commonModes)
outstream.schedule(in: .current, forMode: .commonModes)
instream.open()
outstream.open()
}
In case I have a bad internet connection / no connection / server is not reachable for some reason, I want to show the user an error message. This already works but it takes almost a minute until the error "The operation couldn’t be completed. Operation timed out" happens.
Can I somehow reduce the timeout in my app or is this a system-wide timeout that cannot be changed?
The constants for Stream.setProperty do not seam to contain any timeout-related stuff: https://developer.apple.com/documentation/foundation/stream#1666775
A workaround I can imagine is to manually schedule a function after X seconds and if the connection is not established then, to cancel it. Is this the best-practice to achieve a custom timeout?
After reading a few threads about this problem I decided to use a Timer that checks for a successful connection after n seconds:
Stream.getStreamsToHost(withName: self.host!, port: self.port!, inputStream: &self.inputStream, outputStream: &self.outputStream)
if let instream = self.inputStream, let outstream = self.outputStream {
instream.delegate = self
outstream.delegate = self
instream.schedule(in: .current, forMode: .commonModes)
outstream.schedule(in: .current, forMode: .commonModes)
instream.open()
outstream.open()
self.connectionTimer = Timer.scheduledTimer(withTimeInterval: self.CONNECTION_TIMEOUT, repeats: false) { _ in
if !self.instreamReady || !self.outstreamReady {
self.errorHandler(NetworkingError.Timeout("Timeout \(self.CONNECTION_TIMEOUT)s exeeded"))
self.disconnect()
}
self.connectionTimer?.invalidate()
self.connectionTimer = nil
}
}
Works :)
var continue = true
var countin = 0
var inp :InputStream?
var out :OutputStream?
let testData = mesaj
Stream.getStreamsToHost(withName: addr, port: port, inputStream: &inp, outputStream: &out)
let stream = out!
print( stream.streamStatus.rawValue)
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
guard continue == true else{
timer.invalidate()
return
}
if (counting == 0){
DispatchQueue.global(qos: .utility).async() {
stream.open()
var position = 0
print( stream.streamStatus.rawValue)
while position < testData.count {
let length = min(4096, testData.count - position)
let amount = testData[position...].withUnsafeBytes {
stream.write($0.bindMemory(to: UInt8.self).baseAddress!, maxLength: length)
}
if amount <= 0 {
// Error or EOF
break
}
position += amount
}
stream.close()
DispatchQueue.main.sync {
self.durumTF.text = "Send to PC"
self.durumTF.textColor = .blue
}
continue=false
}
}
if (counting == 3){
print(stream.streamStatus.rawValue)
if stream.streamStatus.rawValue == 7 {
stream.close()
self.durumTF.text = "Couldn't connect to PC"
self.durumTF.textColor = .red
}
if stream.streamStatus.rawValue == 1 {
stream.close()
self.durumTF.text = "Run server on pc"
self.durumTF.textColor = .red
}
continue = false
}
counting += 1
}

StreamDelegate stops to receive events after a amount of readings in swift

I have an iOS chat application that receives messages over a socket connection.
When user open the app after a long time and there are more than 50 unread messages, the server send over socket a message telling the number of unread messages, at this point the application show an alert with a progress bar and then the server send each one message.
So, the application get each one message on the StreamDelegate method stream(_ stream: Stream, handle eventCode: Stream.Event) and update the progress bar until the end of messages.
The problem is, when I have a large amount of unread messages (about 300+) at some point the StreamDelegate stops to receive the events with the messages, and none error messages is displayed.
I call the connect method on a global queue:
DispatchQueue.global().async {
self.connect(host, port: port)
}
This is my socket connect code:
fileprivate func connect(_ host: String, port: Int) {
postStatus(.connecting)
self.host = NSString(string: host)
self.port = UInt32(port)
self.log("connect to \(host):\(port)")
var readStream : Unmanaged<CFReadStream>?
var writeStream : Unmanaged<CFWriteStream>?
CFStreamCreatePairWithSocketToHost(nil, self.host, self.port, &readStream, &writeStream)
self.inOk = false
self.outOk = false
self.inputStream = readStream!.takeRetainedValue()
self.outputStream = writeStream!.takeRetainedValue()
self.inputStream.delegate = self
self.outputStream.delegate = self
let mainThread = Thread.isMainThread;
let loop = mainThread ? RunLoop.main : RunLoop.current
self.inputStream.schedule(in: loop, forMode: RunLoopMode.defaultRunLoopMode)
self.outputStream.schedule(in: loop, forMode: RunLoopMode.defaultRunLoopMode)
self.inputStream.open()
self.outputStream.open()
self.timer = Timer.scheduledTimer(timeInterval: 5, target: self, selector: #selector(connectionTimeout), userInfo: nil, repeats: false)
if(!mainThread) {
loop.run()
}
}
In the StreamDelegate method stream(_ stream: Stream, handle eventCode: Stream.Event) I get the message event and process it on the method read(String)
case Stream.Event.hasBytesAvailable:
if let timer = timer {
timer.invalidate()
self.timer = nil
}
let json = ChatLibSwift.readMessage(self.inputStream)
do {
if StringUtils.isNotEmpty(json) {
try self.read(json)
}
} catch let ex as NSError {
LogUtils.log("ERROR: \(ex.description)")
}
break
case Stream.Event.hasSpaceAvailable:
break
The method that read each one message:
static func readMessage(_ inputStream: InputStream) -> String {
do {
var lenBytes = [UInt8](repeating: 0, count: 4)
inputStream.read(&lenBytes, maxLength: 4)
// header
let i32: Int = Int(UInt32.init(lenBytes[3]) | UInt32.init(lenBytes[2]) << 8 | UInt32.init(lenBytes[1]) << 16 | UInt32.init(lenBytes[0]) << 24 )
var msg = [UInt8](repeating: 0, count: (MemoryLayout<UInt8>.size * Int(i32)))
let bytesRead = inputStream.read(&msg, maxLength: Int(i32))
if bytesRead == -1 {
print("<< ChatLib ERROR -1")
return ""
}
let s = NSString(bytes: msg, length: bytesRead, encoding: String.Encoding.utf8.rawValue) as String?
if let s = s {
if bytesRead == Int(i32) {
return s
}
else {
print("Error: readMessage \(s)")
}
return s
}
return ""
} catch {
return ""
}
}
Anyone has idea how to solve it?
The main idea is to force schedule the reading of the stream after a successful read operation:
let _preallocatedBufferSize = 64 * 1024
var _preallocatedBuffer = [UInt8](repeating: 0, count: MemoryLayout<UInt8>.size * Int(_preallocatedBufferSize))
var message : ....
func readMessage(_ inputStream: InputStream) {
if !inputStream.hasBytesAvailable || message.isCompleted {
return
}
var theBuffer : UnsafeMutablePointer<UInt8>?
var theLength : Int = 0
// try to get buffer from the stream otherwise use the preallocated buffer
if !inputStream.getBuffer(&theBuffer, length:&theLength) || nil == theBuffer
{
memset(&_preallocatedBuffer, 0, _preallocatedBufferSize)
let theReadCount = inputStream.read(&_preallocatedBuffer, maxLength:_preallocatedBufferSize)
if theReadCount > 0 {
theBuffer = _preallocatedBuffer;
theLength = theReadCount;
} else {
theBuffer = nil;
theLength = 0;
}
}
if nil != theBuffer && theLength > 0 {
_message.appendData(theBuffer, length:theLength)
self.perform(#selector(readMessage), with:inputStream, afterDelay:0.0, inModes:[RunLoopMode.defaultRunLoopMode])
}
}

NSOutputStream blocking, HasBytesAvailable event was not triggered

I am trying to create custom framework that will deal with ExternalAccessory.framework to do read/write operation with the connected accessory.
I can able to create a session and open the device to do read/write operations.
Problem i am facing was, when i try to write data using NSOutputStream.write from the application, data successfully received by the accessory but after that UI was not responding and data returned byte accessory was not received by the app(HasBytesAvailable was not called)
Here my UIViewController.swift
let sessionHandler = SessionHandler().sharedController()
in ViewDidLoad()
let runLoop = NSRunLoop.currentRunLoop()
sessionHandler.openDeviceWithProtocolString(runLoop)
#IBAction calling sessionHandler._WriteData()
This SessionHandler class was in inside my custom SDK (custom framework)
Here my SessionHandler Class
import UIKit
import ExternalAccessory
public class SessionHandler: NSObject, NSStreamDelegate {
var readData: NSMutableData?
var writeData: NSMutableData?
var _session: EASession?
public func sharedController() -> SessionHandler{
var sessionController: SessionHandler?
if sessionController == nil {
sessionController = SessionHandler()
}
return sessionController!
}
func getConnectedAccessoris() -> Array<EAAccessory>{
let accessories : Array<EAAccessory> = EAAccessoryManager.sharedAccessoryManager().connectedAccessories
return accessories
}
public func openDeviceWithProtocolString(_runLoop: NSRunLoop){
print("Inside openDeviceWithProtocolString")
let _accessories = getConnectedAccessoris()
var _accessory: EAAccessory?
for acsy in _accessories {
if acsy.protocolStrings.contains("my.protocol.string") {
_accessory = acsy
}
}
if _accessory != nil {
_session = EASession(accessory: _accessory!, forProtocol: "my.protocol.string")
print("EASession create :: \(_session)")
if _session != nil {
_session?.inputStream?.delegate = self
_session?.inputStream?.scheduleInRunLoop(_runLoop, forMode: NSDefaultRunLoopMode)
_session?.inputStream?.open()
print("Input stream Opened")
_session?.outputStream?.delegate = self
_session?.outputStream?.scheduleInRunLoop(_runLoop, forMode: NSDefaultRunLoopMode)
_session?.outputStream?.open()
print("Output Stream Opened")
}else {
print("SessionHandler : session nil")
}
}
}
public func _readData() {
print("Trying to read data")
let INPUT_BUFFER_SIZE = 65536
let buf = UnsafeMutablePointer<UInt8>.alloc(INPUT_BUFFER_SIZE)
while ((_session?.inputStream?.hasBytesAvailable) != nil) {
let bytesRead = _session?.inputStream?.read(buf, maxLength: INPUT_BUFFER_SIZE)
if readData == nil {
readData = NSMutableData()
}
readData?.appendBytes(buf, length: bytesRead!)
}
if readData != nil {
let data: NSData = readData!
let count = data.length / sizeof(UInt8)
// create an array of Uint8
var array = [UInt8](count: count, repeatedValue: 0)
// copy bytes into array
data.getBytes(&array, length:count * sizeof(UInt8))
print("Data Received :: \(array)")
validateData(array)
}
}
public func _writeData() {
while _session?.outputStream?.hasSpaceAvailable != nil && writeData?.length > 0 {
print("Writting bytes :: \(writeData?.bytes)")
let bytesWritten = _session?.outputStream?.write(UnsafePointer<UInt8>((writeData?.bytes)!), maxLength: (writeData?.length)!)
print("written bytes : \(bytesWritten)")
if bytesWritten == -1 {
//Write error
print("written error")
break
}else if bytesWritten > 0 {
print("Written success")
validateData(writeData)
writeData?.replaceBytesInRange(NSMakeRange(0, bytesWritten!), withBytes: nil, length: 0)
}
}
}
func stream(aStream: NSStream, handleEvent eventCode: NSStreamEvent) {
switch (eventCode) {
case NSStreamEvent.None:
print("NSStream None")
break
case NSStreamEvent.OpenCompleted:
print("Open Completed")
break
case NSStreamEvent.HasBytesAvailable:
print("Has Bytes Available")
_readData()
break
case NSStreamEvent.HasSpaceAvailable:
print("Hase space Available")
_writeData()
break
case NSStreamEvent.ErrorOccurred:
print("Error occurred")
break
case NSStreamEvent.EndEncountered:
print("End Encountered")
break
default:
print("No stream event")
break
}
}
}
Thanks in Advance..
sessionHandler.openDeviceWithProtocolString(runLoop)
Isn't it supposed to be called on another thread?
I'm sure this is too late, but while ((_session?.inputStream?.hasBytesAvailable) != nil) looks like an infinite loop to me, because true/false will always be != nil.

swift OutputStream could not find member "write"

I'm trying for a while to send data to an OutputStream to communicate with another device. (For this i use the External Accessory Framework) But the function does not work. I have the error Could not find member "write". I hope somebody can help me to find the solution. thank you.
func _writeData() {
while (_session.outputStream.hasSpaceAvailable && _write.length > 0) {
var bytesWritten: Int = _session?.outputStream.write(_write.bytes, _write.length);
if(bytesWritten == -1){
println("write error");
break;
}
else if (bytesWritten > 0){
_write.replaceBytesInRange(NSMakeRange(0, bytesWritten), withBytes: nil, length: 0);
}
}
}
//high level write data method
func writeData(data: NSData) {
if(_write == nil) {
_write = NSMutableData.alloc();
}
_write.appendData(data);
self._writeData();
}
The Error --------> var bytesWritten: Int = _session?.outputStream.write(_write.bytes, _write.length);
There are the function where i open and close the session
//open a session with the accessory and set up the input and output stream on the default run loop
func openSession(accessory: EAAccessory, withProtocolString protocolString: String) -> ObjCBool{
_session = EASession(accessory: accessory, forProtocol: protocolString);
if(_session != nil) {
_session.inputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode);
_session.inputStream.open()
_session.outputStream.scheduleInRunLoop(NSRunLoop.currentRunLoop(), forMode: NSDefaultRunLoopMode);
_session.outputStream.open();
} else {
println("Creating session failed");
}
return (_session != nil);
}
//close the session with the accessory
func closeSession() {
_session.inputStream.close();
_session.inputStream.removeFromRunLoop(NSRunLoop(), forMode: NSDefaultRunLoopMode);
_session.outputStream.close();
_session.outputStream.removeFromRunLoop(NSRunLoop(), forMode: NSDefaultRunLoopMode);
}
after testing many things, I have been able to write to the socket, here is my code:
var inputStream: NSInputStream?
var outputStream: NSOutputStream?
NSStream.getStreamsToHostWithName("localhost", port: 8443, inputStream: &inputStream, outputStream: &outputStream)
outputStream?.open()
inputStream?.open()
//while(true){
//get input
//}
let data: NSData = "this is a test string".dataUsingEncoding(NSUTF8StringEncoding)!
outputStream?.write(UnsafePointer<UInt8>(data.bytes), maxLength: data.length)
you can test it using net cat
nc -l 8443

Resources