This is a sibling question of Asynchronous IO with CFWriteStream. I'm using CFReadStreamScheduleWithRunLoop and CFReadSteamRead to do asynchronous IO. How can we safely retrieve all the date with blocking?
Let's say the actual size of a message was 1200 (but we don't know), and the size of my read buffer was 1024. A call to CFReadStreamRead will retrieve up to 1024 bytes of data, but since we don't know the size of the message, we should call CFReadStreamRead repeatedly. The problem is that since we don't know how much of data the stream socket has received, the CFReadStreamRead might block from the second call. How can we avoid this problem?
Thanks!
Call again CFReadStreamHasBytesAvailable() on your stream to see if it's still safe to read from it (or if the only way to know is to try).
I've added a sample code, that did the job for me. It's using CFReadStreamHasBytesAvailable(). It's important to check for the status of the stream, otherwise you might end up in an endless loop. My example also includes a timeout handling.
NSMutableData* bodyData = [NSMutableData dataWithCapacity:kHTTPReadStreamBufferSize];
NSDate* startTimeStamp = [NSDate date];
while (TRUE) {
if (CFReadStreamHasBytesAvailable(httpReadStream)) {
startTimeStamp = [NSDate date];
UInt8* streambuffer = malloc(kHTTPReadStreamBufferSize);
int readBytes = CFReadStreamRead (httpReadStream,streambuffer,kHTTPReadStreamBufferSize);
NSLog(#"Read: %d",readBytes);
[bodyData appendBytes:streambuffer length:readBytes];
free(streambuffer);
}
if (CFReadStreamGetStatus(httpReadStream) == kCFStreamStatusError) {
*error = (NSError*)CFReadStreamCopyError (httpReadStream);
if ([*error code] == 61) {
NSLog(#"Error occured: %d",[*error code]);
// connection refused
[PlusError errorForDomainWithCode:kPlusHostUnreachable errorDescription:NSLocalizedString(#"kPlusHostUnreachable",#"")
underlyingError:nil url:nil toError:error];
}
*responseHeader = nil;
*bodyContent = nil;
break;
}
if (CFReadStreamGetStatus(httpReadStream) == kCFStreamStatusAtEnd) {
NSLog(#"Stream reached end!");
*responseHeader = (CFHTTPMessageRef)CFReadStreamCopyProperty(httpReadStream, kCFStreamPropertyHTTPResponseHeader);
*error = nil;
break;
}
// timeout management
NSTimeInterval timeInterval = [startTimeStamp timeIntervalSinceNow]*-1;
if (timeInterval >= kHTTPReadTimeOutTimeInSeconds) {
[PlusError errorForDomainWithCode:kPlusResourceLoadingError errorDescription:NSLocalizedString(#"kPlusResourceLoadingError",#"")
underlyingError:nil url:nil toError:error];
break;
}
}
Related
I'm trying to send a message to another device using sockets and streams, using code found here:
http://mobileorchard.com/tutorial-networking-and-bonjour-on-iphone/
This works fine, and I can send a message from one device and have another device receive it and read it without problem. With larger messages, which include things such as NSData, it freezes up my UI while the message is sent, and so I want to run it on a background thread. My code looks the same, except with background threading implemented:
dispatch_queue_t queue = dispatch_queue_create("com.App.SendMessage", NULL);
dispatch_async(queue, ^{
NSMutableDictionary *mutable = [[NSMutableDictionary alloc] init];
[mutable setObject:#"Test" forKey:#"TestKey"];
[self.server sendMessage:mutable toService:service];
});
dispatch_release(queue);
And the messaging code looks like this:
NSData* rawPacket = [NSKeyedArchiver archivedDataWithRootObject:packet];
// Write header: lengh of raw packet
int packetLength = [rawPacket length];
[outgoingDataBuffer appendBytes:&packetLength length:sizeof(int)];
// Write body: encoded NSDictionary
[outgoingDataBuffer appendData:rawPacket];
// Try to write to stream
[self writeOutgoingBufferToStream];
- (void)writeOutgoingBufferToStream {
// Is connection open?
if ( !readStreamOpen || !writeStreamOpen ) {
// No, wait until everything is operational before pushing data through
return;
}
// Do we have anything to write?
if ( [outgoingDataBuffer length] == 0 ) {
return;
}
// Can stream take any data in?
if ( !CFWriteStreamCanAcceptBytes(writeStream) ) {
return;
}
// Write as much as we can
CFIndex writtenBytes = CFWriteStreamWrite(writeStream, [outgoingDataBuffer bytes], [outgoingDataBuffer length]);
if ( writtenBytes == -1 ) {
// Error occurred. Close everything up.
[self close];
[delegate connectionTerminated:self];
return;
}
NSRange range = {0, writtenBytes};
[outgoingDataBuffer replaceBytesInRange:range withBytes:NULL length:0];
}
It crashes with the error:
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[NSConcreteMutableData replaceBytesInRange:withBytes:length:]: range {0, 1855} exceeds data length 0'
Anybody know why this might be crashing on a background thread?
how can i clear an CFStream buffer?
Everytime i read from socket there is still data from an old request, i mean complete response to an old request not just a fragment of it.
Am i missing something ?
This is a function i use to initialize the connection:
-(void)openSocketConnection:(UInt32)port: (NSString *)host
{
NSString *hoststring = [[NSString alloc] initWithString:host];
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,(__bridge CFStringRef)hoststring ,
port,&_nnet_readStream,&_nnet_writeStream);
CFWriteStreamCanAcceptBytes(_nnet_writeStream);
CFWriteStreamSetProperty(_nnet_writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFReadStreamSetProperty(_nnet_readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
if(!CFWriteStreamOpen(_nnet_writeStream)) {
NSLog(#"Error Opening Write Socket");
}
if(!CFReadStreamOpen(_nnet_readStream)) {
NSLog(#"Error Opening Write Socket");
}
}
This is a function i use to read data from socket:
BOOL done = NO;
NSMutableString* result = [NSMutableString string];
while (!done)
{
if (CFReadStreamHasBytesAvailable(_nnet_readStream))
{
UInt8 buf[1024];
CFIndex bytesRead = CFReadStreamRead(_nnet_readStream, buf, 1024);
if (bytesRead < 0)
{
CFStreamError error = CFReadStreamGetError(_nnet_readStream);
NSLog(#"%#",error);
} else if (bytesRead == 0)
{
if (CFReadStreamGetStatus(_nnet_readStream) == kCFStreamStatusAtEnd)
{
done = YES;
}
} else
{
[result appendString:[[NSString alloc] initWithBytes:buf length:bytesRead encoding:NSUTF8StringEncoding]];
}
} else
{
done = YES;
}
}
You seem to assume that when you get data from the host on the other end it will always be available all in one go.
I don't know what the underlying protocol is, but in general it's entirely possible that the remote end will try and send 1600 bytes and that you'll get 1500 bytes but then have to wait a few milliseconds (or even seconds) for the next 100 bytes. Meanwhile CFReadStreamHasBytesAvailable would return false and so your code would set your done flag even though you haven't read all the data the server sent.
I am facing an issue in updating to core data using batch updating on a background thread. In the below code I am using main thread to notify user with a progress view and a string which calls a method in appdelegate. But if I am getting bad access error in the NSEntity line in random number of data where I have thousands of objects to update.if I uncomment the NSLOG i have indicated below there is no error, or if i comment the main thread no error, or if I dont update through batch instead if I use bulk update then also no error. IF i comment autorelease pool also the error is appearing. Will some body help me on this please.
Thanks in advance,
Cheers,
Shravan
NSAutoreleasePool *tempPool = [[NSAutoreleasePool alloc] init];
NSUInteger iterator = 1;
for (int i = 0; i < totalNo; i++) {
NSDictionary *alertResult = [[alertResultList objectAtIndex:i] retain];
if (alertResult == nil) {
continue;
}
//managedObjectContext = [appDelegate.managedObjectContext retain];
NSLog(#"Object Count:%u", [[managedObjectContext insertedObjects]count]);
AlertResult *result = (AlertResult *)[NSEntityDescription
insertNewObjectForEntityForName:#"AlertResult"
inManagedObjectContext:managedObjectContext];
[result setUserName:#"A"];
iterator++;
//When count reaches max update count we are saving and draining the pool and resetting the pool
if (iterator == kUploadCount) {
if ([self update] == NO) {
// If unable to update Alert results in the Core Data repository, return
// a custom status code.
statusCode = -1;
}
[managedObjectContext reset];
[tempPool drain];
tempPool = [[NSAutoreleasePool alloc] init];
iterator = 0;
}
//Adding code to change the display string for the lock view to notify user
float count1 = (float)(counter/totalAlerts);
counter = counter + 1.0f;
NSString *dispStr = [NSString stringWithFormat:#"%f",count1];//[NSString stringWithFormat:#"Loading %d out of %d alerts",(i+1),totalAlerts];
NSString *dispMess = [NSString stringWithFormat:#"Alerts %d of %d",(i+1),totalNo];
[self performSelectorOnMainThread:#selector(changeLockScreenMessageWith:) withObject:[NSArray arrayWithObjects:dispStr,dispMess, nil] waitUntilDone:YES];
//NSLog(#"count"); /* If I uncomment this line code runs fine */
[alertResult release];
alertResult = nil;
}
//If count is inbetween the update limit we are updating and we are draining the pool
if (iterator != 0) {
if ([self update] == NO) {
// If unable to update Alert results in the Core Data repository, return
// a custom status code.
statusCode = -1;
}
[managedObjectContext reset];
//[tempPool drain];
}
//Out side the previous method
- (BOOL)update {
NSError *error;
if (![managedObjectContext save:&error]) {
NSLog(#"%#", [error userInfo]);
return NO;
}
return YES;
}
The most likely cause of the kind of crash you're describing is using your managedObjectContext across threads. managedObjectContext is not thread-safe. You must create a new MOC for each thread. I assume managedObjectContext is an ivar; you should never access your ivars directly like this (except in init and dealloc). Always use an accessor to handle memory management for you.
The reason NSLog makes it crash is because NSLog dramatically changes the timing of this function, and you have a race condition.
All,
I have a server that has a tcp socket stream for communication. I need to get to that stream and read the initial data that it needs to send me.
My current code is as follows. To be honest, I'm going at this completely blind. I'm not sure if this is correct let alone the right thing for the job.
-(void) initNetworkCommunication
{
//input stream
NSInputStream *iStream;
NSURL *url = [url initWithString:#"192.168.17.1:2004"];
[iStream initWithURL:url];
[iStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[iStream open];
}
So from the way I see it, this code initializes the stream, but how do I read from the stream?
Thanks
There are two ways to get data from a stream: polling and using stream events.
Polling is simpler, but will block the thread it is running in. If you use this method, you don't need to perform the setDelegate: or scheduleInRunLoop:forMode: calls. Polling is performed by repeatedly calling read:maxLength:.
NSInteger result;
uint8_t buffer[BUFFER_LEN]; // BUFFER_LEN can be any positive integer
while((result = [iStream read:buffer maxLength:BUFFER_LEN]) != 0) {
if(result > 0) {
// buffer contains result bytes of data to be handled
} else {
// The stream had an error. You can get an NSError object using [iStream streamError]
}
}
// Either the stream ran out of data or there was an error
Using stream events requires setting the delegate and adding the stream to a run loop. Instead of blocking the thread, the stream will send a stream:handleEvent: message to its delegate when certain events occur, including when it receives data. The delegate can then retrieve the data from the stream. Here is an example stream:handleEvent: method:
- (void)stream:(NSInputStream *)iStream handleEvent:(NSStreamEvent)event {
BOOL shouldClose = NO;
switch(event) {
case NSStreamEventEndEncountered:
shouldClose = YES;
// If all data hasn't been read, fall through to the "has bytes" event
if(![iStream hasBytesAvailable]) break;
case NSStreamEventHasBytesAvailable: ; // We need a semicolon here before we can declare local variables
uint8_t *buffer;
NSUInteger length;
BOOL freeBuffer = NO;
// The stream has data. Try to get its internal buffer instead of creating one
if(![iStream getBuffer:&buffer length:&length]) {
// The stream couldn't provide its internal buffer. We have to make one ourselves
buffer = malloc(BUFFER_LEN * sizeof(uint8_t));
freeBuffer = YES;
NSInteger result = [iStream read:buffer maxLength:BUFFER_LEN];
if(result < 0) {
// error copying to buffer
break;
}
length = result;
}
// length bytes of data in buffer
if(freeBuffer) free(buffer);
break;
case NSStreamEventErrorOccurred:
// some other error
shouldClose = YES;
break;
}
if(shouldClose) [iStream close];
}
I came up with this, based on some other answers.
public enum StreamError: Error {
case Error(error: Error?, partialData: [UInt8])
}
extension InputStream {
public func readData(bufferSize: Int = 1024) throws -> Data {
var buffer = [UInt8](repeating: 0, count: bufferSize)
var data: [UInt8] = []
open()
while true {
let count = read(&buffer, maxLength: buffer.capacity)
guard count >= 0 else {
close()
throw StreamError.Error(error: streamError, partialData: data)
}
guard count != 0 else {
close()
return Data(bytes: data)
}
data.append(contentsOf: (buffer.prefix(count)))
}
}
}
i created a network Application to transfer text message between two devices through WiFi network.when i select a device to pair there is a delegate called "TCPServerAcceptCallBack" will trigger in receiver side and it will setup the input and output streams.
but it will take milliseconds to do the setup of streams.whenever i am sending a data from my device there is checking of [_outStream hasSpaceAvailable].i mentioned that instream and outstream setup in receiver side will take milliseconds .so first time the condition will false.i used while loop to stop doing send method before setup is done.but there is a problem that when execution in while loop there is a chance to connection lose in receiver side and also not confident with while loop.
next i created a sleep of 3 millisecond ,it working fine but there is chance of occur error when network is very slow.
i am planning to trigger a delegate from receiver side to sender device at the time of instream and outstream setup is done.so please tell me how can do it (or) provide me any other good way to solve this problem...
My Code...receiver side
case NSStreamEventHasBytesAvailable:
{
if (stream == _inStream) {
// read it in
unsigned int len = 0;
//here the length is assigning..i can send text files only after this below statement is executed.but it will take few minitus.
len = [_inStream read:buf maxLength:buffSize];
buf[len] = '\0';
if(!len) {
if ([stream streamStatus] != NSStreamStatusAtEnd)
NSLog(#"Failed reading data from peer");
} else {
NSString *message = [NSString stringWithUTF8String:(char *)buf];
//Display image from app bundle
NSString *path=[[NSBundle mainBundle] pathForResource:message ofType:#"png"];
if(path !=nil)
{
[imageView setImage:[UIImage imageWithContentsOfFile:path]];
}
else
{
UIImage *image = [UIImage imageNamed:#"no_image.png"];
[imageView setImage:image];
}
// here you do whatever you need with your received NSString *message
[txtUserInput setText:message];
}
}
break;
Sender part..
[- (void) sendText:(NSString *)string {
const uint8_t *message = (const uint8_t *)[string UTF8String];
//waiting up to outStream hasSpaceAvailable
while (![_outStream hasSpaceAvailable]) {
continue;
}
//once outStream hasSpaceAvailable it will send datas.
if (_outStream && [_outStream hasSpaceAvailable])
if([_outStream write:message maxLength:strlen((char *)message)] == -1)
NSLog(#"Failed sending data to peer");
}
#end
Don't use a while loop,that's just wasting CPU cycles. Do it in the callback.