Monday, February 25, 2008

AsyncSocket 4.3.4

We have been using the AsyncSocket library EXTENSIVELY here at deusty. We use it as the basis for our Open Source Embedded HTTP Server, and we also use it within our XMPP client, and within our STUNT (TCP NAT Traversal) code.

Over time, we've grown to love this little class, but that's not to say we didn't run into a few bugs. We've squashed a few, added a few features, and many comments. We're calling our latest version AsyncSocket 4.3.4 and you can download it here:
http://www.deusty.com/opensource/downloads/AsyncSocket4.3.4.zip

CHANGES IN VERSION 4.3.1:

Bugfix:
If user called acceptOnPort:0, then the OS would automatically pick a port for you. This is what is supposed to happen, except that it would pick a different port for IPv4 and IPv6. Added code to make sure both protocols are listening on the same port

Added comments in many places

Altered bits of code to match Apple's coding style guidelines

Renamed method "attachAcceptSockets" to "attachSocketsToRunLoop:error:"

CHANGES IN VERSION 4.3.2

Removed polling - it's not needed

Added another delegate method: onSocket:didReadPartialDataOfLength:tag:
Often, when using the AsyncSocket class, it was important to display a progress bar to the user. This was possible using Timers, and calling progressOfRead, but it wasn't the easiest solution. This delegate method will allow for automatic notification when using readToLength: or readToData:

CHANGES IN VERSION 4.3.3

Bugfix:
The methods connectedHost, connectedPort, localHost, and localPort all assumed IPv4 connection. In other words they all assumed theSocket was valid, causing a crash when the OS connected via IPv6. Updated all methods to properly check either theSocket or theSocket6.

Bugfix:
In the doStreamOpen method, there was an assumption that IPv4 was used and thus a valid theSocket variable. This was not always the case, causing this to fail:
CFSocketCopyPeerAddress(theSocket)
Fixed the problem by simply using the connectedHost and connectedPort methods.

Tweak:
Added safety check in addressPort: method:
if (cfaddr == NULL) return 0;

Tweak:
The kCFStreamPropertyShouldCloseNativeSocket was previously getting set in the configureStreamsAndReturnError: method. This may have been OK, but it would have caused a problem if the following sequence occurred:
A socket was accepted and doAcceptWithSocket: method was called,
This method then encoutered an error while attaching the streams to the run loop.
If this sequence happened, the BSD socket would not have been closed.
So I moved this into the createStreams... methods.

CHANGES IN VERSION 4.3.4

Bugfix:
In doReadTimeout: the code properly calls endCurrentWrite and then closes with an error.
In doWriteTimeout: the code was calling completeCurrentWrite and then closes with an error.
This resulted in the delegate being told that his write completed (when it didn't)
immediately prior to disconnecting with an error.

6 comments:

David said...

A couple more bugs for you:

1. in -acceptOnAddress, you shouldn't pass to [NSData dataWithBytesNoCopy:] a soon to be out-of-scope local.

2. in -doStreamOpen, there should be a return statement after [self closeWithError:err].

Deusty said...
This comment has been removed by the author.
Deusty said...

Excellent point. I've made both corrections, and I'm working on getting this code up on google code to make it easier for people to report bugs, issues, or feature requests.

Thomas said...

I am loving asyncsocket myself. I recently have been working on a client for the echoserver example. I had a question about what makes asyncsocket asynchronous. It's because it uses a run loop and is non-blocking right? Not because the sockets it creates have asynchronous callback? In the client I made, after I changed the echoserver to just a message server, I had to make a socketpair to get asynchronous callback, where both client and server could sent messages either way. So basically the question I have is I am right in my thinking and programming here, correct? Because unless I make the four-tuple sets, I can't send messages freely between the two. Thanks

Frank said...

Dear all!

Very nice class which solves several problems I was fighting with while developing a tool for data exchange between iPhone and Mac.

Two questions (of which I think I am just too stupid to find a solution by myself ... ;-) ):

1. When connectind TO EchoServer with port "12345" it is quoted and usable with this port. But EchoServer quotes back a connection to client with a random system port (i.g. 50210). How can I force EchoServer to use the same port for the way back to the client?

2. When initializing the connection the welcome Message is quoted back correctly to the client. But when sending a message to server it is only written to the View log and (as seen in debgger) echoed back to the client. BUT: On side of the client the "didReadData" method is NEVER called as seen in the debugger.

I would by very grateful for any assistance - thanks alot in advance!

Robbie Hanson said...

Frank,
Excellent questions that I'd be happy to answer for you. But there's not enough room to do so here. Jump over to the CocoaAsyncSocket mailing list and post the question so I can answer it in depth.