I'm trying to receive broadcast UDP datagrams on iOS. I started to use the CocoaAsyncSocket library which seems pretty good. I'm using it like this:
m_socket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
[m_socket setIPv6Enabled:NO];
NSError* error = nil;
if (![m_socket enableBroadcast:YES error:&error])
return log_warn(#"Failed to enable broadcast: %#.", error.description);
if (![m_socket bindToPort:43211 error:&error])
return log_warn(#"Failed to bind to port: %#.", error.description);
if (![m_socket beginReceiving:&error])
return log_warn(#"Failed to begin receiving.");
The result is that the didReceiveData callback seems not to be called.
I therefore tried to use regular socket API and it seems I can get some bytes: I simply used this code.
I am curious about why the CocoaAsyncSocket code above is not working. I tried to compare the two approaches by reading the internals of CocoaAsyncSocket but I'm still not able to find which difference is causing no byte to arrive. Am I missing something in the above code using CocoaAsyncSocket?
By reading the code in CocoaAsyncSocket I see that the member holding the reference to the delegate is weak. Therefore the reason why no callback and no error were received was that the delegate was freed. Storing a strong reference to the object was sufficient to get the data.
Related
I have been noticing random crashes in my application that all seem to have to do with SignalR-ObjC. The crash can occur either shortly after the SignalR data is received or after several minutes of receiving data.
The error message is:
malloc: *** error for object 0x7fbae292ce00: incorrect checksum for freed object - object was probably modified after being freed.
*** set a breakpoint in malloc_error_break to debug
The breakpoint gets placed on this line in the SRChunkBuffer.m file:
[_buffer appendString:[[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding]];
If I comment out my code that uses SignalR-ObjC to invoke a server-side method to subscribe to groups, this crash does not occur:
for (NSString *groupName in combinedArray ){
[proxy invoke:#"Subscribe" withArgs:#[groupName] completionHandler:nil];
}
I have made sure all my cocoa pods / dependencies for SignalR-ObjC are up to date, removed my loop for registering for groups and instead manually registered for them one at a time, and yet I still get a crash randomly.
I see mentions of potentially using #synchronized but not sure how that fits into this case.
How can I stop this crash?
Resolved by replacing my [connection start] with:
[connection start:[[SRLongPollingTransport alloc] init]]
Had to import "SRLongPollingTransport.h" to make this work...
Source: https://github.com/DyKnow/SignalR-ObjC/issues/243
when i use GCDasyncsocket to get socket Message from the server(java),it works in the ios simulator(xcode 6,iphone6+); but when i use my iphone6+ connect it to testing, it will lose data when it receive big data,details below
i send a request to the server and get 3 answers
the first data the server answer length is 9 ,and receive 9
the second is 149 and receive 149
the last data the server answer is :2912,but the code shows 1448,
i get the 1448 in the below code:
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag {
NSLog(#"receive datas from method 1");
NSLog(#"Data length = %d",[data length]);
...
and when i go deep,the code in the gcdasyncsocket shows the same,code below
if (currentRead->bufferOwner)
{
// We created the buffer on behalf of the user.
// Trim our buffer to be the proper size.
[currentRead->buffer setLength:currentRead->bytesDone];
result = currentRead->buffer;
NSLog(#"if %lu %lu",(unsigned long)currentRead->bytesDone,(unsigned long)result.length);
}
it works well in the simulator,but goes wrong in the real phone;i also test in the iphone4s,it works well most times ~~~~~~~~~~
how strange!
can anyone give me some suggests?
i have got the reason; it is that the protocol split the message ;
i use the method to send the length of the message to tell the protocol how to split it ;
also ,there are some other solutions to solve it ;
and after that ,i also found that the protocol will combine some message,so,when you try to solve it ,you can not just think about the split sitution!
I am just getting started with socket programming on iOS and I am struggling to determine the use of the NSStreamEventHasSpaceAvailable event for NSOutputStreams.
On the one hand, Apple's official documentation (Listing 2) shows that in the -stream:handleEvent: delegate method, data should be written to the output buffer with -write:maxLength: message, passing data continually from a buffer, whenever the NSStreamEventHasSpaceAvailable event is received.
On the other hand, this tutorial from Ray Wenderlich and this iOS TCP socket example on GitHub ignore the NSStreamEventHasSpaceAvailable event altogether, and just go ahead and -write:maxLength: to the buffer whenever they need to (even ignoring -hasSpaceAvailable).
Thirdly, there is this example code which appears to do both...
My question is therefore, what is the correct way(s) to handle writing data to an NSOutputStream that is attached to a socket? And of what use is the NSStreamEventHasSpaceAvailable event code if it can (apparently) be ignored? It seems to me that there is either very fortunate UB happening (in examples 2 and 3), or there are several ways of sending data through a socket-based NSOutputStream...
You can write to a stream at any time, but for network streams, -write:maxLength: returns only until at least one byte has been written to the socket write buffer. Therefore, if the socket write buffer is full (e.g. because the other end of the connection does not read the data fast enough),
this will block the current thread. If you write from the main thread, this will block
the user interface.
The NSStreamEventHasSpaceAvailable event is signalled when you can write to the stream
without blocking. Writing only in response to that event avoids that the current thread
and possibly the user interface is blocked.
Alternatively, you can write to a network stream from a separate "writer thread".
After seeing #MartinR's answer, I re-read the Apple Docs and did some reading up on NSRunLoop events. The solution was not as trivial as I first thought and requires some extra buffering.
Conclusions
While the Ray Wenderlich example works, it is not optimal - as noted by #MartinR, if there is no room in the outgoing TCP window, the call to write:maxLength will block. The reason Ray Wenderlich's example does work is because the messages sent are small and infrequent, and given an error-free and large-bandwidth internet connection, it will 'probably' work. When you start dealing with (much) larger amounts of data being sent (much) more frequently however, the write:maxLength: calls could start to block and the App will start to stall...
For the NSStreamEventHasSpaceAvailable event, Apple's documentation has the following advice:
If the delegate receives an NSStreamEventHasSpaceAvailable event and does not write anything to the stream, it does not receive further space-available events from the run loop until the NSOutputStream object receives more bytes. ... ... You can have the delegate set a flag when it doesn’t write to the stream upon receiving an NSStreamEventHasSpaceAvailable event. Later, when your program has more bytes to write, it can check this flag and, if set, write to the output-stream instance directly.
It is therefore only 'guaranteed to be safe' to call write:maxLength: in two scenarios:
Inside the callback (on receipt of the NSStreamEventHasSpaceAvailable event).
Outside the callback if and only if we have already received the NSStreamEventHasSpaceAvailable but elected not to call write:maxLength: inside the callback itself (e.g. we had no data to actually write).
For scenario (2), we will not receive the callback again until write:maxLength is actually called directly - Apple suggest setting a flag inside the delegate callback (see above) to indicate when we are allowed to do this.
My solution was to use an additional level of buffering - adding an NSMutableArray as a data queue. My code for writing data to a socket looks like this (comments and error checking omitted for brevity, the currentDataOffset variable indicates how much of the 'current' NSData object we have sent):
// Public interface for sending data.
- (void)sendData:(NSData *)data {
[_dataWriteQueue insertObject:data atIndex:0];
if (flag_canSendDirectly) [self _sendData];
}
// NSStreamDelegate message
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
// ...
case NSStreamEventHasSpaceAvailable: {
[self _sendData];
break;
}
}
// Private
- (void)_sendData {
flag_canSendDirectly = NO;
NSData *data = [_dataWriteQueue lastObject];
if (data == nil) {
flag_canSendDirectly = YES;
return;
}
uint8_t *readBytes = (uint8_t *)[data bytes];
readBytes += currentDataOffset;
NSUInteger dataLength = [data length];
NSUInteger lengthOfDataToWrite = (dataLength - currentDataOffset >= 1024) ? 1024 : (dataLength - currentDataOffset);
NSInteger bytesWritten = [_outputStream write:readBytes maxLength:lengthOfDataToWrite];
currentDataOffset += bytesWritten;
if (bytesWritten > 0) {
self.currentDataOffset += bytesWritten;
if (self.currentDataOffset == dataLength) {
[self.dataWriteQueue removeLastObject];
self.currentDataOffset = 0;
}
}
}
I am new to objective-c, I am doing the same app for android and iPhone.
When in Java I use a try-catch block to catch a network exception and handle it.
In Objective-C I am using:
[[NSXMLParser alloc] initWithContentsOfURL:url]
So, How can I handle a network exception when parsing that input stream, so if network goes down or there's any problem I can notify user and keep my app working?.
If you are using NSXMLParser then use its delgate method..
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError
{
NSLog(#"Error = %#", parseError);
}
It is never a good idea to use the initWithContentsOfURL: methods.
You are better of retrieving the the data via NSURLConnection or some library like AFNetworking. This will allow you to handle the HTTP status code and network error.
For example the NSURLConnectionDelegate has a call back method – connection:didFailWithError: which will be called in case of an error.
You can then examen the error object to see what went wrong.
Before you call the URL You can check the internet connectivity.
I suggest you to look the below link
How to check for an active Internet connection on iOS or OSX?
And I suggest for parsing XML file use raptureXML Which is very simple.
https://github.com/ZaBlanc/RaptureXML
Basically I write an NSData object:
[asyncSocket writeData:JSONRequestData withTimeout:-1 tag:1];
Which when run, invokes the delegate
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
But for some reason, when the write goes to the buffer... it's not written out to the server.
When I stop my build, I guess the socket decides to write everything and the server ends up seeing it.
How would I fix this? There's no way to flush a CocoaAsyncSocket since it's not necessary...
I've tried adding line endings, like "/n" and all, but that doesn't do anything either.
This is based on user523234's suggestion.
Basically the NSData that you're writing out needs to have some kind of signal to tell the socket that no more data will be coming in.
I appended
[GCDAsyncSocket CRLFData];
to the NSData object
Note: to use the
appendData:
Method, you must convert NSData to NSMutableData.