Thursday, March 20, 2008

Network Communications with NSFileHandle

Okay, well, because of the NDA, I can't post code samples about how to do things using the iPhone SDK yet, but I can post regular old Cocoa code without worrying about the Apple lawyers. If that code just happens to be usable on the iPhone also... oh, well.

With the fact that so many people have downloaded the SDK, I have to think at least seven or eight of those people do not have any prior experience using Objective-C or Cocoa. Those people will have a bit of a grokkng curve. I say "grokking" instead of "learning" because the syntax of Objective-C is straightforward and relatively easy to learn. However, the approach it takes is decidedly different from the application frameworks used with Visual Basic, C#, and Java; it takes a little while to really understand the way it all fits together and start working with the frameworks rather than against it.

One of the things that I'm sure many aspiring iPhone developers will want to do in their applications is to communicate with remote servers. Now, any of the old school C programmers who want to do so will have access to BSD sockets and can do network programming just as they have always done. Because Objective-C and Cocoa build on top of C, you always have available to you everything you do when writing procedural C.

But, often, there's a better, or at least, an easier way to do things using existing Cocoa classes. Unfortunately, what that "better way" would be for this isn't quite so straightforward. One option is to use CFNetwork. CF stands for Core Foundation, and when you see something beginning with CF in the world of Apple software development, you are looking at lower level C functions that are used by the higher level libraries like Cocoa and Carbon. CFNetwork underlies many of the higher-level classes capable of communicating with remote servers. When you use NSURLConnection, or instantiate an NSString using stringWithContentsOfURL:encoding:error:, you are using classes that sit on top of CFNetwork.

And CFNetwork is great - it is robust and written to work with an existing run loop mechanism, meaning you don't have to detach threads to keep from blocking. Unfortunately, CFNetwork is conceptually hard and using it in Cocoa creates confusing, hard to maintain classes. Coercing C callbacks into an instance of a Cocoa class works, and it works well, but it just doesn't feel right in the context of Objective-C/Cocoa.

Unfortunately, there is no generic Cocoa class for accessing remote servers. There are lots of special-purpose methods and classes that sit on top of CFNetwork and let you seamlessly get information from over the network using URLs. But what can you do if you want to implement your own protocol, or access a different kind of server?

Well, there are several third party classes that you could use for this, including Dustin Voss' free AsyncSockets and the open source SmallSockets project. These are both good options if you want to use them.

Someone on the Cocoa-Dev mailing list recently pointed out that NSFileHandle is capable of handling socket communications, but you have to actually create the socket the old fashioned way and feed it to NSFileHandle using initWithFileDescriptor: to do so. Thanks to the beauty of Objective-C categories, however, you only have to do it once if you write the code generically and put it in a category.

So, thanks to the help from several people on the Cocoa-Dev list, I present a category on NSFileHandle that lets you get a conection to a remote server. You can download the category right here.

Using this category is very straight forward. You can create a new NSFileHandle that is connected to a remote server like this:
NSFileHandle *fh = [NSFileHandle fileHandleToRemoteHost:@"" onPort:80];
To send a command and get the response, you need to create a method (probably on your controller class) that will be called whenever data is available on the read stream. That method might look something like this:
NSFileHandle *fh = [notification object];
NSMutableData *data = [fh availableData];
NSString *dataString = [[NSString alloc]
/* Process the data however you need to here */
[dataString release];
if (booleanVariableThatIndicatesIfWeExpectMoreData)
[fh readInBackgroundAndNotify];
Notice the last two lines. If we are expecting more data, we have to call readInBackgroundAndNotify again, or it won't continue to pull more data from the stream and we'll end up with only part of the server's response. If we don't expect more data, we don't have to call readInBackgroundAndNotify any more, and could also close the stream using the closeFile method of NSFileHandle. You could also leave the stream open and send more commands to it. 

If you call readInBackgroundAndNotify and there's nothing to read, your program won't be harmed, but your method won't get called again unless you send another message through to the server. So, if you have processing to do on the retrieved data once you get it all, you need to figure out in your code when you've received everything. Usually the protocol gives you a mechanism for doing that. NNTP, for example, sends a line with just a single period on it to tell you when it's done sending you data, but it leaves the connection open in case you want to send more commands through.

The nice thing here: no threads. Our application goes about its merry way, and calls us back when there's something for us to do. At this point, we've created the file handle, and we've created a method to process the responses from the server, so now we just need to send something to the remote server so that our process: method has something to wait for, which we do like this:
[fh writLine:@"HELO\r\n" withObserver:self andProcessWith:@selector(process:)];
That's all there is to it. 

As I said before, this is Cocoa code, not Cocoa Touch code, but since NSFileHandle is part of the Foundation framework and not the AppKit, it should (in theory) work unchanged in Cocoa Touch. I haven't yet tested it, but I'm going to, and as soon as the NDA lifts in June, I'll tell you if any changes are necessary to get it to work on the iPhone.


Seba said...


thanks for this!
I'm really new to Objective-C..and wow.. it's not easy :)

Can't just get this to work :(

The client connects to the server, but seems that my message is not being sent.. I have:

fh writeLine:@"..a long xml string.." withObserver:self andProcessWith:@selector(process:)];

and get an uncought exception:
process:]: unrecognized selector sent to class

thanks for your time and help!
Can't seem to find examples like this one anywhere!

Gaurav said...

I tried the code the connecting to the server is fine but when i send deta i do not get prpper responce from the server

Seba said...


Now I got it to send the message correctly, and the server gets in.. the problem.. as Gaurav says..
Im just getting the correct response from the server 1/3 times :(

Please, I really need this to work..

~1/3 i get the correct reply
~1/3 i only get the last line in the 5 line reply
~1/3 i don't get nothing :(

When I try to use the readInBackgroundAndNotify I just get a infinite loop with empty replies in the Log...

Have you Gaurav found a solution?

Thank you so much!

Maen said...

Hello there,

I keep running into a "unrecognized selector sent to instance" error. apparently Seba had the same problem. Could you help me? Thanx in advance.

katty said...

I have been some problem with my connection and I thought could be my network card, how ever i wasn´t sure. So, I decided to looking for information by internet and try to understand the network problem. I am happy because all the solution which advice this blog are very useful and interesting.
costa rica investment opportunities gave me another alternatives to do the best investment, but of course i always will need a good network.

Edwin said...

scrub m65 kamagra attorney lawyer body scrub field jacket lovegra marijuana attorney injury lawyer

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels

Hire iphone developer said...

They have experienced game developers who know each and every tricks and tweaks of the game development market.

hire iphone developer