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
Related
I've got a Java server (and it's able to correctly read a request from my iOS client -- it even generates a response and appears to send it correctly, though I got First message response from server every time but not getting other messages after receiving first message):
sequence of communication
Step 1-> client send login message to server
Step 2-> server validate the user and sends login info to the clients
Step 3-> Message Packet1
Message Packet2
Message Packet3
Message Packet4
step-4-> I have checked server log and it says server has send 4 string
messages
Step-5 -> On client side I am receiving only first message i.e. Message
Packet1, and there no other packets on NSInputStream showing. or NSStreamEventHasBytesAvailable option in delegate method->
- (void)stream:(NSStream *)theStream
handleEvent:(NSStreamEvent)streamEvent
Not calling most of the time more than one but sometimes It calls and gets MessagePacket2 or MessagePacket4 data.
Please help me out, I am unable to figure out why I am receiving only first packet from server instead of 4 packets, as server sending 4 packets.
I have used code from the below tutorial ->
http://www.raywenderlich.com/3932/networking-tutorial-for-ios-how-to-create-a-socket-based-iphone-app-and-server#comments
My code is here->
#interface NetworkManager()<NSStreamDelegate>
#property (strong, nonatomic)NSInputStream *objInputStream;
#property (strong, nonatomic)NSOutputStream *objOutputStream;
#end
- (void)initializeNetworkCommunicationToServer
{
self.networkOpened = NO;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL,
(CFStringRef)SERVER_HOSTNAME,
SERVER_PORT_ADDR,
&readStream,
&writeStream);
self.objInputStream = (__bridge_transfer NSInputStream *)readStream;
self.objOutputStream = (__bridge_transfer NSOutputStream*)writeStream;
[self.objInputStream setDelegate:self];
[self.objOutputStream setDelegate:self];
[self.objInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[self.objOutputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[self.objInputStream open];
[self.objOutputStream open];
}
//------------------------------------------------------
pragma NSStreamDelegate delegate method
//------------------------------------------------------
- (void)stream:(NSStream *)theStream
handleEvent:(NSStreamEvent)streamEvent
{
switch (streamEvent)
{
case NSStreamEventNone:
{
NSLog(#"NSStreamEventNone");
break;
}
case NSStreamEventOpenCompleted:
{
NSLog(#"NSStreamEventOpenCompleted");
}
break;
case NSStreamEventHasBytesAvailable:
{
NSLog(#"NSStreamEventHasBytesAvailable:");
if (theStream == self.objInputStream)
{
while ([self.objInputStream hasBytesAvailable])
{
uint8_t buffer[1024];
unsigned int len = 0;
len = [self.objInputStream read:buffer
maxLength:sizeof(buffer)];
if (len > 0)
{
NSString *output =
[[NSString alloc] initWithBytes:buffer
length:len
encoding:NSASCIIStringEncoding];
if (nil != output)
{
NSLog(#"server said: %#", output);
}
}//end of if(len > 0)
}//end of while
} //end of if (theStream == self.objInputStream)
}
break;
case NSStreamEventErrorOccurred:
{
NSLog(#"NSStreamEventErrorOccurred: Can not connect to the host!");
}
break;
case NSStreamEventEndEncountered:
{
NSLog(#"NSStreamEventEndEncountered & network connection ended");
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
theStream = nil;
}
break;
// The NSStreamEventHasSpaceAvailable event indicates that you can write (at least one byte!) to the stream without blocking. That does not mean that previously written data is completely delivered to the other endpoint of the connection.
case NSStreamEventHasSpaceAvailable:
{
NSLog(#"NSStreamEventHasSpaceAvailable");
if(NO == self.networkOpened)
{
self.networkOpened = YES;
[self sendMessage:#"login:username,password"];
}
}
break;
default:
{
NSLog(#"Unknown event");
}
}
}
//------------------------------------------------------
#pragma mark - send packet
//------------------------------------------------------
- (void)sendMessage:(NSString*)lstrMessage
{
NSMutableData *data = [[NSMutableData alloc] initWithData:
[lstrMessage dataUsingEncoding:NSASCIIStringEncoding]];
unsigned char suffixBytes[] = {1, 1, 0};
[data appendBytes:suffixBytes length:3];
[self.objOutputStream write:[data bytes] maxLength:[data length]];
NSLog(#"message sent->%#",[NSString stringWithUTF8String:[data bytes]]);
}
I have no idea what is wrong with your code, but my experience with streams tuning tells me that there will be make sence to try another way to subscribe to Core Foundation streams using CF API instead of toll-free-bridging it to NSInputStream. I mean CFReadStreamSetClient and CFReadStreamScheduleWithRunLoop functions. You can see example how to do so in my helper class for testing POSInputStreamLibrary.
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.
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];
I have an application that connects users to a serve. Users can connect to a host to communicate inserting an IP address and communicate using NSStream. Pressing the connect button starts a connection to the host and the user can receive and send messages.
I want to give a user the ability to disconnect from the server to not listen to pressing a disconnect button. The problem is that I am never able to successfully close the stream.
This is my current implementation:
// Create connection "Connect Button"
- (void)initNetworkCommunication:(UIViewController *)sender {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"123.456.789.123", 80, &readStream, &writeStream);
inputStream = (NSInputStream *)readStream;
outputStream = (NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
controller = sender;
}
// Stop connection "Disconnect Button"
- (void)stopNetworkConnection {
[inputStream close];
[outputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream release];
[outputStream release];
inputStream = nil;
outputStream = nil;
NSLog(#"Disconnect from stream");
}
The flow of operations is as follows:
User insert a valid IP address;
User press "Connect Button";
Everything works fine, connection established and user can send/receive messages;
User press "Disconnect Button" and user do not receive messages;
User press "Connect Button" but no longer able to connect
I tried this solution but it did not work:
NSStream closing and opening error
Thanks in advance for the help
I have written this code to setup a stream with a server:
-(void)streamOpenWithIp:(NSString *)ip withPortNumber:(int)portNumber;
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)ip, portNumber, &readStream, &writeStream);
if(readStream && writeStream)
{
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
//Setup inpustream
inputStream = (__bridge NSInputStream *)readStream;
[inputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
//Setup outputstream
outputStream = (__bridge NSOutputStream *)writeStream;
[outputStream setDelegate:self];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream open];
}
}
I am able to connect to the server and send & receive data. But I want to check if the connection is still there. If I disconnect a cable from the wifi router, I still can send data in the stream and no error occurred.
You can solve this on application level by using a timer to send some message and check if you receive something back. But you can solve this also with TCP Keep-Alive technique on a lower level.
How do I implement this with NSStream? How can I set the checking interval?
I assume that you get a NSStreamEventErrorOcurred when the stream is down by checking it with TCP Keep-Alive?
I have checked this post, but I can't figure it out:
Keeping a socket connection alive in iOS
Thanks for your help!
You can get the native socket handle with
CFDataRef socketData = CFReadStreamCopyProperty((__bridge CFReadStreamRef)(inputStream), kCFStreamPropertySocketNativeHandle);
CFSocketNativeHandle socket;
CFDataGetBytes(socketData, CFRangeMake(0, sizeof(CFSocketNativeHandle)), (UInt8 *)&socket);
CFRelease(socketData);
and then set socket options (you need to #include <sys/socket.h> for this):
int on = 1;
if (setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) {
NSLog(#"setsockopt failed: %s", strerror(errno));
}
You could put this code in the event handler function for the kCFStreamEventOpenCompleted event:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event {
switch (event) {
case kCFStreamEventOpenCompleted:
if (stream == self.inputStream) {
CFDataRef socketData = CFReadStreamCopyProperty((__bridge CFReadStreamRef)(stream), kCFStreamPropertySocketNativeHandle);
CFSocketNativeHandle socket;
CFDataGetBytes(socketData, CFRangeMake(0, sizeof(CFSocketNativeHandle)), (UInt8 *)&socket);
CFRelease(socketData);
int on = 1;
if (setsockopt(socket, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof(on)) == -1) {
NSLog(#"setsockopt failed: %s", strerror(errno));
}
}
break;
/* ... other cases ... */;
}
}
There is a more complete answer to a similar question.
For the example of the app that starts sending keepalive after 10 seconds, sends keppalive every 2 seconds and closes the stream after 4 keepalives with no reply, take a look at this post:
Is it possible to activate TCP keepalive on Apple iOS devices
It also shows how to set retransmission timeout after which the connection is closed.