iOS how can i perform multiple NSInputStream - ios

My app uses NSInputStream like below:
inputStream.delegate = self;
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[readStream open];
and delegate:
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
It works fine, but all other requests that i do, it queued until first is finished.
I can do one per time and there is no way to do multiple concurrent requests.
There is a solution ?
Thank you
This solution not work for me :
https://stackoverflow.com/a/15346292/1376961
UPDATE:
Was my server can't handle multiple connections from the same source.

You will need to create your streams in separate threads to enable them to work simultaneously. I assume you have a method that sets up the inputStream you referred to:
- (void)openStreamInNewThread {
[NSThread detachNewThreadSelector:#selector(openStream) toTarget:self withObject:nil];
}
- (void)openStream {
NSInputStream *inputStream;
// stream setup
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
}
Note that [NSRunLoop currentRunLoop] will return the runloop of the current thread. So you have the newly created stream running in a separate thread loading data simultaneously with other streams in their own threads.

You can try to schedule each stream in its own run loop. Below is a refined method from the mock class designed to unit-test my POSInputStreamLibrary:
static const NSTimeInterval kRunLoopCycleInterval = 0.01f;
static const uint64_t kDispatchDeltaNanoSec = 250000000;
- (POSRunLoopResult)launchNSRunLoopWithStream:(NSInputStream *)stream delegate:(id<NSStreamDelegate>)streamDelegate {
stream.delegate = streamDelegate;
__block BOOL breakRunLoop = NO;
__block dispatch_semaphore_t doneSemaphore = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[stream scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
if ([stream streamStatus] == NSStreamStatusNotOpen) {
NSLog(#"%#: opening stream...", [NSThread currentThread]);
[stream open];
}
while ([runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:kRunLoopCycleInterval]] && !breakRunLoop)
{}
NSLog(#"%#: We are done!", [NSThread currentThread]);
dispatch_semaphore_signal(doneSemaphore);
});
POSRunLoopResult result = dispatch_semaphore_wait(doneSemaphore, dispatch_time(DISPATCH_TIME_NOW, kDispatchDeltaNanoSec)) == 0 ? POSRunLoopResultDone : POSRunLoopResultTimeout;
if (POSRunLoopResultTimeout == result) {
breakRunLoop = YES;
dispatch_semaphore_wait(doneSemaphore, DISPATCH_TIME_FOREVER);
}
return result;
}

Each time I create a new NSInputStream, I add it to a block object, and then store the block object in an NSMutableArray.
I posted code that streams video from one iOS to another:
https://app.box.com/s/94dcm9qjk8giuar08305qspdbe0pc784
Build this app with Xcode 11; run it on two iOS 11 devices.
Touch the Camera icon on one of the two devices to start streaming live video.
If you don't have two devices, you can run the app in a simulator; however, stream from the real device only (the camera is not available on the simulator).

Related

iOS NSStream FTP not responding after 5 minutes of inactivity

I am using a FTP library for iOS (nkreipke/FTPManager). It works great. I can download a list of directories, upload images, etc.
The problem is that if you leave the app open for like 5 minutes doing nothing and then you try to download or upload, nothing happens.
I've been debugging it and found out that the NSStreamDelegate method - (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent never gets called after that short period of time of being inactive.
Here is the code:
- (NSArray*) _contentsOfServer:(FMServer*)server {
BOOL success = YES;
action = _FMCurrentActionContentsOfServer;
fileSize = 0;
self.directoryListingData = [[NSMutableData alloc] init];
NSURL* dest = [server.destination ftpURLForPort:server.port];
And(success, (dest != nil));
Check(success);
if (![dest.absoluteString hasSuffix:#"/"]) {
//if the url does not end with an '/' the method fails.
//no problem, we can fix this.
dest = [NSURL URLWithString:[NSString stringWithFormat:#"%#/",dest.absoluteString]];
}
CFReadStreamRef readStream = CFReadStreamCreateWithFTPURL(NULL, (__bridge CFURLRef)dest);
And(success, (readStream != NULL));
if (!success) return nil;
self.serverReadStream = (__bridge_transfer NSInputStream*) readStream;
And(success, [self.serverReadStream setProperty:server.username forKey:(id)kCFStreamPropertyFTPUserName]);
And(success, [self.serverReadStream setProperty:server.password forKey:(id)kCFStreamPropertyFTPPassword]);
if (!success) return nil;
self.bufferOffset = 0;
self.bufferLimit = 0;
currentRunLoop = CFRunLoopGetCurrent();
self.serverReadStream.delegate = self;
[self.serverReadStream open];
[self.serverReadStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
CFRunLoopRun(); //<- Hangs here.
And(success, streamSuccess);
if (!success) return nil;
NSArray* directoryContents = [self _createListingArrayFromDirectoryListingData:self.directoryListingData];
self.directoryListingData = nil;
return directoryContents;
serverReadStream is a NSInputStream object.
When the action ends:
if (self.serverReadStream) {
[self.serverReadStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
CFRunLoopStop(CFRunLoopGetCurrent());
self.serverReadStream.delegate = nil;
[self.serverReadStream close];
self.serverReadStream = nil;
}
This code is located at FTPManager.m. I've been looking for answers around the internet but couldn't find any. I don't know why the NSStreamDelegate method gets called when used constantly but after some time of inactivity, it doesn't get called.
It would be nice if someone could help me.
Thanks.
I'm using the same library in my iPad application and I was stuck in the same CFRunLoopRun() call when I was trying to refresh the FTP server content when resuming my application.
I had to do two changes to the FTPManager code.
First, I've changed the order of
[self.serverReadStream open];
[self.serverReadStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
to
[self.serverReadStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.serverReadStream open];
The reason why is that the stream cannot by opened. An error event is raised during the call to open but if the stream is not schedule to the run loop, we will never receive the error event. So scheduling before opening will allow us to get the error event.
The other change which was needed in my case was to disable the persistent connection. It seems that after a suspend/resume cycle of the device, the internal socket used by the stream is getting staled and is always returning an error when trying to access it.
Adding the following line in the stream configuration has solve the issue on my side :
[self.serverReadStream setProperty: (id) kCFBooleanFalse forKey: (id) kCFStreamPropertyFTPAttemptPersistentConnection];
Hope this helps.

NSStream closing too soon

I have been doing a client-server communication for a while in iOS but I am here faced to an issue I have some troubles to understand.
I wrote two basic functions: one to send data to the server and the other to receive data from the server. Each one has a parameter called timeout which allows to make the current thread sleep and wake up every 0.25s until the timeout is reached:
-(ReturnCode) send : (NSData*)data :(int)timeOut
{
if(![self isConnectionOpened]) return KO;
float timer = 0;
while(![_outputStream hasSpaceAvailable])
{
[NSThread sleepForTimeInterval:0.25];
timer+=0.25;
if(timer == timeOut) break;
}
int ret = 0;
if([_outputStream hasSpaceAvailable]){
int lg = [data length];
ret = [_outputStream write:[data bytes] maxLength:lg];
if(ret == -1) return KO;
else return OK;
}
return TIMEOUT;
}
- (ReturnCode) receive : (NSData**)data : (int)timeOut : (int)maxLength
{
uint8_t buffer[maxLength];
int len;
NSMutableData* dataReceived = [[NSMutableData alloc] init];
if(! [self isConnectionOpened]) return KO;
float timer = 0;
while(![_inputStream hasBytesAvailable])
{
[NSThread sleepForTimeInterval:0.25];
timer+=0.25;
if(timer == timeOut) break;
}
if(![_inputStream hasBytesAvailable]) return TIMEOUT;
while ([_inputStream hasBytesAvailable]) {
len = [_inputStream read:buffer maxLength:sizeof(buffer)];
if (len > 0) {
[dataReceived appendBytes:buffer length:len];
*data = dataReceived;
return OK;
}
}
return KO;
}
With iPhone 4 + iOS6, everything is going fine. But under iOS7, for some fuzzy reasons, inputstream and outputstream are closing prematurely (NSStreamEventErrorOccurred raised). The fact is, if I put a breakpoint just before receiving data from server and make the code run, it works fine and reading/writing streams dont close wrongly.
So I think there is a synchronisation problem but I dont understand why... If anyone has ideas, pls help...
I found where my issue was coming from.
Actually, be really careful on where are scheduled inputstream and outputstream. Indeed, I was told that Apple objects had to be executed on main thread, so I scheduled them this way:
dispatch_async(dispatch_get_main_queue(), ^ {
[_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_inputStream open];
[_outputStream open];
});
But actually, it seems better to schedule them on the current loop from the current thread, and not dispatching the schedule action on main thread:
[_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[_inputStream open];
[_outputStream open];

NSOutputStream of EASession stops sending data to EAAccessory

I have a project that connects to an external accessory and communicates a small amount of data to and from an iOS app. I am able to setup the session and streams like apple does in their EADemo reference code and everything seems to work fine.
The problem I have is that after a random amount of time using the app, the output stream stops working, but the input stream still operates fine. I check to make sure hasSpaceAvailable is true before each write attempt and when I read back the number of bytes written, everything looks correct. Also, looking at the run loop doesn't indicate any differences between working and non working, and the stream status still reads as open.
The only thing that I can see that causes this is that my accessory doesn't ACK a few of the app's write attempts in a row, and then it breaks.
How can I detect I am in this state and how can I fix it?
// low level write method - write data to the accessory while there is space available and data to write
- (void)_writeData {
while (([[_session outputStream] hasSpaceAvailable]) && ([_dataToWrite length] > 0))
{
NSInteger bytesWritten = [[_session outputStream] write:[_dataToWrite bytes] maxLength:[_dataToWrite length]];
if (bytesWritten == -1)
{
NSLog(#"write error");
break;
}
else if (bytesWritten > 0)
{
[_dataToWrite replaceBytesInRange:NSMakeRange(0, bytesWritten) withBytes:NULL length:0];
}
}
}
// low level read method - read data while there is data and space available in the input buffer
- (void)_readData {
NSLog(#"reading data to buffer");
#define EAD_INPUT_BUFFER_SIZE 128
uint8_t buf[EAD_INPUT_BUFFER_SIZE];
while ([[_session inputStream] hasBytesAvailable])
{
NSInteger bytesRead = [[_session inputStream] read:buf maxLength:EAD_INPUT_BUFFER_SIZE];
if (_dataToRead == nil) {
_dataToRead = [[NSMutableData alloc] init];
}
[_dataToRead appendBytes:(void *)buf length:bytesRead];
}
[[NSNotificationCenter defaultCenter] postNotificationName:EASessionDataReceivedNotification object:self userInfo:nil];
}
// high level write data method
- (void)writeData:(NSData *)data
{
// NSLog(#"writing data to buffer");
if (_dataToWrite == nil) {
_dataToWrite = [[NSMutableData alloc] init];
}
[_dataToWrite appendData:data];
[self _writeData];
}
// high level read method
- (NSData *)readData:(NSUInteger)bytesToRead
{
NSLog(#"reading data");
NSData *data = nil;
if ([_dataToRead length] >= bytesToRead) {
NSRange range = NSMakeRange(0, bytesToRead);
data = [_dataToRead subdataWithRange:range];
[_dataToRead replaceBytesInRange:range withBytes:NULL length:0];
}
return data;
}
- (BOOL)openSession
{
NSLog(#"openSession");
[_accessory setDelegate:self];
if(_session){
[self closeSession];
}
_session = [[EASession alloc] initWithAccessory:_accessory forProtocol:_protocolString];
if (_session)
{
_runLoop = [NSRunLoop currentRunLoop];
[[_session inputStream] setDelegate:self];
[[_session inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[_session inputStream] open];
[[_session outputStream] setDelegate:self];
[[_session outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[[_session outputStream] open];
NSLog(#"creating session succeeded!");
}
else
{
NSLog(#"creating session failed!");
}
return (_session != nil);
}
I think I may be experiencing the same problem. I have a bluetooth connected accessory, and when I am testing range I often end up in a situation with exactly the same symptoms that you describe in your question.
But how do you detect that the problem is caused by the accessory failing to ACK data?
I am guessing in my situation the Accessory is sending ACK's but the because I am on the edge of bluetooth range my phone never receives the ACK.
Right now my best bet is to try and detect the situation in the APP. In my situation I can do this because the accessory will resend the same package if it does not receive any data from the phone. So if I see the same data transmitted from the accessory a number of times I will drop the connection and ask the user to move closer to the accessory, and reconnect.

NSInputStream stops running, sometimes throws EXC_BAD_ACCESS

(UPDATED) this is the problem in a nutshell: in iOS I want to read a large file, do some processing on it (in this particular case encode as Base64 string() and save to a temp file on the device. I set up an NSInputStream to read from a file, then in
(void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
I'm doing most of the work. For some reason, sometimes I can see the NSInputStream just stops working. I know because I have a line
NSLog(#"stream %# got event %x", stream, (unsigned)eventCode);
in the beginning of (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode and sometimes I would just see the output
stream <__NSCFInputStream: 0x1f020b00> got event 2
(which corresponds to the event NSStreamEventHasBytesAvailable) and then nothing afterwards. Not event 10, which corresponds to NSStreamEventEndEncountered, not an error event, nothing! And also sometimes I even get a EXC_BAD_ACCESS exception which I have no idea at the moment how to debug. Any help would be appreciated.
Here is the implementation. Everything starts when I hit a "submit" button, which triggers:
- (IBAction)submit:(id)sender {
[p_spinner startAnimating];
[self performSelector: #selector(sendData)
withObject: nil
afterDelay: 0];
}
Here is sendData:
-(void)sendData{
...
_tempFilePath = ... ;
[[NSFileManager defaultManager] createFileAtPath:_tempFilePath contents:nil attributes:nil];
[self setUpStreamsForInputFile: [self.p_mediaURL path] outputFile:_tempFilePath];
[p_spinner stopAnimating];
//Pop back to previous VC
[self.navigationController popViewControllerAnimated:NO] ;
}
Here is setUpStreamsForInputFile called above:
- (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath {
self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
[p_iStream setDelegate:self];
[p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[p_iStream open];
}
Finally, this is where most logic occurs:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
NSLog(#"stream %# got event %x", stream, (unsigned)eventCode);
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if (stream == self.p_iStream){
if(!_tempMutableData) {
_tempMutableData = [NSMutableData data];
}
if ([_streamdata length]==0){ //we want to write to the buffer only when it has been emptied by the output stream
unsigned int buffer_len = 24000;//read in chunks of 24000
uint8_t buf[buffer_len];
unsigned int len = 0;
len = [p_iStream read:buf maxLength:buffer_len];
if(len) {
[_tempMutableData appendBytes:(const void *)buf length:len];
NSString* base64encData = [Base64 encodeBase64WithData:_tempMutableData];
_streamdata = [base64encData dataUsingEncoding:NSUTF8StringEncoding]; //encode the data as Base64 string
[_tempFileHandle writeData:_streamdata];//write the data
[_tempFileHandle seekToEndOfFile];// and move to the end
_tempMutableData = [NSMutableData data]; //reset mutable data buffer
_streamdata = [[NSData alloc] init]; //release the data buffer
}
}
}
break;
case NSStreamEventEndEncountered:
{
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
stream = nil;
//do some more stuff here...
...
break;
}
case NSStreamEventHasSpaceAvailable:
case NSStreamEventOpenCompleted:
case NSStreamEventNone:
{
...
}
}
case NSStreamEventErrorOccurred:{
...
}
}
}
Note: when I posted this first, I was under a wrong impression the issue had something to do with using GCD. As per Rob's answer below I removed the GCD code and the issue persists.
First: in your original code, you were not using a background thread, but the main thread (dispatch_async but on the main queue).
When you schedule NSInputStream to run on the default runloop (so, the runloop of the main thread), the events are received when the main thread is in the default mode (NSDefaultRunLoopMode).
But: if you check, the default runloop changes mode in some situations (for example, during an UIScrollView scroll and some other UI updates). When the main runloop is in a mode different than the NSDefaultRunLoopMode, your events are not received.
Your old code, with the dispatch_async, was almost good (but move the UI Updates on the main thread). You have to add only few changes:
dispatch in the background, with something like this:
:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue, ^{
// your background code
//end of your code
[[NSRunLoop currentRunLoop] run]; // start a run loop, look at the next point
});
start a run loop on that thread. This must be done at the end (last line) of the dispatch async call, with this code
:
[[NSRunLoop currentRunLoop] run]; // note: this method never returns, so it must be THE LAST LINE of your dispatch
Try and let me know
EDIT - added example code:
To be more clear, I copy-paste your original code updated:
- (void)setUpStreamsForInputFile:(NSString *)inpath outputFile:(NSString *)outpath {
self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
[p_iStream setDelegate:self];
// here: change the queue type and use a background queue (you can change priority)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_async(queue, ^ {
[p_iStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[p_iStream open];
// here: start the loop
[[NSRunLoop currentRunLoop] run];
// note: all code below this line won't be executed, because the above method NEVER returns.
});
}
After making this modification, your:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {}
method, will be called on the same thread where you started the run loop, a background thread: if you need to update the UI, it's important that you dispatch again to the main thread.
Extra informations:
In my code I use dispatch_async on a random background queue (which dispatch your code on one of the available background threads, or start a new one if needed, all "automagically"). If you prefer, you can start your own thread instead of using a dispatch async.
Moreover, I don't check if a runloop is already running before sending the "run" message (but you can check it using the currentMode method, look NSRunLoop reference for more informations). It shouldn't be necessary because each thread has only one associated NSRunLoop instance, so sending another run (if already running) does nothing bad :-)
You can even avoid the direct use of runLoops and switch to a complete GCD approach, using dispatch_source, but I've never used it directly so I can't give you a "good example code" now
NSStream requires a run loop. GCD doesn't provide one. But you don't need GCD here. NSStream is already asynchronous. Just use it on the main thread; that's what it's designed for.
You're also doing several UI interactions while on a background thread. You can't do that. All UI interactions have to occur on the main thread (which is easy if you remove the GCD code).
Where GCD can be useful is if reading and processing the data is time consuming, you can hand that operation to GCD during NSStreamEventHasBytesAvailable.

VoIP socket on iOS - no notifications received

I have a VoIP app that uses a TCP service to wake it up on incoming calls.
The TCP socket is created with this code fragment:
CFReadStreamRef read = NULL;
CFWriteStreamRef write = NULL;
...
CFStreamCreatePairWithSocketToHost(NULL,(__bridge CFStringRef)shost, port, &read, &write);
self.read = (__bridge NSInputStream*)read;
self.write = (__bridge NSOutputStream*)write;
if (![self.read setProperty:NSStreamNetworkServiceTypeVoIP
forKey:NSStreamNetworkServiceType]){
[Log log:#"Could not set VoIP mode to read stream"];
}
if (![self.write setProperty:NSStreamNetworkServiceTypeVoIP
forKey:NSStreamNetworkServiceType]){
[Log log:#"Could not set VoIP mode to write stream"];
}
self.read.delegate = self;
self.write.delegate = self;
CFRelease(read);
CFRelease(write);
[self.read scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.write scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[self.read open];
[self.write open];
I've also set the following:
VoIP & Audio in the info plist
Keep alive timer using [UIApplication sharedApplication] setKeepAliveTimeout
UIRequiresPersistentWiFi = YES in the info plist (quite sure it's not required, but...)
This works well while the app is in the foreground, and even works well in the background for a few minutes, but after a few minutes - the app does not receive any new TCP messages.
It doesn't work on wifi or 3G, same result for both.
I also tried setting the property just to the read stream (though the read and write point to the same socket).
Whenever I receive data on the TCP or send data I also start a short background task.
BTW - everything takes place on the main thread.
I've checked if the app crashes - it doesn't.
The same behavior can be observed while debugging on the device - after a while - nothing is received (no crashes, warnings, anything).
What am I doing wrong?
Looks like your code should work. But there may be two technical problems I can think of:
If you try this from LAN connection, while app in background the LAN router can close passive TCP connection because, in this case, SIP stack(guess you use SIP protocol) can't send data keep alive every 15 to 30 secs like it would in foreground.
Less likely, suppose you know what you doing, but since registration keep alive can be triggered only once in 10 minutes while in background, make sure that SIP server allows such a long expire period and you define it right in registration message.
Try the following code.Make sure you have only one voip socket in your app.
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"1.2.3.4",9999, &readStream, &writeStream);
CFReadStreamSetProperty(readStream,kCFStreamNetworkServiceType,kCFStreamNetworkServiceTypeVoIP);
CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
inputStream = (NSInputStream *)readStream;
[inputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
outputStream = (NSOutputStream *)writeStream;
[outputStream setDelegate:self];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
In ViewController.h file add
#property (nonatomic, strong) NSInputStream *inputStream;
#property (nonatomic, strong) NSOutputStream *outputStream;
#property (nonatomic) BOOL sentPing;
In ViewController.m file add after #implementation ViewController
const uint8_t pingString[] = "ping\n";
const uint8_t pongString[] = "pong\n";
Add following code in viewDidLoad
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(#"192.168.0.104"), 10000, &readStream, &writeStream);
//in above line user your MAC IP instead of 192.168.0.104
self.sentPing = NO;
//self.communicationLog = [[NSMutableString alloc] init];
self.inputStream = (__bridge_transfer NSInputStream *)readStream;
self.outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[self.inputStream setProperty:NSStreamNetworkServiceTypeVoIP forKey:NSStreamNetworkServiceType];
[self.inputStream setDelegate:self];
[self.outputStream setDelegate:self];
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[self.inputStream open];
[self.outputStream open];
//After every 10 mins this block will be execute to ping server, so connection will be live for more 10 mins
[[UIApplication sharedApplication] setKeepAliveTimeout:600 handler:^{
if (self.outputStream)
{
[self.outputStream write:pingString maxLength:strlen((char*)pingString)];
//[self addEvent:#"Ping sent"];
}
}];
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventNone:
// do nothing.
break;
case NSStreamEventEndEncountered:
//[self addEvent:#"Connection Closed"];
break;
case NSStreamEventErrorOccurred:
//[self addEvent:[NSString stringWithFormat:#"Had error: %#", aStream.streamError]];
break;
case NSStreamEventHasBytesAvailable:
if (aStream == self.inputStream)
{
uint8_t buffer[1024];
NSInteger bytesRead = [self.inputStream read:buffer maxLength:1024];
NSString *stringRead = [[NSString alloc] initWithBytes:buffer length:bytesRead encoding:NSUTF8StringEncoding];
stringRead = [stringRead stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
//[self addEvent:[NSString stringWithFormat:#"Received: %#", stringRead]];
//if server response is 'call' then a notification will go to notification center and it will be fired
//immediately and it will popup if app is in background.
if ([stringRead isEqualToString:#"call"])
{
UILocalNotification *notification = [[UILocalNotification alloc] init];
notification.alertBody = #"New VOIP call";
notification.alertAction = #"Answer";
//[self addEvent:#"Notification sent"];
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
}
//else if ([stringRead isEqualToString:#"ping"])
//{
//if server response is 'ping' then a sting 'pong' will go to server immediately
//[self.outputStream write:pongString maxLength:strlen((char*)pongString)];
//}
}
break;
case NSStreamEventHasSpaceAvailable:
if (aStream == self.outputStream && !self.sentPing)
{
self.sentPing = YES;
if (aStream == self.outputStream)
{
[self.outputStream write:pingString maxLength:strlen((char*)pingString)];
//[self addEvent:#"Ping sent"];
}
}
break;
case NSStreamEventOpenCompleted:
if (aStream == self.inputStream)
{
//[self addEvent:#"Connection Opened"];
}
break;
default:
break;
}
}
Build your app and run
Open terminal in your MAC PC and write nc -l 10000 and press enter
$ nc -l 10000
Then write call and press enter

Resources