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])
}
}
Related
I have a working connection between 2 IOS-Devices. Sending a live-stream from one camera to the other device works. But now i want to send mic-audio and this does not work. I get no error, but i just receive "click"-noices.
I also can see, that bytes are transmitted, but i do not know, where the failure is.
Below you find the sending and the receiving functions. I also insert the streaming-function, which works fine for transmitting video.
sender:
func recorder() {
let settings : Dictionary = ["AVSampleRateKey" : 44100.0,
"AVNumberOfChannelsKey" : 1,
"AVFormatIDKey" : 1819304813,
"AVLinearPCMIsNonInterleaved" : 0,
"AVLinearPCMIsBigEndianKey" : 0,
"AVLinearPCMBitDepthKey" : 16,
"AVLinearPCMIsFloatKey" : 0]
audioFormat = AVAudioFormat.init(settings: settings)
audioEngine = AVAudioEngine.init()
audioEngine?.inputNode.installTap(onBus: 0, bufferSize: 4410, format: audioEngine?.inputNode.outputFormat(forBus: 0), block: {buffer, when in
let audioBuffer = buffer.audioBufferList.pointee.mBuffers
let data : Data = Data.init(bytes: audioBuffer.mData!, count: Int(audioBuffer.mDataByteSize))
let arraySize = Int(buffer.frameLength)
let samples = Array(UnsafeBufferPointer(start: buffer.floatChannelData![0], count:arraySize))
self.streamData(data: data, len: 4410)
})
// Start audio engine
self.audioEngine?.prepare()
do {
try self.audioEngine?.start()
}
catch {
NSLog("cannot start audio engine")
}
if(self.audioEngine?.isRunning == true){
NSLog("Audioengine is running")
}
}
sender streamData (working fine for e.g. video)
func streamData(data : Data, len : Int)
{
var baseCaseCondition : Bool = false
var _len : Int = len
var _byteIndex : Int = 0
func recursiveBlock(block: #escaping (()->Void)->Void) -> ()->Void {
return { block(recursiveBlock(block: block)) }
}
let aRecursiveBlock :()->Void = recursiveBlock {recurse in
baseCaseCondition = (data.count > 0 && _byteIndex < data.count) ? true : false
if ((baseCaseCondition)) {
_len = (data.count - _byteIndex) == 0 ? 1 : (data.count - _byteIndex) < len ? (data.count - _byteIndex) : len
NSLog("START | byteIndex: %lu/%lu writing len: %lu", _byteIndex, data.count, _len)
var bytes = [UInt8](repeating:0, count:_len)
data.copyBytes(to: &bytes, from: _byteIndex ..< _byteIndex+_len )
_byteIndex += (self.outputStream?.write(&bytes, maxLength: _len))!
NSLog("END | byteIndex: %lu/%lu wrote len: %lu", _byteIndex, data.count, _len)
recurse()
}
}
if (self.outputStream!.hasSpaceAvailable){
aRecursiveBlock();
}
}
receiver:
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
...
case Stream.Event.hasBytesAvailable:
var thePCMBuffer : AVAudioPCMBuffer = AVAudioPCMBuffer.init(pcmFormat: (self.audioEngine?.inputNode.outputFormat(forBus: 0))!, frameCapacity: AVAudioFrameCount(mlen))!
thePCMBuffer.frameLength = thePCMBuffer.frameCapacity
let channels = UnsafeBufferPointer(start: thePCMBuffer.floatChannelData, count: Int(thePCMBuffer.format.channelCount))
_ = mdata?.copyBytes(to: UnsafeMutableBufferPointer(start: channels[0], count: Int(thePCMBuffer.frameLength)))
if((self.player?.isPlaying) != nil){
DispatchQueue.global(qos: .background).async {
// Background Thread
DispatchQueue.main.async {
// Run UI Updates
self.player?.scheduleBuffer(thePCMBuffer, completionHandler: {
NSLog("Scheduled buffer")
NSLog("\(self.player!.isPlaying)")
let arraySize = Int(thePCMBuffer.frameLength)
let samples = Array(UnsafeBufferPointer(start: thePCMBuffer.floatChannelData![0], count:arraySize))
for sample in samples{
NSLog("\(sample)")
}
})
}
}
}
}
mdata = Data.init()
mlen = DATA_LENGTH
}
break;
...
}
Found the solution i guess. I tried with one simulator and one real device. Now i read, that there es a problem because of different sample-rates. Running on 2 devices (or 2 simulators) just works fine.
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
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)
}
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
}
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