I try to open a TCP stream to speak to a device with a cocoa app.
I searched the web and found that there is some possibilities to do that but i'm a little bit stuck.
I decided to use the NSStream way (because it's referenced in cocoa-touch, will be usefull if i want to port my app to iPhone i presume), so here is my code:
#implementation AppDelegate
- (IBAction)connect:(id)sender {
[NSStream getStreamsToHost:"192.168.1.4" port:23 inputStream:&inputStream outputStream:&outputStream];
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
}
// Both streams call this when events happen
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
{
if (aStream == inputStream) {
[self handleInputStreamEvent:eventCode];
} else if (aStream == outputStream) {
[self handleOutputStreamEvent:eventCode];
}
}
- (void)handleInputStreamEvent:(NSStreamEvent)eventCode
{
switch (eventCode) {
case NSStreamEventHasBytesAvailable:[self readBytes];
break;
case NSStreamEventOpenCompleted:
// Do Something
break;
default:
case NSStreamEventErrorOccurred:
NSLog(#"An error occurred on the input stream.");
break;
}
}
So, when I click on my connect button, it is supposed to open the stream to my host and make my 2 objects (inputstream and outputstream)
The first step I would like to reach is to have the inputStream in a NSTextView and know if the host has been reached or not... but i'm still stuck :(
If someone can light my way, it would be nice! I'm new on Stack Overflow and I'll be glad to help the community on somethings that i know much! :)
I updated my code and it seems that the light is coming, slowly but it's coming :)
I made a stream to a telnet server. I got the "hello" in a texview.
Now, I would like to send the user & password to be able to send commands to the server, but here is my "send user & pass" button code:
- (IBAction)sendusername:(id)sender {
NSString * usernameMsg = [NSString stringWithFormat:#"user #", [usernameField stringValue]];
NSData * usertosend = [[NSData alloc] initWithData:[usernameMsg dataUsingEncoding:NSUTF8StringEncoding]];
[outputStream write:[usertosend bytes] maxLength:[usertosend length]];
}
Follow my searchs, the server should respond me a thing like "user +ok" but nothing...
2 stranges things:
- If I open the socket to a FTP server of SSH server, I've always the "hello" response without problem. But in telnet, 90% of connections respond me a strange hello like this: "ÿýÿýÿûÿû", why?
When I send the user, nothing happen, only an unrocognized event from the handleEvent...
I can suggest you to look at https://github.com/robbiehanson/CocoaAsyncSocket/, a nice Objective-C wrapper for BSD sockets. It allows you to handle the send-receive interactions on the event loop in nice and clean callback manner (it even handles custom "message terminating" symbols for you allowing to concentrate more on actual packet handling rather then on combining and splitting the stuff you receive from wire).
Related
I'm learning Cocoa...
I tried different way to do that but I'm still in the black...
I have this method in my implementation:
- (void)closeStream:(NSStream *)theStream {
[theStream close];
[theStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
How can I call it from an IBAction in my #synthetize?
- (IBAction)connect:(id)sender {
if ([[connectNOK stringValue] isEqualToString:#"Disconnected"]) {
[connectButton setTitle:#"Disconnect"];
NSString * hostFromField = [hostField stringValue];
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)hostFromField, [portField intValue], &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];
} else {
[connectButton setTitle:#"Connect"];
// I want to call this method here
}
}
If the closeStream: method is defined in the same class than the connect: method, you'll have to use:
[ self closeStream: someStream ];
Where someStream is the NSStream object you need to pass.
The self keyword refers to the current instance of the class.
If you don't know that, or what it means, I suggest you take a look at the Objective-C language basics before trying to do/code anything, and maybe after, the complete language reference.
EDIT:
I can see in your code that your connect: method «toggles» the connection based on the value of a button label.
This is not a very good design, for you to know, but you'll have other problems here.
I guess you want to close the input and output streams, if necessary.
The problem is, when the connect method is called a second time, the inputStream and outputStream variables are not accessible anymore, as they are local variables.
You probably need to store them as instance variables instead, so you can refer to them later on.
Once again, it seems you really should start by reading some documentation about programming principles, as well as some Object-Oriented programming principles.
Don't try to go too fast. Knowledge is the key to everything, so start by reading the documentation I mentioned before.
i'm trying to establish an FTP connection within an app. i want to upload several files to a FTP server, all files in one directory. So at first i want to create the remote directory.
- (void) createRemoteDir {
NSURL *destinationDirURL = [NSURL URLWithString: uploadDir];
CFWriteStreamRef writeStreamRef = CFWriteStreamCreateWithFTPURL(NULL, (__bridge CFURLRef) destinationDirURL);
assert(writeStreamRef != NULL);
ftpStream = (__bridge_transfer NSOutputStream *) writeStreamRef;
BOOL success = [ftpStream setProperty: ftpUser forKey: (id)kCFStreamPropertyFTPUserName];
if (success) {
NSLog(#"\tsuccessfully set the user name");
}
success = [ftpStream setProperty: ftpPass forKey: (id)kCFStreamPropertyFTPPassword];
if (success) {
NSLog(#"\tsuccessfully set the password");
}
ftpStream.delegate = self;
[ftpStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
// open stream
[ftpStream open];
}
This code doesn't work when executed in a background thread using the following call:
[self performSelectorInBackground: #selector(createRemoteDir) withObject: nil];
my guess is that the (background-threads) runloop isn't running?
If i send the message inside the main thread the uploading just works fine:
[self createRemoteDir];
as the runloop of the main thread is up and running.
but fairly large files are going to be uploaded; so i want to put that workload in a background thread.
but how and where do i set up the NSRunLoop, so that the whole uploading happens in a background thread? Apples documentation on NSRunLoops (especially how to start them without using a timer/input source, as in this case) didn't help me out.
I found/created a solution that at least works for me.
with the above method (createRemoteDir), the following code applied and worked for me:
NSError *error;
createdDirectory = FALSE;
/*
only 'prepares' the stream for upload
- doesn't actually upload anything until the runloop of this background thread is run
*/
[self createRemoteDir];
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
do {
if(![currentRunLoop runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]]) {
// log error if the runloop invocation failed
error = [[NSError alloc] initWithDomain: #"org.mJae.FTPUploadTrial"
code: 23
userInfo: nil];
}
} while (!createdDirectory && !error);
// close stream, remove from runloop
[ftpStream close];
[ftpStream removeFromRunLoop: [NSRunLoop currentRunLoop] forMode: NSDefaultRunLoopMode];
if (error) {
// handle error
}
It runs in a background thread and creates the directory on the ftp server.
I like it more than other examples where runloops are only run for an assumed small interval, say 1second.
[NSDate distantFuture]
is a date in the futur (several centuries, according to Apples documentation). But that's good as the "break-condition" is handled by my class property createdDirectory - or the occurance of an error while starting the runloop.
I can't explain why it works without me explicitly attaching an input source to the runloop (NSTimer or NSPort), but my guess is, it is sufficient that the NSOutputStream is scheduled in the runloop of the background thread (see createRemoteDir).
You could also try to use a dispatch_async call to perform your createRemoteDir in the background. It's much simpler to use and you won't have to worry about managing extra threads.
Here's what the code would look like:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self createRemoteDir];
});
I wrote a quick objective-C method that uses Amazon's AWS iOS SDK to synchronously download a file from my Amazon S3 Bucket in my iPad app. This is an enterprise app, and I am using reachability to detect WiFi before allowing synchronization with S3. It is working fine for short downloads (those in kilobytes), but with files that are around 20-30 megs, it will continue to download into the stream and the file will continue growing. I've not let it go to see if it will eventually stop/crash, but I've watched a file that was 30 megs go past 90 megs in my iOS Simulator. I've read into several cold threads where some have experienced the same and I really need an answer.
Here is my method...
- (void)retrieveRemoteFile:(NSString *)fileName {
NSString *destinationFileName = [NSString stringWithFormat:#"%#%#",[self getBucketDirectory],fileName];
AmazonS3Client *s3 = [[[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY] autorelease];
NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:destinationFileName append:NO];
[stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[stream open];
S3GetObjectRequest *request = [[S3GetObjectRequest alloc] initWithKey:fileName withBucket:BUCKET];
request.outputStream = stream;
[s3 getObject:request];
[stream close];
[stream release];
[request release];
}
Re-evaluating the situation, I'd really like to get my method using an ASynchronous Request and use a S3RequestDelegate object to help me update the bytesIn as it's downloading. In their archive, there is a sample in S3AsyncViewController that should show how to do what I want. I've added S3RequestDelegate.h/.m into my project, and implemented a S3RequestDelegate in my .h like this...
#import "S3RequestDelegate.h"
... {
S3RequestDelegate *s3Delegate;
}
I've altered my retrieveRemoteFile method to look a little like this (it's changed all day and I haven't gotten anywhere)
- (void)retrieveRemoteFile:(NSString *)fileName {
NSString *destinationFileName = [NSString stringWithFormat:#"%#%#",[self getBucketDirectory],fileName];
AmazonS3Client *s3 = [[[AmazonS3Client alloc] initWithAccessKey:ACCESS_KEY_ID withSecretKey:SECRET_KEY] autorelease];
NSOutputStream *stream = [[NSOutputStream alloc] initToFileAtPath:destinationFileName append:NO];
//[stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
//[stream open];
S3GetObjectRequest *request = [[S3GetObjectRequest alloc] initWithKey:fileName withBucket:BUCKET];
request.outputStream = stream;
s3Delegate = [[S3RequestDelegate alloc] init];
[request setDelegate:s3Delegate];
[s3 getObject:request];
//[stream close];
//[stream release];
//[request release];
}
As you can see, I've set the S3GetObjectRequest delegate with setDelegate to my S3RequestDelegate pointer s3Delegate. I've added breakpoints in all of the delegate methods of the S3RequestDelegate object, but none of them are executing. In looking for a received file on my simulator, nothing is even getting downloaded now.
The sample makes it look like all you need to do is set a delegate to make it asynchronous. It also makes it look like you don't need to manage the stream object, and whether you do or not, nothing gets downloaded. I'm setting the delegate and it's never running any of the delegate methods; didReceiveResponse, didCompleteWithResponse, didReceiveData, didSendData, totalBytesExpectedToWrite, didFailWithError or didFailWithServiceException.
I was setting up my request to run in an OperationsQueue. Removed this code and kick off the Asynchronous transfer on the main thread and the delegate functions handle transferring the next file in the array. I've incorporated a status bar and cancel button and it worked out great.
I created a NSInputStream to load content from a file(IOS):
NSString* fileName = [[NSBundle mainBundle] pathForResource:#"resource" ofType:#".dat"];
NSInputStream* dataStream = [NSInputStream inputStreamWithFileAtPath:fileName];
if (dataStream == nil) {
NSLog(#"load asset failed");
return;
}
[dataStream setDelegate:self];
[dataStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
[dataStream open];
Then, add event handler:
- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)eventCode {
switch(eventCode) {
case NSStreamEventEndEncountered: {
[stream removeFromRunLoop:[NSRunLoop currentRunLoop]
forMode:NSDefaultRunLoopMode];
break;
}
}
}
I want to catch the event:NSStreamEventEndEncountered, but not happend. I can only catch NSStreamEventOpenCompleted and NSStreamEventHasBytesAvailable.
Anything wrong? Thanks for any help!
I can't see anything wrong with the code you've posted. Make sure that when you're finished with the stream that you are closing it yourself rather than simply relying on getting an NSStreamEventEndEncountered notification, which you can do simply with something like this:
- (void) disconnect {
// Close all open streams
[inputStream close];
[outputStream close];
}
You'll usually only get NSStreamEventEndEncountered if the connection is closed by the other end of the stream, which depending on what you're doing may be beyond your control.
I just ran in to this. Replace NSStreamEventEndEncountered with 4 in the switch/case statement.
NSStreamEventEndEncountered as an NSStream enum doesn't end up being caught in a case statement.
None of these answers are correct. To trigger the NSStreamEventEndEncountered event, you must attempt to read data from the input stream when there is no data to read (in other words, when the paired output stream stops writing data).
So I'm using Apple's PictureSharing/PictureSharingBrowser samples to send and receive data. This uses an NSFileHandle on the server side to send a picture using NSFileHandle's writeData method.
NSFileHandle * incomingConnection = [[aNotification userInfo] objectForKey:NSFileHandleNotificationFileHandleItem];
[[aNotification object] acceptConnectionInBackgroundAndNotify];
[incomingConnection writeData:dataToWrite];
[incomingConnection closeFile];
This seems to work fine until I want to send large amounts of data (in this case 1MB worth of data). When I attempt this, the application hangs while executing the writeData method. The client doesn't even begin reading the data, it simply opens the connection, but nothing happens. (it's supposed to read the data chunk by chunk, while the server sends all teh data at once).
I'm guessing some deadlock is occurring somewhere, but i'm not sure where. I tried to look for an async. way of writing the data chuck by chuck with NSFileHandle, but i could not find such a way.
Any guidance would help!
I missed one step basically... in NSNetServiceBrowser's netServiceBrowser: didFindService:( moreComing: delegate method, instead of me simply trying to connect to every incoming service, I instead (as the doc says :) ) retain the service, set the delegate for that found service, and attempt to resolve the service.
I am then able to open a stream to the resolved service in *- (void)netServiceDidResolveAddress:(NSNetService )sender which is NSNetservice's delegate method.
- (void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing {
[aNetService retain];
[aNetService setDelegate:self];
[aNetService resolveWithTimeout:5.0];
}
- (void)netServiceDidResolveAddress:(NSNetService *)service{
NSInputStream * istream;
[sender getInputStream:&istream outputStream:nil];
[istream retain];
[istream setDelegate:self];
[istream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[istream open];
[service release];
}
//... NSStreamDelegate method to retrieve the data via the stream.