I'm writing a messaging app that uses a NSStream to communicate with a server written in Python. The server works perfectly with a companion Python client. But when I connect to it with a NSStream, the NSInputStream doesn't seem to get any data. The NSOutputStream, however, works perfectly. I am opening the string like so:
-(void)openStream
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"tihmstar.dyndns.org", 80, &readStream, &writeStream);
inputStream = (__bridge_transfer NSInputStream *)readStream;
outputStream = (__bridge_transfer NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
[self auth];
[[NSNotificationCenter defaultCenter] postNotificationName:#"InitCompleted" object:nil];
}
The delegate method is as so:
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
{
NSLog(#"Handle Event - ");
switch (streamEvent)
{
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
NSLog(#"Bytes Available!");
if(theStream == inputStream)
{
NSLog(#"inputStream is ready.");
uint8_t buf[1024];
unsigned int len = 0;
len = [inputStream read:buf maxLength:1024];
if(len > 0)
{
NSMutableData* data=[[NSMutableData alloc] initWithLength:0];
[data appendBytes: (const void *)buf length:len];
NSString* string = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"Server said- %#", string);
[self messageReceived:[string lowercaseString]];
}
}
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
break;
case NSStreamEventEndEncountered:
NSLog(#"End Encountered");
break;
case NSStreamEventHasSpaceAvailable:
NSLog(#"Space Availible.");
break;
default:
NSLog(#"Unknown event- %u", streamEvent);
}
}
My problem is that case NSStreamEventHasBytesAvailable is never called, and therefore the message from the server is never being received. Anyone have any solutions for this? I've found some related questions here on StackOverflow, but none of them have been answered.
Thanks in advance.
I was just looking into the code. try to remove delegate of NSInputStream. I was looking into the another Using NSXMLParser initWithStream: no parser delegate methods received
which deals with similar situation.
I don't know exactly the answer for your question but I know exactly that you shouldn't call
[self auth];
after NSStream open right away. You have to wait for
NSStreamEventHasSpaceAvailable
event for your NSOutputStream and write data inside it afterwards only.
Related
I'm currently creating a chat app based off of Rey Wenderlich's chat app tutorial which is going great except I can't seem to reconnect without having to run the app from Xcode again.
It goes something like this: run server -> run app (At this point, everything is great) then I stop server -> run server again at which point my streams refuse to reconnect and keep giving me the erorr: Error Domain=NSPOSIXErrorDomain Code=61 "Connection refused".
Any ideas why this is?
Method to init streams:
- (void)initStreams {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)SERVER_IP, port, &readStream, &writeStream);
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
inputStream.delegate = self;
outputStream.delegate = self;
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
My delegate
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
NSString *streamName = #"Output Stream";
if (aStream == inputStream) {
streamName = #"Input Stream";
}
switch (eventCode) {
case NSStreamEventOpenCompleted:
NSLog(#"%# opened! (%lu)",streamName, eventCode);
[_delegate socketDidOpenSuccessfully];
break;
case NSStreamEventHasBytesAvailable:
[self readDataFromStream:aStream];
break;
case NSStreamEventErrorOccurred:
NSLog(#"Error: %#",[aStream streamError]);
[self resetStreams];
break;
case NSStreamEventEndEncountered:
NSLog(#"%# read ended! (%lu)",streamName, eventCode);
[self resetStreams];
break;
default:
NSLog(#"Unknown %#: %d",streamName, (int)eventCode);
break;
}
}
Reset streams Method:
- (void)resetStreams {
NSLog(#"Reseting!");
toSend = [NSMutableArray new];
_loginStatus = LTLoginStatusLoggedOut;
[inputStream close];
[outputStream close];
[self performSelector:#selector(initStreams) withObject:self afterDelay:2];
}
So after hours of frustration, I decided to just wrap it in another object and re instantiate that whenever it gets disconnected. Seems to work whereas re-initiating the stream does not.
I'm trying to dig deeper by using this example of an iPhone Chart Server and all is working as expected.
What I wanted to learn next is how to recover when the connection to the server is lost (for whatever reason) while the app is running.
First issue is that the app tries to open the input & output streams concurrently thus if I implement an alert, I get it two times. I managed to resolve this by closing the streams if there is an error.
The second is that if I'm another view controller and the connection is lost I can't seem to be able to recover it.I call the sendSocketMessage and if there is the error flat to try to use the initNetworkCommunication but I get a fatal error.
This 2nd issue is troubling me. I've added an All Exceptions breakpoint but got nothing. How I try to 'test' this is by making sure the server works and the app loads and connects. Then I shut-down the server, try a few clicks on the app and I get the alert. I switch-on the server and try to click again, I get the message sent to the server but then the app crashes with no info!
#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];
}
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...
}
- (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
{
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 error I get is this screen grab:
int main(int argc, char * argv[]) {
#autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
Thread 1: EXC_BAD_ACCESS(code=1, address=0x0)
Any advice?
First issue:
I would use a strategy that involves pairing your alerts to connection objects. Since there is no built-in connection object in the API for dealing with CF streams, you'll have to create your own. Typically, a useful connection object would have properties for the associated NSInputStream and NSOutputStream. This approach should eliminate the problem of getting two alerts, since you will be showing an alert for the one connection object, instead of it's associated IO streams. This also encapsulates properties and operations neatly into one connection object, which you can separate from your view controller. Another benefit to using a connection object is that you will have more flexibility in your view controllers' object graph, since you will be using the connection object through composition, instead of inheritance in view controller subclasses.
Second issue:
The main function is shown when you get the error, not because it is causing the problem, but rather it is the function at the end of the call stack--when an exception is thrown, the system exits each call in the stack until it is caught, or until it reaches the end of the call stack, which is typically the main function in Objective-C.
I would first go your breakpoints panel, and add a general breakpoint, by tapping on the + sign in the bottom left corner, and selecting Add Exception Breakpoint. This usually helps me, when it successfully shows the point in my code that initially threw an exception, instead of showing the main function, which isn't helpful. You don't need to edit this breakpoint once it is added. Sometimes it causes the debugger to stop unnecessarily, if there are other exceptions that are thrown and caught in the system. If this is the case, then you can simply disable the exception break point until you arrive at the point in question, and then manually re-enable the break point before initiating an operation.
Edit
Another approach that you can use, if the catch-all Exception break point fails, is to use an exception handler. You would typically apply this inside your application:didLaunchWithOptions: method, like this:
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
And, implement your handler to print out some useful information:
void uncaughtExceptionHandler(NSException *exception) {
debugLog(#"CRASH: %#", exception);
debugLog(#"Stack trace: %#", [exception callStackSymbols]);
}
I am using NSInputStream to upload media file to server in following way.
uploadInputStream = [[NSInputStream alloc] initWithFileAtPath:videoFilePath];
uploadInputStream.delegate = self;
[uploadInputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[uploadInputStream open];
And in NSStream delegate method stream:handleEvent: i am fetching chunk of media file and uploading to server.
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
switch (eventCode) {
case NSStreamEventOpenCompleted:
NSLog(#"Strem opened");
break;
case NSStreamEventHasBytesAvailable: {
uint8_t buf[1024*1024];
unsigned int len = 0;
len = [(NSInputStream *)aStream read:buf maxLength:1024*1024];
if(len)
{
#autoreleasepool {
NSMutableData *fileData = [NSMutableData data];
[fileData appendBytes:(const void *)buf length:len];
[self uploadVideo:fileData];
}
}
break;
}
case NSStreamEventHasSpaceAvailable:
break;
case NSStreamEventEndEncountered: {
break;
}
case NSStreamEventErrorOccurred:
break;
case NSStreamEventNone:
break;
default:
break;
}
}
So far so good and everything works fine in simulator. The issue is if i test this same code in real device (iPad-mini for now), it always crashing the application with EXC_BAD_ACCESS code=1 at strating of the delegate method stream:handleEvent: .
Anyone has any idea about this? Any help will be appreciated.
Thanks,
Jay Stepin.
Here is the issue. When my app starts up, I connect to server1 at port 5000. I send to data to server1. Server1 sends data back. Server1 close connection. NSStreamEventEndEncountered event occurs for inputStream. Then I connect to server2 at port 5001. I try to send data to server2, but the data ends up going to server1. Somehow the inputStream is connected at port 5001 and my outputStream isconnected at 5000. What am I doing wrong?
- (void) initNetworkCommunication: (uint32_t)port {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"localhost", port, &readStream, &writeStream);
inputStream = (NSInputStream *)readStream;
outputStream = (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) onClientConnectionLost {
if (atServer1 == YES) {
atServer1 = NO;
[self initNetworkCommunication: 5001];
}
if (atServer1 == NO) {
atServer1 = YES;
[self initNetworkCommunication: 5000];
}
}
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
switch (streamEvent) {
case NSStreamEventOpenCompleted:
NSLog(#"Stream opened");
break;
case NSStreamEventHasBytesAvailable:
if (theStream == inputStream) {
//handle packets...
}
break;
case NSStreamEventErrorOccurred:
NSLog(#"Can not connect to the host!");
break;
case NSStreamEventEndEncountered:
if (theStream == inputStream) {
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[theStream release];
theStream = nil;
[outputStream close];
[outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream release];
outputStream = nil;
[self onClientConnectionLost];
}
break;
default:
NSLog(#"Unknown event");
}
}
- (void) onClientConnectionLost {
if (atServer1 == YES) {
atServer1 = NO;
[self initNetworkCommunication: 5001];
}
else if (atServer1 == NO) {
atServer1 = YES;
[self initNetworkCommunication: 5000];
}
}
On your old code... when atServer1 = YES, it will execute the first if statement.... which sets atServer1 to NO... so second if statement is TRUE.. so it will also executes that..
I'm trying to connect to a server in a custom GCD queue. This is how I'm doing it.
- (void) initNetworkCommunication{
if(!self.connQueue){
self.connQueue = dispatch_queue_create("connection_queue", NULL);
}
dispatch_async(self.connQueue, ^(void) {
if(self.inputStream ==nil && self.outputStream ==nil) {
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
NSString *host= [[NSUserDefaults standardUserDefaults] objectForKey:#"ip_preference"];
NSNumber *portNum = [[NSUserDefaults standardUserDefaults] objectForKey:#"port_preference"];
int port = [portNum intValue];
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP);
self.inputStream = (__bridge NSInputStream *)readStream;
self.outputStream = (__bridge NSOutputStream *)writeStream;
[self.inputStream setDelegate:self];
[self.outputStream setDelegate:self];
[self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self.outputStream open];
[self.inputStream open];
} else {
NSLog(#"persistant connection alerady opened");
return;
}
});
}
Now, if I write this piece of code in dispatch_sync, it calls delegate function correctly.
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent
But, when I use dispatch_asynch, which is what I want to do, it does not call my delegate function.
From what I've read on SO so far, GCD queues have a runloop associated with them but these are not run by the system and we need to do so ourselves. I understand this in theory, but but how is it done. Dispatch sources associated with this somehow?
Thank You in advance.
Add this method after [self.inputStream open];
[[NSRunLoop currentRunLoop]run];
This puts the receiver into a permanent loop, during which time it processes data from all attached input sources.
See apple docs about RunLoops
The other way let run on dispatch_get_main_queue when use dispatch_async();