TCP connections with NSStream/CFStream - ios

I am desperately trying to figure out how to detect errors when opening TCP streams using NSStream +getStreamsToHost/CFStreamCreatePairWithSocket(). If I do this:
NSInputStream* input = nil;
NSOutputStream* output = nil;
[NSStream getStreamstoHost:[NSHost hostWithName:#"localhost"] port:80 inputStream:&input outputStream:&output];
NSError* error1 = [input streamError];
assert(error1 == nil);
NSStreamStatus status1 = [input streamStatus];
[input open];
NSError* error2 = [input streamError];
assert(error2 == nil);
NSStreamStatus status2 = [input streamStatus];
status1 is NSStreamStatusNotOpen, which is expected. error1 is nil. error2 is also nil, and status2 is NSStreamStatusOpening. If I telnet to the same address, I get connection refused - there is nothing listening on port 80. If I try to connect to some nonsensical address, such as yaddayadda, I get nil streams.
How do I handle errors properly? No example anywhere seems to handle error conditions, and the docs don't say anything about it, other than the streams may be nil. I'm stumped. Don't tell me I have to run the connection through a run loop, just to get proper error handling...?
I know there's always the possibility of using good ol' BSD sockets, but the docs warns against that, as some high level networking features may fail (auto connections over VPN, and similar stuff).

I faced the same problem and this is how i fixed it.
In my case i used the SSL but you can skip that part of code.
NSString *host = #"10.38.129.234";
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)host, 403, &readStream, &writeStream);
[readStream setProperty:NSStreamSocketSecurityLevelSSLv3 forKey:NSStreamSocketSecurityLevelKey];
[readStream setProperty:(id)kCFBooleanFalse forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
[writeStream setProperty:NSStreamSocketSecurityLevelSSLv3 forKey:NSStreamSocketSecurityLevelKey];
[writeStream setProperty:(id)kCFBooleanFalse forKey:(NSString *)kCFStreamPropertyShouldCloseNativeSocket];
//Setup SSL properties
NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
[NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
[NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
#"10.38.129.234",kCFStreamSSLPeerName,
nil];
settings = [settings autorelease];
CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
//Open the OutputStream
CFWriteStreamOpen(writeStream);
UInt8 buf[] = "your message ";
[self writeToStream:writeStream :buf];
//Read the Stream
CFReadStreamOpen(readStream);
NSString *response = [self readFromStream:readStream];
if(response != nil)
{
NSLog(#"%#",response);
}
UInt8 pull[] = "another message\n";
[self writeToStream:writeStream :pull];
response = [self readFromStream:readStream];
if(response != nil)
{
NSLog(#"%#",response);
}
//Close the readstream
CFReadStreamClose(readStream);
CFRelease(readStream);
readStream = NULL;
//Close the writestream
CFWriteStreamClose(writeStream);
CFRelease(writeStream);
writeStream = NULL;

read over the stream guide at apple LINK
you will see you are suppose to have a method that switches stream events like so
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent)
{
case NSStreamEventOpenCompleted:
{
DDLogVerbose(#"Stream opened");
break;
}
case NSStreamEventHasBytesAvailable:
{
if(!rawData) {
rawData = [[NSMutableData data] retain];
}
uint8_t buf[1024];
unsigned int len = 0;
len = [(NSInputStream *)theStream read:buf maxLength:1024];
if(len) {
[rawData initWithBytes:buf length:len];
} else {
DDLogVerbose(#"no buffer!");
}
const uint8_t *bytes = [rawData bytes];
NSMutableArray *mutableBuffer = [[NSMutableArray alloc] initWithCapacity:len];
for (int i =0; i < [rawData length]; i++) {
[mutableBuffer addObject:[NSString stringWithFormat:#"%02X", bytes[i]]];
}
[self gateKeeper:mutableBuffer];
[mutableBuffer release];
break;
}
case NSStreamEventErrorOccurred:
{
if ([theStream isKindOfClass:[NSInputStream class]]) {
NSString* address = [self getAddress];
NSString* myIPAdress = [NSString stringWithFormat:#"IP Address: %#", address];
//[cClient updateRequest];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Cant Connect" message:[NSString stringWithFormat:#"Cant connect to server: %#, Make sure you are connected to the proper wireless network. Your Ip Address is %#",CCV.ipAddress,myIPAdress] delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:#"Reconnect", nil];
[alert show];
[alert release];
}
break;
}
case NSStreamEventEndEncountered:
{
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[theStream release];
break;
}
case NSStreamEventHasSpaceAvailable:
{
//DDLogVerbose(#"has space available");
break;
}
case NSStreamEventNone:
{
DDLogVerbose(#"none");
break;
}
default:
{
DDLogVerbose(#"Unknown event");
}
}
}

What you need to do is either poll for the status using the streamStatus method on the input stream, or schedule the streams for events in a run loop. To better provide error information about erroneous host names, it's better to resolve the name before hand using either NSHost or CFHost.

Related

iphone socket stream buffer different on simulator and iPhone 6 plus?

I have this sample code taken from the example of this iPhone chat server.
In my case I need to send larger amounts of data, 100k-200k thus I changed the buffer size to something that can accommodate what I want.
On the iOS simulator (emulating a 6plus) everything works perfectly, as soon as I try to debug on my iPhone 6plus after implementing the suggestions of #tc. I get the incoming message broken into 3-4 parts! Any ideas?
2015-02-03 22:42:34.756 Sock1[7390:3773054] Incoming message: XML part 1
2015-02-03 22:42:34.759 Sock1[7390:3773054] Incoming message: XML part 2
2015-02-03 22:42:34.774 Sock1[7390:3773054] Incoming message: XML part 3
2015-02-03 22:42:34.794 Sock1[7390:3773054] Incoming message: XML part 4
XML part 1-4 make up the entire message that should have been in one as there is no null byte character within.
Just to make things even stranger, when I add a break point on this line:
[currentMessage appendBytes:buffer length:len];
and go through step by step (69-70 pressing Continue) everything works fine! No errors at all! So it is something to do with the speed of parsing or receiving or something I can't figure out?
What I've made sure is that the server doesn't send any null byte characters apart from the terminating one at the end of the message.
The code I use is this:
#implementation ViewController
bool connectionError = true;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)initNetworkCommunication {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)#"192.168.1.1", 6035, &readStream, &writeStream);
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
- (void)closeAll {
NSLog(#"Closing streams.");
[inputStream close];
[outputStream close];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream setDelegate:nil];
[outputStream setDelegate:nil];
inputStream = nil;
outputStream = nil;
}
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
connectionError = false;
if (theStream == inputStream) {
uint8_t buffer[1024];
long len;
NSMutableData *currentMessage;
currentMessage = [NSMutableData dataWithBytes:"" length:strlen("")];
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
[currentMessage appendBytes:buffer length:len];
}
NSString *newMessage = [[NSString alloc] initWithData:currentMessage encoding:NSUTF8StringEncoding];
NSLog(#"Incoming message: %#",newMessage);
NSData *nullByte = [NSMutableData dataWithLength:1];
len = currentMessage.length;
NSRange searchRange = {0, len};
for (NSRange r; (r = [currentMessage rangeOfData:nullByte options:0 range:searchRange]).length; ) {
NSString *message = [[NSString alloc] initWithBytes:currentMessage.bytes length:r.location encoding:NSUTF8StringEncoding];
searchRange.location = r.location+r.length;
searchRange.length = len - searchRange.location;
[self messageReceived:message];
}
[currentMessage replaceBytesInRange:(NSRange){0, searchRange.location} withBytes:NULL length:0];
}
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
connectionError = true;
[self closeAll];
[self connectionLost];
break;
case NSStreamEventEndEncountered:
break;
default:
NSLog(#"Unknown event");
}
}
- (void) connectionLost {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Alert!"
message:#"Connection to the server lost!"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
}
- (void) messageReceived:(NSString *)message {
[messages addObject:message];
if (inputStream.streamStatus == NSStreamStatusClosed || inputStream.streamStatus == NSStreamStatusError || inputStream.streamStatus == NSStreamStatusNotOpen) {
[self closeAll];
[self initNetworkCommunication];
}
// do things with the message, XML parsing...
}
- (void) initConnection {
[self initNetworkCommunication];
messages = [[NSMutableArray alloc] init];
}
- (IBAction)joinChat:(id)sender {
[self initConnection];
[self sendSocketMessage: #"iam:" message: _inputNameField.text];
}
- (void) sendSocketMessage:(NSString*) sendCommand message:(NSString*) sendMessage
{
// do something...
if (outputStream.streamStatus == NSStreamStatusClosed || outputStream.streamStatus == NSStreamStatusError || outputStream.streamStatus == NSStreamStatusNotOpen) {
[self closeAll];
[self initNetworkCommunication];
}
NSString *response = [NSString stringWithFormat: #"%#%#", sendCommand, sendMessage];
NSData *data = [[NSData alloc] initWithData:[response dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[data bytes] maxLength:[data length]];
NSLog(#"clint sent: %#", response);
}
#end
The short answer is that TCP offers a stream of bytes, not a stream of variable-length messages¹. I don't know why it works in the simulator, unless 192.168.1.1 is the same machine (in which case it isn't subject to the usual flow control, though it's perhaps surprising that the kernel buffers are that big).
To work around this, you need to somehow signal where the end of a message is. There are two common ways:
Length-prefixes. Examples include DJB's netstrings (e.g. 5:abcde for the bytestring "abcde"), HTTP's Content-Length, HTTP 1.1's chunked encoding, and various binary protocols typically with 8-bit, 16-bit, or variable-length-encoded lengths.
Delimiters. Examples include newlines (traditionally CRLF, e.g. in Telnet/SMTP/HTTP/IRC), null bytes, MIME boundaries (ick).
In this case, we can make use of the fact that null bytes aren't permitted within XML. currentMessage is assumed to be a NSMutableData ivar and not nil.
while ([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
[currentMessage appendBytes:buffer length:len];
}
NSData * nullByte = [NSMutableData dataWithLength:1];
len = currentMessage.length;
NSRange searchRange = {0, len};
for (NSRange r; (r = [currentMessage rangeOfData:nullByte options:0 range:searchRange]).length; ) {
NSString * message = [[NSString alloc] initWithBytes:currentMessage.bytes length:r.location encoding:NSUTF8StringEncoding];
searchRange.location = r.location+r.length;
searchRange.length = len - searchRange.location;
[self messageReceived:message];
}
[currentMessage replaceBytesInRange:(NSRange){0, searchRange.location} withBytes:NULL length:0];
Also note that initializing things in -viewDidLoad requires some care if the view can be unloaded (this is less of an issue if you only support iOS 6+, since views are no longer automatically unloaded).

App runs if I activate breakpoints, fails otherwise

I have an app that uses sockets to send login information to a server and receives a response in form of a contact list. This is the main method for processing responses from server:
-(void) stream:(NSStream*)theStream handleEvent:(NSStreamEvent)streamEvent {
switch(streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
if(theStream == inputStream) {
uint8_t buffer[8196];
int len;
while([inputStream hasBytesAvailable]) {
len = [inputStream read:buffer maxLength:sizeof(buffer)];
if(len>0) {
NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSUTF8StringEncoding];
if(output != nil) {
NSLog(#"server said: %#", output);
id jsonOutput = [self createJsonFromString:output];
if([[jsonOutput objectForKey:#"msg_type"] isEqualToString:#"LoginResponse"])
{
[self sendMessage:[self createAddressBookRequestJsonData]];
} else if ([[jsonOutput objectForKey:#"msg_type"] isEqualToString:#"GetAddressBookResponse"])
{
id contactList = [jsonOutput objectForKey:#"contact_list"];
for(id contactListItem in contactList)
{
XYZAddressBookEntry *newEntry = [[XYZAddressBookEntry alloc] init];
newEntry.firstName = [contactListItem objectForKey:#"FirstName"];
newEntry.lastName = [contactListItem objectForKey:#"LastName"];
newEntry.displayName = [contactListItem objectForKey:#"DisplayName"];
newEntry.softphoneNumber = [contactListItem objectForKey:#"SoftphoneNumber"];
newEntry.homeNumber = [contactListItem objectForKey:#"HomeNumber"];
newEntry.mobileNumber = [contactListItem objectForKey:#"MobileNumber"];
newEntry.businessNumber = [contactListItem objectForKey:#"BusinessNumber"];
newEntry.name = [contactListItem objectForKey:#"Name"];
newEntry.phoneAddress = [contactListItem objectForKey:#"PhoneAddress"];
[self.entryList addObject:newEntry];
}
[self.tableView reloadData];
}
}
}
}
}
break;
case NSStreamEventHasSpaceAvailable:
break;
case NSStreamEventEndEncountered:
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
break;
default:
NSLog(#"Unknown event:");
} }
I always get a correct output in my log, so everything goes smooth, but if I don't set a breakpoint right after NSLog(#"server said: %#", output), it will not enter the if else code block. What am I missing?
By the way, I'm a newbie to this, started using objective-c just 2 weeks ago.

Reading sent and received bytes of all sockets in ios

I am new to socket programming.I am trying to track bytes sent and received through all sockets opened in my iPhone. Which data structure present in iOS, gives information about these? I tried below code posted in one of the SO question, but it is giving data from last reboot time. I want to list all sockets established in my iPhone and track bytes sent and received through each socket
- (NSArray *)getDataCounters {
BOOL success;
struct ifaddrs *addrs;
const struct ifaddrs *cursor;
const struct if_data *networkStatisc;
int WiFiSent = 0;
int WiFiReceived = 0;
int WWANSent = 0;
int WWANReceived = 0;
NSString *name=[[[NSString alloc]init]autorelease];
success = getifaddrs(&addrs) == 0;
if (success)
{
cursor = addrs;
while (cursor != NULL)
{
name=[NSString stringWithFormat:#"%s",cursor->ifa_name];
NSLog(#"ifa_name %s == %#\n", cursor->ifa_name,name);
// names of interfaces: en0 is WiFi ,pdp_ip0 is WWAN
if (cursor->ifa_addr->sa_family == AF_LINK)
{
if ([name hasPrefix:#"en"])
{
networkStatisc = (const struct if_data *) cursor->ifa_data;
WiFiSent+=networkStatisc->ifi_obytes;
WiFiReceived+=networkStatisc->ifi_ibytes;
NSLog(#"WiFiSent %d ==%d",WiFiSent,networkStatisc->ifi_obytes);
NSLog(#"WiFiReceived %d ==%d",WiFiReceived,networkStatisc->ifi_ibytes);
}
if ([name hasPrefix:#"pdp_ip"])
{
networkStatisc = (const struct if_data *) cursor->ifa_data;
WWANSent+=networkStatisc->ifi_obytes;
WWANReceived+=networkStatisc->ifi_ibytes;
NSLog(#"WWANSent %d ==%d",WWANSent,networkStatisc->ifi_obytes);
NSLog(#"WWANReceived %d ==%d",WWANReceived,networkStatisc->ifi_ibytes);
}
}
cursor = cursor->ifa_next;
}
freeifaddrs(addrs);
}
return [NSArray arrayWithObjects:[NSNumber numberWithInt:WiFiSent], [NSNumber numberWithInt:WiFiReceived],[NSNumber numberWithInt:WWANSent],[NSNumber numberWithInt:WWANReceived], nil];
I think you should go from basics.
This is how we open socket for input and outputsream.
- (IBAction)searchForSite:(id)sender
{
NSString *urlStr = [sender stringValue];
if (![urlStr isEqualToString:#""]) {
NSURL *website = [NSURL URLWithString:urlStr];
if (!website) {
NSLog(#"%# is not a valid URL");
return;
}
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)[website host], 80, &readStream, &writeStream);
NSInputStream *inputStream = (__bridge_transfer NSInputStream *)readStream;
NSOutputStream *outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
/* Store a reference to the input and output streams so that
they don't go away.... */
...
}
}
this is the delegate method,whick will called every time when you will read or write to socket.
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
break;
case NSStreamEventHasSpaceAvailable
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
break;
case NSStreamEventEndEncountered:
break;
default:
NSLog(#"Unknown event");
}
}
for more information refer links.
https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Streams/Articles/NetworkStreams.html#//apple_ref/doc/uid/20002277-BCIDFCDI
http://www.raywenderlich.com/3932/networking-tutorial-for-ios-how-to-create-a-socket-based-iphone-app-and-server

Unable to establish a Socket connection through IOS

Im writing an IOS application for my first time. It is supposed to connect to a static IP device, and send certain "known" commands to it. But for some reason Im unable to establish a connection.
Bellow are the functions I use to establish my connection, and write data to the port.
-(void)connection//:(NSString *)serviceName forIpAddress:(NSString *)ipAddress forPort:(NSString *)portNo
{
if(input && output)
[self close];
NSString *urlString = [NSString stringWithFormat:#"%.%.%.%", "192.168.3.120"];
NSURL *website = [NSURL URLWithString:urlString];
if (!website) {
NSLog(#"%# is not a valid URL", website);
}
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)[website host], 43, &readStream, &writeStream);
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
NSInputStream *input = (__bridge NSInputStream *)readStream;
NSOutputStream *output= (__bridge NSOutputStream *)writeStream;
}
- (void)open {
[input setDelegate:self];
[output setDelegate:self];
[input scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[output scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode]; [input open];
[output open];
}
-(void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
{
NSString *event;
switch (streamEvent)
{
case NSStreamEventNone:
event = #"NSStreamEventNone";
break;
case NSStreamEventOpenCompleted:
event = #"NSStreamEventOpenCompleted";
break;
case NSStreamEventHasBytesAvailable:
event = #"NSStreamEventHasBytesAvailable";
if (theStream == input)
{
uint8_t buffer[1024];
NSInteger len;
while ([input hasBytesAvailable])
{
len = [input read:buffer maxLength:1024];
if (len > 0)
{
NSMutableString *output = [[NSMutableString alloc]initWithBytes:buffer length:len encoding:NSUTF8StringEncoding]; NSLog(#"Received data--------------------%#", output);
}
}
}
break;
case NSStreamEventHasSpaceAvailable:
event = #"NSStreamEventHasSpaceAvailable";
break;
case NSStreamEventErrorOccurred:
event = #"NSStreamEventErrorOccurred";
//[self close];
break;
case NSStreamEventEndEncountered:
break; default:
event = #"NSStreamEventEndEncountered";
//[self close];
event = #"Unknown"; break;
}
NSLog(#"event------%#",event);
}
- (void)close
{
[input close];
[output close];
[input removeFromRunLoop:[NSRunLoop currentRunLoop]forMode:NSDefaultRunLoopMode];
[output removeFromRunLoop:[NSRunLoop currentRunLoop]forMode:NSDefaultRunLoopMode];
[input setDelegate:nil];
[output setDelegate:nil];
input = nil;
output = nil;
}
- (void)dataSending:(NSString*)data
{
if(output)
{
if(![output hasSpaceAvailable])
return;
NSData *_data=[data dataUsingEncoding:NSUTF8StringEncoding];
NSInteger data_len = [_data length];
uint8_t *readBytes = (uint8_t *)[_data bytes];
int byteIndex=0;
unsigned int len=0;
while (TRUE)
{
len = ((data_len - byteIndex >= 40960) ? 40960 : (data_len-byteIndex));
if(len==0)
break;
uint8_t buf[len];
(void)memcpy(buf, readBytes, len);
len = [output write:(const uint8_t *)buf maxLength:len];
byteIndex += len;
readBytes += len;
}
NSLog(#"Sent data----------------------%#",data);
}
}
I do call the mentioned functions through that code as a test, and nothing happens
- (IBAction)pumpchange:(id)sender {
[self connection];
[self open];
if ([self.pump backgroundImageForState:(UIControlStateNormal)]==[UIImage imageNamed:#"PumpOff.png"])
{
[self.pump setBackgroundImage:[UIImage imageNamed:#"PumpOn.png"] forState:(UIControlStateNormal)];
[self dataSending:#"pump_on"];
}
else //if ([self.pump backgroundImageForState:(UIControlStateNormal)]==[UIImage imageNamed:#"PumpOn.png"])
{
[self.pump setBackgroundImage:[UIImage imageNamed:#"PumpOff.png"] forState:(UIControlStateNormal)];
[self dataSending:#"pump_off"];
}
[self close];
}
Thanks in Advance
There seem to be some misunderstandings how format strings work, because
NSString *urlString = [NSString stringWithFormat:#"%.%.%.%", "192.168.3.120"];
just gives you the string #"...". Perhaps you meant
NSString *urlString = [NSString stringWithFormat:#"%d.%d.%d.%d", 192, 168, 3, 120];
or
NSString *urlString = [NSString stringWithFormat:#"%s", "192.168.3.120"];
But you don't need a format string at all:
NSString *urlString = #"192.168.3.120";

NSOutputStream not calling delegate's NSStreamEventHasSpaceAvailable

I have implemented socket by using input and output streams. The external architecture takes care of sending one request at a time to write.
However if any request does not return no HasBytesAvailable I need to remove that request from queue and inform about request timeout.
For all other requests, I am able to send/receive data correctly, but if any one of the request time outs then after that HasSpaceAvailable never gets called.
My code is as follows :
#implementation CCCommandSocket
#synthesize connectionTimeoutTimer;
#synthesize requestTimeoutTimer;
/*
* init
*
* #params
* ipAddress :ip address of camera socket
* portNumber :port address of camera socket
*
* #return
* Object of type Socket, which will send connection request to ipAddress,portNumber
*
*/
- (id)init
{
self = [super init];
if (self)
{
ip = #"192.168.42.1";
port = 7878;
[self performSelectorOnMainThread:#selector(connectToCamera) withObject:nil waitUntilDone:YES];
bytesReceivedCondition = [[NSCondition alloc] init];
requestCompletedCondition = [[NSCondition alloc] init];
requestReadyToProcess = [[NSCondition alloc] init];
isBytesReceived = false;
isRequestCompleted = false;
isRequestReadyToProcess = false;
responseString = [[NSString alloc] init];
openBracesCount = 0;
mutex = [[NSLock alloc] init];
}
return self;
}
pragma mark-
pragma establish socket communication.
/*
* connectToCamera
*
*/
- (void) connectToCamera
{
NSString *urlStr = ip;
if (![urlStr isEqualToString:#""])
{
NSURL *website = [NSURL URLWithString:urlStr];
if (!website)
{
NSString* messageString = [NSString stringWithFormat:#"%# is not a valid URL",website];
CCLog(LOG_ERROR, messageString);
return;
}
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(urlStr), port, &readStream, &writeStream);
//cast the CFStreams to NSStreams
inputStream = (__bridge_transfer NSInputStream *)readStream;
outputStream = (__bridge_transfer NSOutputStream *)writeStream;
//set the delegate
[inputStream setDelegate:self];
[outputStream setDelegate:self];
//schedule the stream on a run loop
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//open the stream
[inputStream open];
[outputStream open];
if(readStream==NULL)
{
CCLog(LOG_INFO, #"readstream NULL");
}
if(writeStream == NULL)
{
CCLog(LOG_INFO, #"writeStream NULL");
}
[self startConnectionTimeoutTimer];
}
}
pragma mark -
pragma getter methods
/*
* getIP
*
* #return
* Ip address to which socket is connected
*/
-(NSString *) getIP
{
return ip;
}
/*
* getPort
*
* #return
* Port number to which socket is connected
*/
-(int) getPort
{
return port;
}
pragma mark-
pragma Handle socket callbacks.
(void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode
{
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:stream];
[array addObject:[NSNumber numberWithInt:eventCode]];
[self performSelectorInBackground:#selector(myStream:) withObject:array];
}
(void)myStream:(NSMutableArray*) array
{
NSNumber *number = [array objectAtIndex:1];
int eventCode = [number intValue];
switch(eventCode)
{
case NSStreamEventErrorOccurred:
{
CCLog(LOG_ERROR, #"In Command Socket NSStreamEventErrorOccurred");
//[self disconnect];
//[[ErrorDetails getInstance] reportError:NSStreamEventErrorOccurred];
break;
}
//Read from stream
case NSStreamEventHasBytesAvailable:
{
CCLog(LOG_INFO, #"In Command Socket NSStreamEventHasBytesAvailable");
[self handleCommandPortDataReceived];
break;
}
//Write to stream
case NSStreamEventHasSpaceAvailable:
{
#synchronized(self)
{
[requestReadyToProcess lock];
while (isRequestReadyToProcess == false)
{
[requestReadyToProcess wait];
}
[requestReadyToProcess unlock];
CCLog(LOG_INFO,#"In Command Socket NSStreamEventHasSpaceAvailable");
#try
{
#synchronized(requestString)
{
if(requestString != nil)
{
if(outputStream != nil)
{
int dataSent;
uint8_t* data = (uint8_t *)[requestString cStringUsingEncoding:NSUTF8StringEncoding];
responseString = #"";
//[requestReadyToProcess lock];
isRequestReadyToProcess = false;
//[requestReadyToProcess signal];
dataSent = [outputStream write:data maxLength:strlen((char*)data)];
if(dataSent != -1)
{
NSString* message = [NSString stringWithFormat:#"Bytes written %d for request\n %#",dataSent, requestString];
CCLog(LOG_REQUEST, message);
requestString = nil;
isBytesReceived = false;
[bytesReceivedCondition lock];
while (isBytesReceived ==false)
{
[bytesReceivedCondition wait];
}
[requestCompletedCondition lock];
isRequestCompleted = true;
[requestCompletedCondition signal];
[requestCompletedCondition unlock];
[bytesReceivedCondition unlock];
}
else
{
CCLog(LOG_INFO, #"Command Socket : Request not sent (dataSent == -1)");
responseString = #"{ \"rval\": -104}";
CCLog(LOG_RESPONSE, responseString);
[self removeRequestFromQueue];
}
}
else
{
CCLog(LOG_INFO, #"in else :(outputStream != nil)");
}
}
}
}
#catch (NSException *e)
{
CCLog(LOG_WARNING, e.description);
}
}
break;
}
case NSStreamEventNone:
{
CCLog(LOG_INFO, #"In Command Socket NSStreamEventNone");
break;
}
case NSStreamEventOpenCompleted:
{
CCLog(LOG_INFO, #"In Command Socket NSStreamEventOpenCompleted");
[self stopConnectionTimeoutTimer];
break;
}
case NSStreamEventEndEncountered:
{
CCLog(LOG_INFO, #"Command Socket NSStreamEventEndEncountered");
[self disconnectWithNotification:YES];
break;
}
}
}
/*
* execute
*
* #param
* request :command to be sent over socket to camera
*
* #return
* responce :response received from camera
*
*/
-(NSString *) executeRequest :(NSString *)request
{
CCLog(LOG_INFO, #"Command Socket Executing request");
[self performSelectorOnMainThread:#selector(startRequestTimeoutTimer) withObject:nil waitUntilDone:NO];
isRequestCompleted = false;
requestString = request;
responseString = #"";
[requestReadyToProcess lock];
isRequestReadyToProcess = true;
[requestReadyToProcess signal];
[requestReadyToProcess unlock];
[requestCompletedCondition lock];
while (isRequestCompleted ==false)
{
[requestCompletedCondition wait];
}
CCLog(LOG_INFO, #"Command Socket Execute request : request completed");
[requestCompletedCondition unlock];
CCLog(LOG_RESPONSE, responseString);
return responseString;
}
pragma mark-
pragma Handle connection time out
// Call this when you initiate the connection
- (void)startConnectionTimeoutTimer
{
[self stopConnectionTimeoutTimer]; // Or make sure any existing timer is stopped before this method is called
NSTimeInterval interval = 10.0; // Measured in seconds, is a double
self.connectionTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:#selector(handleConnectionTimeout)
userInfo:nil
repeats:NO];
}
(void)handleConnectionTimeout
{
responseString = #"{ \"rval\": -103}";
CCLog(LOG_RESPONSE, responseString);
[self removeRequestFromQueue];
[self disconnectWithNotification:YES];
[self stopConnectionTimeoutTimer];
}
// Call this when you initiate the connection
- (void)startRequestTimeoutTimer
{
[self stopRequestTimeoutTimer]; // Or make sure any existing timer is stopped before this method is called
NSTimeInterval interval = 20.0; // Measured in seconds, is a double
self.requestTimeoutTimer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:#selector(handleRequestTimeout)
userInfo:nil
repeats:NO];
}
(void)handleRequestTimeout
{
responseString = #"{ \"rval\": -103}";
CCLog(LOG_RESPONSE, responseString);
[self connectToCamera];
[self stopRequestTimeoutTimer];
[self removeRequestFromQueue];
}
// Call this when you successfully connect
- (void)stopRequestTimeoutTimer
{
if (requestTimeoutTimer)
{
[requestTimeoutTimer invalidate];
requestTimeoutTimer = nil;
}
}
-(void) disconnectWithNotification:(BOOL)showNotification
{
CCLog(LOG_INFO, #"Socket Disconnected");
[inputStream close];
[inputStream setDelegate:nil];
[inputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
inputStream = nil;
[outputStream close];
[outputStream setDelegate:nil];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
outputStream = nil;
[[CCCore getInstance] disconnectWithNotification:showNotification];
}
// Call this when you successfully connect
- (void)stopConnectionTimeoutTimer
{
if (connectionTimeoutTimer)
{
[connectionTimeoutTimer invalidate];
connectionTimeoutTimer = nil;
}
if (requestTimeoutTimer)
{
[requestTimeoutTimer invalidate];
requestTimeoutTimer = nil;
}
}
-(void) handleCommandPortDataReceived
{
[mutex lock];
[self stopRequestTimeoutTimer];
#try
{
long size = 1024;
uint8_t buf[size];
unsigned int len = 0;
do
{
// read input stream into buffer
strcpy((char *)buf, "\0");
len = [inputStream read:buf maxLength:size];
//NSLog(#"Size = %ld Len = %d, Buf = %s",size, len, (char *)buf);
// Following code checks if we have received complete response by matching "{" and "}"
// from input stream. We continue to form response string unless braces are matched.
if (len > 0)
{
// Create nsdata from buffer
NSMutableData *_data = [[NSMutableData alloc] init];
[_data appendBytes:(const void *)buf length:len];
// create temporary string form nsdata
NSString* currentString = [[NSString alloc] initWithData:_data encoding:NSUTF8StringEncoding];
// check the occurances of { and } in current string
int currentOpeningBraceCount = [[currentString componentsSeparatedByString:#"{"] count] - 1;
int currentClosingBraceCount = [[currentString componentsSeparatedByString:#"}"] count] - 1;
openBracesCount = (openBracesCount + currentOpeningBraceCount) - currentClosingBraceCount;
responseString = [responseString stringByAppendingString:currentString];
// NSLog(#"Total:%d currentOpen:%d currentClose:%d\n\n",openBracesCount, currentOpeningBraceCount, currentClosingBraceCount);
// NSLog(#"Current String : %#\n\n",currentString);
// NSLog(#"Final String : %#",finalString);
// NSLog(#"+++++++++++++++++++++++++++++");
}
else
break;
} while (openBracesCount != 0);
NSRange range = [responseString rangeOfString:#"get_file_complete"];
if(range.location == NSNotFound)
{
//remove it from queue
[bytesReceivedCondition lock];
isBytesReceived = true;
[bytesReceivedCondition signal];
[bytesReceivedCondition unlock];
}
//responseString = #"";
}
#catch (NSException* e)
{
[self connectToCamera];
}
[mutex unlock];
}
-(void) removeRequestFromQueue
{
//remove it from queue
requestString = nil;
[requestReadyToProcess lock];
isRequestReadyToProcess = false;
[requestReadyToProcess unlock];
[requestCompletedCondition lock];
isRequestCompleted = true;
[requestCompletedCondition signal];
[requestCompletedCondition unlock];
}
#end
Which OS version are you trying this on?? I'm having the similar issue, in 10.7 and up it is all good, but on 10.6 and below I get the very same issue you are having I'm doing some debugging but so far have not come up with a good resolution yet.

Resources