Skip to content

Commit

Permalink
Tweaked some documentation, fixed some memory leaks in MRR mode, and …
Browse files Browse the repository at this point in the history
…changed where the CFSocketRef is created, and with which address family.
  • Loading branch information
AlanQuatermain committed Dec 20, 2011
1 parent c71a6f4 commit 649aa23
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 32 deletions.
4 changes: 0 additions & 4 deletions AQSocket/AQSocket.h
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -99,8 +99,6 @@ typedef void (^AQSocketEventHandler)(AQSocketEvent event, id info);
return the `error` parameter will be initialized to a new autoreleased NSError return the `error` parameter will be initialized to a new autoreleased NSError
instance containing detailed information. instance containing detailed information.
@param saddr A socket address structure containing a remote server address. @param saddr A socket address structure containing a remote server address.
@param port The port number to which to connect.
@param timeout The maximum time to wait for the connection to initialize.
@param error If this method returns `NO`, then on return this value contains @param error If this method returns `NO`, then on return this value contains
an NSError object detailing the error. an NSError object detailing the error.
@result `YES` if the connection initialization takes place, `NO` if something prevented it from doing so. @result `YES` if the connection initialization takes place, `NO` if something prevented it from doing so.
Expand All @@ -113,7 +111,6 @@ typedef void (^AQSocketEventHandler)(AQSocketEvent event, id info);
the caller to pass a DNS hostname to specify the destination for the connection. the caller to pass a DNS hostname to specify the destination for the connection.
@param hostname The DNS hostname of the server to which to attempt connection. @param hostname The DNS hostname of the server to which to attempt connection.
@param port The port number to which to connect. @param port The port number to which to connect.
@param timeout The maximum time to wait for the connection to initialize.
@param error If this method returns `NO`, then on return this value contains @param error If this method returns `NO`, then on return this value contains
an NSError object detailing the error. an NSError object detailing the error.
@result Returns `YES` if the async connection attempt began, `NO` otherwise. @result Returns `YES` if the async connection attempt began, `NO` otherwise.
Expand All @@ -128,7 +125,6 @@ typedef void (^AQSocketEventHandler)(AQSocketEvent event, id info);
for the connection. for the connection.
@param address The IPv4/6 address of the server to which to attempt connection. @param address The IPv4/6 address of the server to which to attempt connection.
@param port The port number to which to connect. @param port The port number to which to connect.
@param timeout The maximum time to wait for the connection to initialize.
@param error If this method returns `NO`, then on return this value contains @param error If this method returns `NO`, then on return this value contains
an NSError object detailing the error. an NSError object detailing the error.
@result Returns `YES` if the async connection attempt began, `NO` otherwise. @result Returns `YES` if the async connection attempt began, `NO` otherwise.
Expand Down
110 changes: 82 additions & 28 deletions AQSocket/AQSocket.m
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@


#define USING_MRR (!__has_feature(objc_arc)) #define USING_MRR (!__has_feature(objc_arc))


/// A sly little NSData class simplifying the dispatch_data_t -> NSData transition. // A sly little NSData class simplifying the dispatch_data_t -> NSData transition.
@interface _AQDispatchData : NSData @interface _AQDispatchData : NSData
{ {
dispatch_data_t _ddata; dispatch_data_t _ddata;
Expand Down Expand Up @@ -100,6 +100,8 @@ - (NSUInteger) length


@end @end


#pragma mark -

@interface AQSocket (CFSocketConnectionCallback) @interface AQSocket (CFSocketConnectionCallback)
- (void) connectedSuccessfully; - (void) connectedSuccessfully;
- (void) connectionFailedWithError: (SInt32) err; - (void) connectionFailedWithError: (SInt32) err;
Expand All @@ -119,10 +121,12 @@ static void _CFSocketConnectionCallBack(CFSocketRef s, CFSocketCallBackType type


static BOOL _SocketAddressFromString(NSString * addrStr, BOOL isNumeric, UInt16 port, struct sockaddr_storage * outAddr, NSError * __autoreleasing* outError) static BOOL _SocketAddressFromString(NSString * addrStr, BOOL isNumeric, UInt16 port, struct sockaddr_storage * outAddr, NSError * __autoreleasing* outError)
{ {
// Glags for getaddrinfo(): // Flags for getaddrinfo():
// AI_ADDRCONFIG: Only return IPv4 or IPv6 if the local host has configured //
// `AI_ADDRCONFIG`: Only return IPv4 or IPv6 if the local host has configured
// interfaces for those types. // interfaces for those types.
// AI_V4MAPPED: If no IPv6 addresses found, return an IPv4-mapped IPv6 //
// `AI_V4MAPPED`: If no IPv6 addresses found, return an IPv4-mapped IPv6
// address for any IPv4 addresses found. // address for any IPv4 addresses found.
int flags = AI_ADDRCONFIG|AI_V4MAPPED; int flags = AI_ADDRCONFIG|AI_V4MAPPED;


Expand Down Expand Up @@ -184,9 +188,14 @@ static BOOL _SocketAddressFromString(NSString * addrStr, BOOL isNumeric, UInt16
return ( 0 ); return ( 0 );
} }


#pragma mark -

@implementation AQSocket @implementation AQSocket
{ {
int _socketType;
int _socketProtocol;
CFSocketRef _socketRef; CFSocketRef _socketRef;
CFRunLoopSourceRef _socketRunloopSource;
dispatch_io_t _socketIO; dispatch_io_t _socketIO;
AQSocketReader * _socketReader; AQSocketReader * _socketReader;
dispatch_source_t _readWatcher; dispatch_source_t _readWatcher;
Expand All @@ -201,22 +210,8 @@ - (id) initWithSocketType: (int) type
if ( self == nil ) if ( self == nil )
return ( nil ); return ( nil );


CFSocketContext ctx = { _socketType = type;
.version = 0, _socketProtocol = (type == SOCK_STREAM ? IPPROTO_TCP : IPPROTO_UDP);
.info = (__bridge void *)self, // just a plain bridge cast
.retain = CFRetain,
.release = CFRelease,
.copyDescription = CFCopyDescription
};

_socketRef = CFSocketCreate(kCFAllocatorDefault, AF_INET6, type, (type == SOCK_DGRAM ? IPPROTO_UDP : IPPROTO_TCP), kCFSocketConnectCallBack, _CFSocketConnectionCallBack, &ctx);
if ( _socketRef == NULL )
{
#if USING_MRR
[self release];
#endif
return ( nil );
}


return ( self ); return ( self );
} }
Expand All @@ -235,15 +230,31 @@ - (void) dealloc
} }
if ( _socketIO != NULL ) if ( _socketIO != NULL )
{ {
// closing the socket IO also releases _socketRef // Closing the socket IO also releases _socketRef
dispatch_io_close(_socketIO, DISPATCH_IO_STOP); dispatch_io_close(_socketIO, DISPATCH_IO_STOP);
dispatch_release(_socketIO); dispatch_release(_socketIO);
} }
else if ( _socketRef != NULL ) else if ( _socketRef != NULL )
{ {
// not got around to initializing the dispatch IO channel // Not got around to initializing the dispatch IO channel yet,
// so we will have to release the socket ref manually.
CFRelease(_socketRef); CFRelease(_socketRef);
} }
if ( _socketRunloopSource != NULL )
{
// Ensure the source is no longer scheduled in any run loops.
CFRunLoopRemoveSource(CFRunLoopGetMain(), _socketRunloopSource, kCFRunLoopDefaultMode);
CFRunLoopRemoveSource(CFRunLoopGetMain(), _socketRunloopSource, (__bridge CFStringRef)
#if TARGET_OS_IPHONE
UITrackingRunLoopMode
#else
NSEventTrackingRunLoopMode
#endif
);

// Now we can safely release our reference to it.
CFRelease(_socketRunloopSource);
}
#if USING_MRR #if USING_MRR
[_socketReader release]; [_socketReader release];
#endif #endif
Expand Down Expand Up @@ -283,26 +294,47 @@ - (BOOL) connectToAddress: (struct sockaddr *) saddr
return ( NO ); return ( NO );
} }


// Create the socket with the appropriate socket family from the address
// structure.
CFSocketContext ctx = {
.version = 0,
.info = (__bridge void *)self, // just a plain bridge cast
.retain = CFRetain,
.release = CFRelease,
.copyDescription = CFCopyDescription
};

_socketRef = CFSocketCreate(kCFAllocatorDefault, saddr->sa_family, _socketType, _socketProtocol, kCFSocketConnectCallBack, _CFSocketConnectionCallBack, &ctx);
if ( _socketRef == NULL )
{
// We failed to create the socket, so build an error (if appropriate)
// and return `NO`.
if ( error != NULL )
{
// This error code is -1004.
*error = [NSError errorWithDomain: NSURLErrorDomain code: NSURLErrorCannotConnectToHost userInfo: nil];
}

return ( NO );
}

// Create a runloop source for the socket reference and bind it to a // Create a runloop source for the socket reference and bind it to a
// runloop that's guaranteed to be running some time in the future: the main // runloop that's guaranteed to be running some time in the future: the main
// one. // one.
CFRunLoopSourceRef rls = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socketRef, 0); _socketRunloopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, _socketRef, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), rls, kCFRunLoopDefaultMode); CFRunLoopAddSource(CFRunLoopGetMain(), _socketRunloopSource, kCFRunLoopDefaultMode);


// We also want to ensure that the connection callback fires during // We also want to ensure that the connection callback fires during
// input event tracking. There are different constants for this on iOS and // input event tracking. There are different constants for this on iOS and
// OS X, so I've used a compiler switch for that. // OS X, so I've used a compiler switch for that.
CFRunLoopAddSource(CFRunLoopGetMain(), rls, (__bridge CFStringRef) CFRunLoopAddSource(CFRunLoopGetMain(), _socketRunloopSource, (__bridge CFStringRef)
#if TARGET_OS_IPHONE #if TARGET_OS_IPHONE
UITrackingRunLoopMode UITrackingRunLoopMode
#else #else
NSEventTrackingRunLoopMode NSEventTrackingRunLoopMode
#endif #endif
); );


// This becomes owned by the runloop modes now.
CFRelease(rls);

// Start the connection process. // Start the connection process.
// Let's fire off the connection attempt and wait for the socket to become // Let's fire off the connection attempt and wait for the socket to become
// readable, which means the connection succeeded. The timeout value of // readable, which means the connection succeeded. The timeout value of
Expand All @@ -314,6 +346,10 @@ - (BOOL) connectToAddress: (struct sockaddr *) saddr
length: saddr->sa_len length: saddr->sa_len
freeWhenDone: NO]; freeWhenDone: NO];
CFSocketError err = CFSocketConnectToAddress(_socketRef, (__bridge CFDataRef)data, -1); CFSocketError err = CFSocketConnectToAddress(_socketRef, (__bridge CFDataRef)data, -1);
#if USING_MRR
[data release];
#endif

if ( err != kCFSocketSuccess ) if ( err != kCFSocketSuccess )
{ {
NSLog(@"Error connecting socket: %ld", err); NSLog(@"Error connecting socket: %ld", err);
Expand Down Expand Up @@ -447,6 +483,20 @@ - (void) connectedSuccessfully
// 2. The AQSocketReader object which will serve as our read buffer. // 2. The AQSocketReader object which will serve as our read buffer.
// 3. The dispatch source which will notify us of incoming data. // 3. The dispatch source which will notify us of incoming data.


// Before all that though, we'll remove the CFSocketRef from the runloop.
CFRunLoopRemoveSource(CFRunLoopGetMain(), _socketRunloopSource, kCFRunLoopDefaultMode);
CFRunLoopRemoveSource(CFRunLoopGetMain(), _socketRunloopSource, (__bridge CFStringRef)
#if TARGET_OS_IPHONE
UITrackingRunLoopMode
#else
NSEventTrackingRunLoopMode
#endif
);

// All done with this one now.
CFRelease(_socketRunloopSource);
_socketRunloopSource = NULL;

// First, the IO channel. We will have its cleanup handler release the // First, the IO channel. We will have its cleanup handler release the
// CFSocketRef for us; in other words, the IO channel now owns the // CFSocketRef for us; in other words, the IO channel now owns the
// CFSocketRef. // CFSocketRef.
Expand Down Expand Up @@ -518,6 +568,10 @@ - (void) connectionFailedWithError: (SInt32) err
{ {
NSError * info = [NSError errorWithDomain: NSPOSIXErrorDomain code: err userInfo: nil]; NSError * info = [NSError errorWithDomain: NSPOSIXErrorDomain code: err userInfo: nil];
self.eventHandler(AQSocketEventConnectionFailed, info); self.eventHandler(AQSocketEventConnectionFailed, info);

// Get rid of the socket now, since we might try to re-connect, which will
// create a new CFSocketRef.
CFRelease(_socketRef); _socketRef = NULL;
} }


@end @end

0 comments on commit 649aa23

Please sign in to comment.