// A simple (TCP) socket server and a buffered socket connection // (C)2007 Simon Urbanek // Dual-license: GPL v2 and AT&T Proprietary // // this implementation is based on regular unix sockets #import "TCPAsyncConnection.h" #define kDefaultLineBufferSize 1024 #include #include #include #include #include #include #include /*-- arguments to _asyncConnect: thread --*/ @interface AsyncConnArgs_ : NSObject { NSHost *host; int port; } - (id) initWithHost: (NSHost*) aHost port: (int) aPort; - (int) port; - (NSHost*) host; @end @implementation AsyncConnArgs_ - (id) initWithHost: (NSHost*) aHost port: (int) aPort { self = [super init]; if (self) { host = [aHost retain]; port = aPort; } return self; } - (int) port { return port; } - (NSHost*) host { return host; } - (void) dealloc { [host release]; [super dealloc]; } @end #pragma mark -- TCPAsyncConnection implementation -- @implementation TCPAsyncConnection - (id) init { self = [super init]; if (self != nil) { asyncSendData = nil; asyncLock = nil; delegate = nil; } return self; } - (id) initWithSocket: (int) socketHandle connected: (BOOL) isConnected { self = [super initWithSocket:socketHandle connected:isConnected]; if (self) { asyncSendData = nil; asyncLock = nil; delegate = nil; } return self; } - (void) dealloc { #ifdef TALK_DEBUG NSLog(@"TCPAsyncConnection %@ - dealloc"); #endif if (asyncLock) [asyncLock release]; if (asyncSendData) [asyncSendData release]; if (delegate) [delegate release]; [super dealloc]; } - (void) _asyncConnect: (AsyncConnArgs_*) args { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; #ifdef TALK_DEBUG NSLog(@"TCPAsyncConnection _asyncConnect:"); #endif [[NSThread currentThread] setName:@"async-connect"]; NSHost *host = [[args host] retain]; int port = [args port]; asyncLock = [[NSLock alloc] init]; struct hostent *haddr; if(!(haddr = gethostbyname([[host address] UTF8String]))) { if (lastError) [lastError release]; #ifdef TALK_DEBUG NSLog(@" _asyncConnect: host lookup failed"); #endif lastError = [errorFromSystem() retain]; [host release]; if (delegate && [delegate respondsToSelector:@selector(connectionFailed:)]) [delegate performSelectorOnMainThread:@selector(connectionFailed:) withObject:self waitUntilDone:NO]; [pool release]; [self close]; return; } [host release]; #ifdef TALK_DEBUG NSLog(@" _asyncConnect: host lookup succeeded, connecting"); #endif struct sockaddr_in sa; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; sa.sin_port = htons(port); sa.sin_addr.s_addr = *((unsigned long *) haddr->h_addr); if(connect(sock, (struct sockaddr *)&sa, sizeof(sa)) == -1) { if (lastError) [lastError release]; #ifdef TALK_DEBUG NSLog(@" _asyncConnect: connect failed"); #endif lastError = [errorFromSystem() retain]; if (delegate && [delegate respondsToSelector:@selector(connectionFailed:)]) [delegate performSelectorOnMainThread:@selector(connectionFailed:) withObject:self waitUntilDone:NO]; [self close]; [pool release]; return; } #ifdef TALK_DEBUG NSLog(@" _asyncConnect: connected"); #endif connected = YES; [asyncLock lock]; if (asyncSendData) { /* if there are any unsent data, process them */ #ifdef TALK_DEBUG NSLog(@" _asyncConnect: asynchronous data are present, sending"); #endif NSMutableData *data = asyncSendData; asyncSendData = nil; [self writeData:data]; [data release]; } [asyncLock unlock]; if (delegate && [delegate respondsToSelector:@selector(connectionDidConnect:)]) [delegate performSelectorOnMainThread:@selector(connectionDidConnect:) withObject:self waitUntilDone:NO]; [pool release]; #ifdef TALK_DEBUG NSLog(@" _asyncConnect: thread finished"); #endif } + (id) connectAsynchronouslyToHost: (NSHost*) host port: (int) port delegate:(id) aDelegate { TCPAsyncConnection *conn = [[TCPAsyncConnection alloc] init]; if (aDelegate) [conn setDelegate:aDelegate]; NSError *err = [conn connectAsynchronouslyToHost:host port:port]; if (err) { [conn release]; return nil; } return [conn autorelease]; } - (void) setDelegate: (id) aDelegate { if (delegate) [delegate release]; delegate = aDelegate; if (delegate) [delegate retain]; } - (id) delegate { return delegate; } - (NSError*) connectAsynchronouslyToHost: (NSHost*) host port: (int) port { #ifdef TALK_DEBUG NSLog(@"BufferedTCPConnection connectAsynchronouslyToHost: %@ port: %d", [host name], port); #endif AsyncConnArgs_ *args = [[AsyncConnArgs_ alloc] initWithHost:host port:port]; asyncSendData = [[NSMutableData alloc] initWithCapacity:1024]; [NSThread detachNewThreadSelector:@selector(_asyncConnect:) toTarget:self withObject:args]; [args release]; return nil; } - (NSInteger) write: (const unsigned char*) buf maxLength: (NSUInteger) len { #ifdef TALK_DEBUG NSLog(@"TCPAsyncConnection: write (len=%d)", len); #endif NSError *error = lastError; lastError = nil; if (error) [error release]; if (asyncSendData) { /* we don't use lock on this check, because in most cases we won't be using the lock and it may be expensive */ #ifdef TALK_DEBUG NSLog(@" - asynchronous data present (%d bytes, connected=%@)", [asyncSendData length], connected ? @"YES" : @"NO"); #endif if (connected) { /* asynchronous, yet connected - ready to send */ #ifdef TALK_DEBUG NSLog(@" - obtain lock to async data"); #endif /* when connected the lock is guaranteed to be there, hence no check */ [asyncLock lock]; #ifdef TALK_DEBUG NSLog(@" - got lock"); #endif NSMutableData *asyData = asyncSendData; /* copy the pointer to atomically process this */ asyncSendData = nil; [asyncLock unlock]; if (asyData) { /* in theory asyncConnect could have flushed it before we issued the lock, so guard against that */ #ifdef TALK_DEBUG NSLog(@" - send unsent data"); #endif BOOL res = [super writeData:asyData]; [asyData release]; #ifdef TALK_DEBUG if (!res) NSLog(@" - ERROR: FAILED to send unsent data! Returning -2"); #endif if (!res) return -2; } } else { /* asynchronous, unconnected - collect the data */ BOOL locked = NO; /* we're not connected, so the lock may not be in place yet (until the thread starts), so we need to be careful */ if (asyncLock) { locked = YES; [asyncLock lock]; } #ifdef TALK_DEBUG NSLog(@" - not connected, appending %d bytes to async data (has lock=%@)", len, locked ? @"YES" : @"NO"); #endif [asyncSendData appendBytes:buf length:len]; if (locked) [asyncLock unlock]; return len; } } if (len < 1) return len; #ifdef TALK_DEBUG NSInteger res = [super write:buf maxLength:len]; NSLog(@" - write result: %d", (int) res); return res; #else return [super write:buf maxLength:len]; #endif } @end