Why does CFStreamCreatePairWithSocketToCFHost fail when CFStreamCreatePairWithSocketToHost succeeds - ios

On iOS6.1, the following code should do the very same thing, but when I forgot to write addr.sin_len = sizeof(adde) the first block failed. The original error was:
GOT EVENT FROM INPUT Event: 8
ERR: Error Domain=NSPOSIXErrorDomain Code=12 "The operation couldn’t be completed. Cannot allocate memory"
After adding in the missing line to set the struct size, the first block worked just as the second. Probably other developers will see that error message and trip on this post.
The code:
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
#if 1 // LONG WAY
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr); // ORIGINALLY WAS MISSING
addr.sin_family = AF_INET;
addr.sin_port = htons(5566);
int ret = inet_pton(AF_INET, "192.168.1.2", &(addr.sin_addr.s_addr)); // IPv4
assert(ret == 1);
NSLog(#"CONNECT");
CFDataRef address = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)&addr, sizeof(addr));
assert(address);
CFHostRef macMini = CFHostCreateWithAddress(kCFAllocatorDefault, address);
CFRelease(address);
assert(macMini);
// (tried, makes not difference) CFHostScheduleWithRunLoop (macMini, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode);
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, macMini, 5566, &readStream, &writeStream);
CFRelease(macMini);
#else // SHORT WAY
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, CFSTR("192.168.1.2"), 5566, &readStream, &writeStream);
#endif
assert(readStream);
assert(writeStream);
iStream = CFBridgingRelease(readStream);
oStream = CFBridgingRelease(writeStream);
[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[iStream open];
NSLog(#"ISTREAM %# status=%d", iStream, [iStream streamStatus]);
NSLog(#"ERR: %#", [iStream streamError]);
[oStream setDelegate:self];
[oStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[oStream open];
NSLog(#"OSTREAM %# status=%d", oStream, [oStream streamStatus]);
NSLog(#"ERR: %#", [oStream streamError]);

Problem was that sin_len was not set. The nice thing about having both sets of code above, is that you can see how to accomplish the task either way.

Related

NSOutputStream (TCP) hangs indefinitely on stream:write after refactoring code

I have some code for writing/reading over a TCP NSStream. I refactored it, and now the NSOutputStream:write hangs indefinitely. Spent hours looking at it, can't crack it. I see the TCP SYN->ACK process happening fine - and I went back to the old version to test, and it still works (so the device on the other end is fine).
Everything is done on a single thread (stream creation, delegate, reading/writing, etc) which is not the main thread. Threading did not change in the refactoring at all.
Here is the code to create the streams (no change in refactoring):
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)_cam.IP, PTPIP_PORT, &readStream, &writeStream);
_mainInputStream = (__bridge NSInputStream *)readStream;
_mainOutputStream = (__bridge NSOutputStream *)writeStream;
[_mainInputStream setDelegate:self];
[_mainOutputStream setDelegate:self];
[_mainInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // common mode!
[_mainOutputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; // common mode!
[_mainInputStream open];
[_mainOutputStream open];
[[NSRunLoop currentRunLoop] run];
Here is the new code that fails while writing, it hangs on stream:write. When I check the stream status right before the write call, the stream is open (good). I also debugged the input to the function before & after the refactoring, there is no difference (good). I also thought to check for stream delegate's NSStreamEventHasSpaceAvailable, but that appears to NEVER be thrown for either the old (working) or new (broken) code, so I guess it isn't applicable for TCP streams the way I'm using them. Other stream delegate events like NSStreamEventHasBytesAvailable are being thrown just fine...
- (int)write:(const unsigned char*)bytes size:(unsigned int)size {
unsigned int bytesToWrite = size;
while( bytesToWrite > 0) {
unsigned int chunkSize = (bytesToWrite > PTP_IP_MAX_PACKET_LEN)?PTP_IP_MAX_PACKET_LEN:bytesToWrite;
// welcome to hangsville, population YOU
int result = [self.myWorker.mainOutputStream write:bytes+(size-bytesToWrite) maxLength:chunkSize];
if( result <= 0 )
return PTP_ERROR_IO;
bytesToWrite -= result;
}
return size-bytesToWrite;
}
Here's the old code that somehow works, even though input is the same...
static short
ptpip_write_func (unsigned char *bytes, unsigned int size, void *data)
{
PTPCamera* cam = (__bridge PTPCamera*)data;
unsigned int bytesToWrite = size;
while( bytesToWrite > 0) {
unsigned chunkSize = (bytesToWrite > PTP_IP_MAX_PACKET_LEN)?PTP_IP_MAX_PACKET_LEN:bytesToWrite;
unsigned char chunk[chunkSize];
memcpy(chunk, bytes+(size-bytesToWrite), chunkSize);
int result = [cam.outStream write:chunk maxLength:chunkSize];
if( result <= 0 )
return PTP_ERROR_IO;
bytesToWrite -= result;
}
return PTP_RC_OK;
}
What am I missing?
After a ton of debugging I concluded that there was really nothing different happening between the two versions, and that it must have been a fluke that it was ever working.
Sure enough, after additional researching I figured out that having stream open call on any thread OTHER than main is not going to work.
Here is the updated code, that works:
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)cam.IP, PTPIP_PORT, &readStream, &writeStream);
worker.mainInputStream = (__bridge NSInputStream *)readStream;
worker.mainOutputStream = (__bridge NSOutputStream *)writeStream;
[worker.mainInputStream setDelegate:worker];
[worker.mainOutputStream setDelegate:worker];
dispatch_async(dispatch_get_main_queue(), ^{
[worker.mainInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[worker.mainOutputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[worker.mainInputStream open];
[worker.mainOutputStream open];
});

NSURLSession set header 'Connection' to 'Upgrade' not work

Environment: Mac OS X 10.9, Xcode 5.0.2
I need to create an HTTP request for my mini WebSocket client, example request:
GET / HTTP/1.1
Host: serverwebsocket.com:10080
Upgrade: websocket
Connection: Upgrade
Origin: http://from.com
I've created NSURLSession with NSURLSessionConfiguration and set the headers, and Wireshark shows all header set except Connection which stays keep-alive, but should not.
// Create request based on Sessions
// Create sesson configuretion
NSURLSessionConfiguration* sessionConf = [NSURLSessionConfiguration defaultSessionConfiguration];
// Configure session config
// set header value, detail header websocket on http://learn.javascript.ru/websockets
sessionConf.HTTPAdditionalHeaders = #{#"Upgrade": #"websocket",
#"Connection": #"Upgrage",
#"Origin": #"http://from.com",
#"User-Agent": #"Chrome/36.0.198.5.143"};
// Declare handler block of response
__block void (^handler)(NSData* data, NSURLResponse* response, NSError* error);
handler = ^(NSData* data, NSURLResponse* response, NSError* error)
{
// If receive response from server
if(data)
{
NSString* result = [[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"response data: %#", result);
}
else // something wrong
{
NSString* errorText;
if(error)
{
errorText = [error localizedDescription];
}
else // Generic description
{
errorText = #"Error Interner connection";
}
NSLog(#"Request error: %#",errorText );
}
};
// Create Session
NSURLSession* session = [NSURLSession sessionWithConfiguration:sessionConf];
NSURL* url = [NSURL URLWithString: [_textUrl stringValue]];
[[session dataTaskWithURL:url completionHandler:handler] resume];
How can I change the header for Connection? And I do not want use another websocket library, I want to handle HTTP header in low level.
May, instead of the NSURLSession there are other classes work with network? In NSURLRequest same problem.
For manage HTTP header in low level best way use CFNetwork level. But do not use CFHTTPStream for send/receive of data, because CFHTTPStream support only two state for ‘Connection’ header: ‘keep-alive’ and ‘close’. See in file https://opensource.apple.com/source/CFNetwork/CFNetwork-128/HTTP/CFHTTPStream.c function ‘extern void cleanUpRequest()’.
Solution:
1 Create and customize request by CFHTTPMessageRef and CFHTTPMessageSetHeaderFieldValue
2 Convert request to raw data
3 Send/receive raw data uses NSOutputStream and NSInputStream
This is a minimal example send/receive HTTP WebSocket message in CFNetwork level:
#import "AppDelegate.h"
#implementation AppDelegate
NSInputStream* inputStream;
NSOutputStream* outputStream;
NSMutableData* inputBuffer; // This data receive from server
NSMutableData* outputBuffer; // This data send to server
- (IBAction)btnSend:(id)sender
{
NSURL* url = [NSURL URLWithString: [_textUrl stringValue]];
if( !outputBuffer)
{
outputBuffer = [[NSMutableData alloc] init];
}
///////////////////////////////////////////////////////////////
/////////////// Create GET request uses CFNetwork level
CFHTTPMessageRef request = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_1);
// Set Host header
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Host"), (__bridge CFStringRef)(url.port ? [NSString stringWithFormat:#"%#:%#", url.host, url.port] : url.host));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("User-Agent"), CFSTR("Chrome/36.0.198.5.143"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Pragma"), CFSTR("no-cache"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Cache-Control"), CFSTR("no-cache"));
// Set special headers for websocket, detail on http://learn.javascript.ru/websockets
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Upgrade"), CFSTR("websocket"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Connection"), CFSTR("Upgrade"));
NSString* origin = #"http://from.com";
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Origin"), (__bridge CFStringRef)origin);
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Protocol"), CFSTR("SIP"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Key"), CFSTR("yuPCDHanXBphfIH83e4JVw=="));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Version"), CFSTR("13"));
CFHTTPMessageSetHeaderFieldValue(request, CFSTR("Sec-WebSocket-Extensions"), CFSTR("permessage-deflate; client_max_window_bits, x-webkit-deflate-frame"));
// Convert request to raw data
NSData* rawHttpMessage = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request));
[outputBuffer appendData:rawHttpMessage];
CFRelease(request);
/////////////////////////////////////////////////////////////////
/////////////// Customize stream for sending/receive data
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)url.host, [url.port intValue],
&readStream, &writeStream);
inputStream = (__bridge_transfer NSInputStream*)readStream;
outputStream = (__bridge_transfer NSOutputStream*)writeStream;
[inputStream setDelegate:self]; // Activate stream event handler
[outputStream setDelegate:self]; // Activate stream event handler
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
[inputStream retain];
[outputStream retain];
}
// Handler of event for NSInputStream and NSOutputStream
// Detail see: https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Streams/Articles/NetworkStreams.html
-(void)stream:(NSStream*)stream handleEvent:(NSStreamEvent)eventCode
{
switch (eventCode)
{
case NSStreamEventHasSpaceAvailable: // when outputstream can send data
if( stream == outputStream)
{
//NSLog(#"Send data [%lu]", [outputBuffer length]);
[outputStream write:[outputBuffer bytes] maxLength:[outputBuffer length]]; // Send data
// Close output stream when all data sent
[outputStream close];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream release];
outputStream = nil;
[outputBuffer release];
outputBuffer = nil;
}
break;
case NSStreamEventHasBytesAvailable: // when inputstream received data
{
const int bufSize = 2048;
if(!inputBuffer)
{
inputBuffer = [[NSMutableData data] retain];
}
uint8_t buf[bufSize];
long len = 0;
len = [inputStream read:buf maxLength:bufSize]; // get data
if(len)
{
[inputBuffer appendBytes:(const void*)buf length:len];
//NSLog(#"Received data from server [%lu]: %#", [inputBuffer length], inputBuffer); // Show in raw format
NSString* rs = [NSString stringWithUTF8String:[inputBuffer bytes]];
NSLog(#"Received data from server [%lu]:\n%#", [inputBuffer length], rs); // Show in string format
}
else
{
NSLog(#"Received data Error[%li]: %#",(long)[inputStream.streamError code], [inputStream.streamError localizedDescription]);
}
// Close inputStream stream when all data receive
[inputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream release];
inputStream = nil;
[inputBuffer release];
inputBuffer = nil;
}
break;
case NSStreamEventErrorOccurred: // when error of transmited data
if( stream == outputStream)
{
NSError* error = [stream streamError];
NSLog(#"Error sending data [%li]: %#",(long)[error code], [error localizedDescription]);
}
else if( stream == inputStream)
{
NSError* error = [stream streamError];
NSLog(#"Error receive data [%li]: %#",(long)[error code], [error localizedDescription]);
}
break;
case NSStreamEventEndEncountered: // this is not work ;)
if( stream == outputStream)
{
NSLog(#"outputStream End");
}
else if( stream == inputStream)
{
NSLog(#"inputputStream End");
}
break;
}
}
#end
This sample send WebSocket request and receive WeSocket response:
WebSocket request to server:
GET / HTTP/1.1
Host: serverwebsocket.com:10080
Upgrade: websocket
Connection: Upgrade
Origin: http://from.com
Sec-WebSocket-Protocol:SIP
Sec-WebSocket-Key: yuPCDHanXBphfIH83e4JVw==
Sec-WebSocket-Version: 13
WebSocket response from server:
HTTP/1.1 101 Switching Protocols
Content-Length: 0
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: mEgcu0WkPuU6yMRtyUl/C+X8zJE=
Sec-WebSocket-Protocol: sip
Sec-WebSocket-Version: 13
For one thing
#"Connection": #"Upgrage"
should probably be
#"Connection": #"Upgrade"

CFSocket accept callback is not being called. What have I missed?

I'm currently writing a simple TCP server class in Objective-C which should be able to bind to and listen on a particular port. I particular I have a function -(void) start which creates a CFSockets, binds it to the port specified and adds it to a run loop. It looks like this:
- (void) start {
//This function is called after the initialiser
if([self isStarted]) //if already started, return without doing anything
return;
//create a socket
CFSocketContext socketCtxt = {0, (__bridge void *)self, NULL, NULL, NULL};
socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&SocketAcceptCallback, &socketCtxt);
NSAssert(socket, #"Error: Cannot create a socket...");
int yes = 1;
setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
//bind it to the desired port
struct sockaddr_in serverAddress;
socklen_t nameLen = 0;
nameLen = sizeof(serverAddress);
memset(&serverAddress, 0, sizeof(serverAddress));
serverAddress.sin_len = nameLen;
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(self.port);
serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);
NSData* address4 = [NSData dataWithBytes:&serverAddress length:nameLen];
if(kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)address4)) {
if (socket) CFRelease(socket);
socket = NULL;
NSAssert(NO, #"Unable to bind the created socket to the desired port");
}
//add to the current run loop
CFRunLoopSourceRef rls = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
assert(rls!=0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopCommonModes);
//done!
}
I also define the callback function SocketAcceptCallback like this:
static void SocketAcceptCallback(CFSocketRef sock, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
//this method is called after a connection is accepted
//here we bind the received connection to a pair of
//NSStreams
if(type == kCFSocketAcceptCallBack){
CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
struct sockaddr_in peerAddress;
socklen_t peerLen = sizeof(peerAddress);
NSString * peer = nil;
if (getpeername(nativeSocketHandle, (struct sockaddr *)&peerAddress, (socklen_t *)&peerLen) == 0) {
peer = [NSString stringWithUTF8String:inet_ntoa(peerAddress.sin_addr)];
} else {
peer = #"Generic Peer";
}
NSLog(#"%# has connected", peer);
//Find corresponding SocketServer object -- it is passed to the callback in the INFO pointer
SocketServer *obj = (__bridge SocketServer*)info;
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
CFStreamCreatePairWithSocket(NULL, nativeSocketHandle, &readStream, &writeStream);
if (readStream && writeStream) {
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
//bridge to NSStreams
obj.inputStream = (NSInputStream *)readStream;
obj.outputStream = (NSOutputStream *)writeStream;
//set delegates
[obj.inputStream setDelegate:obj];
[obj.outputStream setDelegate:obj];
//schedule the runloops
[obj.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[obj.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//open connections
[obj.inputStream open];
[obj.outputStream open];
} else {
// on any failure, need to destroy the CFSocketNativeHandle
// since we are not going to use it any more
close(nativeSocketHandle);
}
/*
if (readStream) CFRelease(readStream);
if (writeStream) CFRelease(writeStream); */
}
}
In the callback, I bridge the connection to a pair of NSStreams which I have as member fields in my class and which I use to read and write.
Now the problem: I have tried to run this server and connect to it via telnet. It connects, but the aforementioned callback is not being called (I have checked by adding a diagnostic NSLog in it). I suspect I haven't added the socket to a run loop correctly, but I can't find any mistakes myself. Any ideas as to what it might be?

NSStream TCP Keep-alive iOS

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.

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