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

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"

Related

Not getting more messages after receiving first message from Java server in iOS client using NSInputStream

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.

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.

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.

Trying to use GCDAsyncSocket for buffered transfer

(UPDATED) I am trying to read a large file ( a video or a picture) and send it to a remote server via a SOAP request. I need to encode the data as a Base64 string. I am trying to do this as follows:
Create a template xml for the SOAP request that will go "around" the base64 encoded data
push the first part of the SOAP xml into a buffer
open the video file and encode it in chunks and push each encoded chunk into the buffer
finally, push the second part of the SOAP xml
To be able to "enqueue" parts as above, I am trying to use GCDAsyncSocket with its buffering capabilities. I figure that since GCDAsyncSocket operates on TCP level, I need to write the HTTP POST header myself. So, there are many moving parts which I only vaguely understand and I might be doing it all incorrectly. But my socket never seems to even take off and I am not even sure how to debug it. Here is my relevant code, try to see if you spot any obvious errors:
NSString *soapBody = ...; //Create the SOAP xml here with the part in the middle reserved for the Base64 encoded data (marked with string "CUT_HERE";
NSArray *soapBodyArray = [soapBody componentsSeparatedByString:#"CUT_HERE"];
self.p_soapBodyPart1 = [soapBodyArray[0] dataUsingEncoding:NSUTF8StringEncoding];
self.p_soapBodyPart2 = [soapBodyArray[1] dataUsingEncoding:NSUTF8StringEncoding];
socketQueue = dispatch_queue_create("socketQueue", NULL);//socketQueue is an ivar
self.p_socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:socketQueue];//create the socket
NSError *err = nil;
if (![p_socket connectToHost:myURL onPort:80 error:&err]) // Asynchronous!
{
NSLog(#"I goofed: %#", err);
return;
}
NSString* httpHeader = [NSString stringWithFormat:#"POST %# HTTP/1.1\r\nHost: %#\r\nAccept-Encoding: gzip, deflate\r\nContent-Type: text/xml\r\nAccept-Language: en-us\r\nAccept: */*\r\nSOAPAction: http://tempuri.org/myAction\r\nConnection: keep-alive\r\nUser-Agent: ...\r\n\r\n", webserviceOperationsPath, webserviceHost];//Create the HTTP POST header
[p_socket writeData:[httpHeader dataUsingEncoding:NSUTF8StringEncoding] withTimeout:-1 tag:1]; //enqueue the HTTP header
[p_socket writeData:self.p_soapBodyPart1 withTimeout:-1 tag:2]; //enqueue the first part of the SOAP xml
[self setUpStreamsForInputFile: [self.p_mediaURL path]];//set up NSInputStream to read from media file and encode it as Base64
The socket seems to always connect all right, which I see using this delegate method:
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
NSLog(#"Socket Connected");
}
setUpStreamsForInputFile method (that is called in the first code listing above):
- (void)setUpStreamsForInputFile:(NSString *)inpath {
self.p_iStream = [[NSInputStream alloc] initWithFileAtPath:inpath];
[p_iStream setDelegate:self];
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];
[[NSRunLoop currentRunLoop] run];
});
}
Now, the NSInputStream setup in the previous method will send events to this delegate:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventHasBytesAvailable:
{
if (stream == self.p_iStream){
if(!self.p_tempMutableData) {
self.p_tempMutableData = [NSMutableData data];
}
uint8_t buf[24000];
unsigned int len = 0;
len = [p_iStream read:buf maxLength:24000];//read a chunk from the file
if(len) {
[p_tempMutableData appendBytes:(const void *)buf length:len];
NSString* base64encData = [Base64 encodeBase64WithData:self.p_tempMutableData];//encode the chunk
self.p_streamEncData = [base64encData dataUsingEncoding:NSUTF8StringEncoding];
[p_socket writeData:self.p_streamEncData withTimeout:-1 tag:3];//write the encoded chunk to the socket
}
}
break;
}
case NSStreamEventEndEncountered:
{
[stream close];
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
stream = nil;
[p_socket writeData:self.p_soapBodyPart2 withTimeout:-1 tag:4];//write the second part of SOAP xml
break;
}
... //some other events handled here
}
}
The socket is supposed to output things to the log with this delegate
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
if (tag == 1)
NSLog(#"HTTP Header Written");
else if (tag == 2)
NSLog(#"Soap Part 1 written");
else if (tag == 3)
NSLog(#"File written");
else if (tag == 4)
NSLog(#"Soap Part 2 written");
}
but this happens kind of randomly. For example, sometimes I see the first 2 if's called and sometimes not. When I do and it reaches the third "if" (the one where I am writing the actual encoded data), it writes it only 2 or 3 times and that's it - too few times, I think, given the size of the file. I never see it reach the last "if", where it should write the last part of SOAP xml.
Would appreciate any help! Thanks in advance.
Further update (3/19/13)
Today testing the socket I am no longer getting the write events at all, which tells me that it is random and I am doing something terribly wrong. Today the connection opens but then times out at some point, as I can see with the following delegate method:
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{ // This method is executed on the socketQueue (not the main thread)
dispatch_async(dispatch_get_main_queue(), ^{
#autoreleasepool {
NSLog(#"socketDidDisconnect:withError: \"%#\"", err);
}
});
}
which returns
socketDidDisconnect:withError: "Error Domain=NSPOSIXErrorDomain Code=60 "Operation timed out" UserInfo=0x1cd89b00 {NSLocalizedFailureReason=Error in connect() function, NSLocalizedDescription=Operation timed out}"
while I am still running writes of Base64 data in the stream delegate above.

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