TCP/TCPListener.m
author jim@Baldrick.local
Tue May 05 23:24:50 2009 -0700 (2009-05-05)
changeset 43 aab592ac36fc
parent 26 cb9cdf247239
child 44 d8a559a39284
permissions -rwxr-xr-x
bug fixes and improvements to new bonjour classes and tcplistener, also added a static library target
     1 //
     2 //  TCPListener.m
     3 //  MYNetwork
     4 //
     5 //  Created by Jens Alfke on 5/10/08.
     6 //  Copyright 2008 Jens Alfke. All rights reserved.
     7 //  Portions based on TCPServer class from Apple's "CocoaEcho" sample code.
     8 
     9 #import "TCPListener.h"
    10 #import "TCPConnection.h"
    11 
    12 #import "Logging.h"
    13 #import "Test.h"
    14 #import "ExceptionUtils.h"
    15 #import "IPAddress.h"
    16 
    17 #include <sys/socket.h>
    18 #include <netinet/in.h>
    19 #include <unistd.h>
    20 
    21 
    22 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, 
    23                                       CFDataRef address, const void *data, void *info);
    24 
    25 @interface TCPListener()
    26 - (void) _openBonjour;
    27 - (void) _publishBonjour;
    28 - (void) _closeBonjour;
    29 @property BOOL bonjourPublished;
    30 @property NSInteger bonjourError;
    31 - (void) _updateTXTRecord;
    32 @end
    33 
    34 
    35 @implementation TCPListener
    36 
    37 
    38 - (id) initWithPort: (UInt16)port
    39 {
    40     self = [super init];
    41     if (self != nil) {
    42         _port = port;
    43     }
    44     return self;
    45 }
    46 
    47 
    48 - (void) dealloc 
    49 {
    50     [self close];
    51     LogTo(TCP,@"DEALLOC %@",self);
    52     [super dealloc];
    53 }
    54 
    55 
    56 @synthesize delegate=_delegate, port=_port, useIPv6=_useIPv6,
    57             bonjourServiceType=_bonjourServiceType,
    58             bonjourPublished=_bonjourPublished, bonjourError=_bonjourError,
    59             bonjourService=_netService,
    60             pickAvailablePort=_pickAvailablePort;
    61 
    62 
    63 - (NSString*) description
    64 {
    65     return $sprintf(@"%@[port %hu]",self.class,_port);
    66 }
    67 
    68 
    69 // Stores the last error from CFSocketCreate or CFSocketSetAddress into *outError.
    70 static void* getLastCFSocketError( NSError **outError ) {
    71     if( outError )
    72         *outError = [NSError errorWithDomain: NSPOSIXErrorDomain code: errno userInfo: nil];
    73     return NULL;
    74 }
    75 
    76 // Closes a socket (if it's not already NULL), and returns NULL to assign to it.
    77 static CFSocketRef closeSocket( CFSocketRef socket ) {
    78     if( socket ) {
    79         CFSocketInvalidate(socket);
    80         CFRelease(socket);
    81     }
    82     return NULL;
    83 }
    84 
    85 // opens a socket of a given protocol, either ipv4 or ipv6.
    86 - (CFSocketRef) _openProtocol: (SInt32) protocolFamily 
    87                       address: (struct sockaddr*)address
    88                         error: (NSError**)error
    89 {
    90     CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL};
    91     CFSocketRef socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP,
    92                                         kCFSocketAcceptCallBack, &TCPListenerAcceptCallBack, &socketCtxt);
    93     if( ! socket ) 
    94         return getLastCFSocketError(error);   // CFSocketCreate leaves error code in errno
    95     
    96     int yes = 1;
    97     setsockopt(CFSocketGetNative(socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));
    98     
    99     NSData *addressData = [NSData dataWithBytes:address length:address->sa_len];
   100     if (kCFSocketSuccess != CFSocketSetAddress(socket, (CFDataRef)addressData)) {
   101         getLastCFSocketError(error);
   102         return closeSocket(socket);
   103     }
   104     // set up the run loop source for the socket
   105     CFRunLoopRef cfrl = CFRunLoopGetCurrent();
   106     CFRunLoopSourceRef source = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0);
   107     CFRunLoopAddSource(cfrl, source, kCFRunLoopCommonModes);
   108     CFRelease(source);
   109     return socket;
   110 }
   111 
   112 - (BOOL) _failedToOpen: (NSError*)error
   113 {
   114     LogTo(TCP,@"%@ failed to open: %@",self,error);
   115     [self tellDelegate: @selector(listener:failedToOpen:) withObject: error];
   116     return NO;
   117 }
   118 
   119 
   120 - (BOOL) open: (NSError**)outError 
   121 {
   122     // set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us
   123     do{
   124         struct sockaddr_in addr4;
   125         memset(&addr4, 0, sizeof(addr4));
   126         addr4.sin_len = sizeof(addr4);
   127         addr4.sin_family = AF_INET;
   128         addr4.sin_port = htons(_port);
   129         addr4.sin_addr.s_addr = htonl(INADDR_ANY);
   130 
   131         NSError *error;
   132         _ipv4socket = [self _openProtocol: PF_INET address: (struct sockaddr*)&addr4 error: &error];
   133         if( ! _ipv4socket ) {
   134             if( error.code==EADDRINUSE && _pickAvailablePort && _port<0xFFFF ) {
   135                 LogTo(BLIPVerbose,@"%@: port busy, trying %hu...",self,_port+1);
   136                 self.port += 1;        // try the next port
   137             } else {
   138                 if( outError ) *outError = error;
   139                 return [self _failedToOpen: error];
   140             }
   141         }
   142     }while( ! _ipv4socket );
   143     
   144     if (0 == _port) {
   145         // now that the binding was successful, we get the port number 
   146         NSData *addr = [(NSData *)CFSocketCopyAddress(_ipv4socket) autorelease];
   147         const struct sockaddr_in *addr4 = addr.bytes;
   148         self.port = ntohs(addr4->sin_port);
   149     }
   150     
   151     if( _useIPv6 ) {
   152         // set up the IPv6 endpoint
   153         struct sockaddr_in6 addr6;
   154         memset(&addr6, 0, sizeof(addr6));
   155         addr6.sin6_len = sizeof(addr6);
   156         addr6.sin6_family = AF_INET6;
   157         addr6.sin6_port = htons(_port);
   158         memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));
   159         
   160         NSError *error;
   161         _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: &error];
   162         if( ! _ipv6socket ) {
   163             _ipv4socket = closeSocket(_ipv4socket);
   164             [self _failedToOpen: error];
   165             if (outError) *outError = error;
   166             return NO;
   167         }
   168     }
   169     
   170     [self _openBonjour];
   171 
   172     LogTo(TCP,@"%@ is open",self);
   173     [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
   174     return YES;
   175 }
   176 
   177 - (BOOL) open
   178 {
   179     return [self open: nil];
   180 }
   181     
   182 
   183 - (void) close 
   184 {
   185     if( _ipv4socket ) {
   186         [self _closeBonjour];
   187         _ipv4socket = closeSocket(_ipv4socket);
   188         _ipv6socket = closeSocket(_ipv6socket);
   189 
   190         LogTo(BLIP,@"%@ is closed",self);
   191         [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
   192     }
   193 }
   194 
   195 
   196 - (BOOL) isOpen
   197 {
   198     return _ipv4socket != NULL;
   199 }
   200 
   201 
   202 #pragma mark -
   203 #pragma mark ACCEPTING CONNECTIONS:
   204 
   205 
   206 @synthesize connectionClass = _connectionClass;
   207 
   208 
   209 - (BOOL) acceptConnection: (CFSocketNativeHandle)socket
   210 {
   211     IPAddress *addr = [IPAddress addressOfSocket: socket];
   212     if( ! addr )
   213         return NO;
   214     if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
   215        && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
   216         return NO;
   217     
   218     Assert(_connectionClass);
   219     TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
   220                                                                       listener: self];
   221     if( ! conn )
   222         return NO;
   223     
   224     if( _sslProperties ) {
   225         conn.SSLProperties = _sslProperties;
   226         [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
   227     }
   228     [conn open];
   229     [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
   230     return YES;
   231 }
   232 
   233 
   234 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) 
   235 {
   236     TCPListener *server = (TCPListener *)info;
   237     if (kCFSocketAcceptCallBack == type) { 
   238         CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
   239         BOOL accepted = NO;
   240         @try{
   241             accepted = [server acceptConnection: nativeSocketHandle];
   242         }catchAndReport(@"TCPListenerAcceptCallBack");
   243         if( ! accepted )
   244             close(nativeSocketHandle);
   245     }
   246 }
   247 
   248 
   249 #pragma mark -
   250 #pragma mark BONJOUR:
   251 
   252 // subclasses can override if they want to call publishWithOptions: instead of publish
   253 - (void) _publishBonjour
   254 {
   255 	[_netService publish];
   256 }
   257 
   258 - (void) _openBonjour
   259 {
   260     if( self.isOpen && _bonjourServiceType && !_netService) {
   261         // instantiate the NSNetService object that will advertise on our behalf.
   262         _netService = [[NSNetService alloc] initWithDomain: @"local." 
   263                                                       type: _bonjourServiceType
   264                                                       name: _bonjourServiceName ?:@""
   265                                                       port: _port];
   266         if( _netService ) {
   267             [_netService setDelegate:self];
   268             if( _bonjourTXTRecord )
   269                 [self _updateTXTRecord];
   270 			[self _publishBonjour];
   271         } else {
   272             self.bonjourError = -1;
   273             Warn(@"%@: Failed to create NSNetService",self);
   274         }
   275     }
   276 }
   277 
   278 - (void) _closeBonjour
   279 {
   280     if( _netService ) {
   281         [_netService stop];
   282         [_netService release];
   283         _netService = nil;
   284         self.bonjourPublished = NO;
   285     }
   286     if( self.bonjourError )
   287         self.bonjourError = 0;
   288 }
   289 
   290 
   291 - (NSString*) bonjourServiceName {return _bonjourServiceName;}
   292 
   293 - (void) setBonjourServiceName: (NSString*)name
   294 {
   295     if( ! $equal(name,_bonjourServiceName) ) {
   296         [self _closeBonjour];
   297         setObj(&_bonjourServiceName,name);
   298         [self _openBonjour];
   299     }
   300 }
   301 
   302 
   303 - (NSDictionary*) bonjourTXTRecord
   304 {
   305     return _bonjourTXTRecord;
   306 }
   307 
   308 - (void) setBonjourTXTRecord: (NSDictionary*)txt
   309 {
   310     if( ifSetObj(&_bonjourTXTRecord,txt) )
   311         [self _updateTXTRecord];
   312 }
   313 
   314 - (void) _updateTXTRecord
   315 {
   316     if( _netService ) {
   317         NSData *data;
   318         if( _bonjourTXTRecord ) {
   319             data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
   320             if( data )
   321                 LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
   322             else
   323                 Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
   324         } else
   325             data = nil;
   326         [_netService setTXTRecordData: data];
   327     }
   328 }
   329 
   330 
   331 - (void)netServiceDidPublish:(NSNetService *)sender
   332 {
   333     LogTo(BLIP,@"%@: Advertising %@",self,sender);
   334     self.bonjourPublished = YES;
   335 }
   336 
   337 - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
   338 {
   339     self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
   340     LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
   341     [_netService release];
   342     _netService = nil;
   343 }
   344 
   345 - (void)netServiceDidStop:(NSNetService *)sender
   346 {
   347     LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
   348     self.bonjourPublished = NO;
   349     [_netService release];
   350     _netService = nil;
   351 }
   352 
   353 
   354 @end
   355 
   356 
   357 
   358 /*
   359  Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   360  
   361  Redistribution and use in source and binary forms, with or without modification, are permitted
   362  provided that the following conditions are met:
   363  
   364  * Redistributions of source code must retain the above copyright notice, this list of conditions
   365  and the following disclaimer.
   366  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   367  and the following disclaimer in the documentation and/or other materials provided with the
   368  distribution.
   369  
   370  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   371  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   372  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   373  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   374  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   375   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   376  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   377  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   378  */