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?
Related
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.
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.
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;
}
}
I'm a bit new to iPhone development, so be gentle! I'm supporting an app which loads a wav file from a URL file-stream, and plays it back through an AudioQueue.
We run a continual loop in another thread, and stop the Queue if we detect that it has no buffers in use, and the input FileStream has reached its end. In turn, we detect if the FileStream has ended within the waitForDataInBackgroundAndNotify callback on the NSFileHandleDataAvailableNotification for the stream, by checking whether the availableData has length 0.
This works under iOS 3.0 - we get a notification of 0 available data at the end of the file - but on iOS 4.0, we don't seem to receive the callback at file end. This happens on an OS 4.0 device, regardless of the target OS version.
Has the API changed between the two versions? How can I detect the end of the file now?
Hopefully-relevant code:
data-available callback:
- (void)readFileData:(NSNotification *)notification
{
#try
{
NSData *data = [[notification object] availableData];
if ([data length] == 0 && self.audioQueueState != AQS_END)
{
/***********************************************************************/
/* We've hit the end of the data but it's possible that more may be */
/* appended to the file (if we're still downloading it) so we need to */
/* wait for the availability of more data. */
/***********************************************************************/
[self setFileStreamerState:FSS_END];
[[notification object] waitForDataInBackgroundAndNotify];
}
else if (self.audioQueueState == AQS_END)
{
TRC_DBG(#"ignore read data as ending");
}
else
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
TRC_DBG(#"Read %d bytes", [data length]);
[self setFileStreamerState:FSS_DATA];
if (discontinuous)
{
TRC_DBG(#"AudioFileStreamParseBytes %d bytes, discontinuous", [data length]);
err = AudioFileStreamParseBytes(audioFileStream, [data length], [data bytes], kAudioFileStreamParseFlag_Discontinuity);
discontinuous = NO;
}
else
{
TRC_DBG(#"AudioFileStreamParseBytes %d bytes, continuous", [data length]);
err = AudioFileStreamParseBytes(audioFileStream, [data length], [data bytes], 0);
}
/***********************************************************************/
/* If error then get out, otherwise wait again for more data. */
/***********************************************************************/
if (err != 0)
{
[self failWithErrorCode:AS_FILE_STREAM_PARSE_BYTES_FAILED];
}
else
{
[[notification object] waitForDataInBackgroundAndNotify];
}
[pool release];
}
}
#catch (NSException *exception)
{
TRC_ERR(#"Exception: %#", exception);
TRC_ERR(#"Exception reason: %#", [exception reason]);
//[self failWithErrorCode:AS_FILE_AVAILABLE_DATA_FAILED];
}
}