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