TCP/TCPListener.m
author Jens Alfke <jens@mooseyard.com>
Sun Jul 13 10:42:50 2008 -0700 (2008-07-13)
changeset 22 8b883753394a
parent 8 6f539dd9921c
child 26 cb9cdf247239
permissions -rwxr-xr-x
* Fixed: Responses still pending when a connection closed were not calling their onComplete targets.
* Fixed: BLIPTestClient target failed to build because it didn't link against zlib.
* If TCPListener.bonjourServiceName is changed while the listener is open, it now re-publishes the service with the new name.
* Added a TCPListener.bonjourService property.
* Added a BLIPMessage.representedObject property.
* Fixed a memory leak.
     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         _ipv6socket = [self _openProtocol: PF_INET6 address: (struct sockaddr*)&addr6 error: outError];
   160         if( ! _ipv6socket ) {
   161             _ipv4socket = closeSocket(_ipv4socket);
   162             return [self _failedToOpen: *outError];
   163         }
   164     }
   165     
   166     [self _openBonjour];
   167 
   168     LogTo(TCP,@"%@ is open",self);
   169     [self tellDelegate: @selector(listenerDidOpen:) withObject: nil];
   170     return YES;
   171 }
   172 
   173 - (BOOL) open
   174 {
   175     return [self open: nil];
   176 }
   177     
   178 
   179 - (void) close 
   180 {
   181     if( _ipv4socket ) {
   182         [self _closeBonjour];
   183         _ipv4socket = closeSocket(_ipv4socket);
   184         _ipv6socket = closeSocket(_ipv6socket);
   185 
   186         LogTo(BLIP,@"%@ is closed",self);
   187         [self tellDelegate: @selector(listenerDidClose:) withObject: nil];
   188     }
   189 }
   190 
   191 
   192 - (BOOL) isOpen
   193 {
   194     return _ipv4socket != NULL;
   195 }
   196 
   197 
   198 #pragma mark -
   199 #pragma mark ACCEPTING CONNECTIONS:
   200 
   201 
   202 @synthesize connectionClass = _connectionClass;
   203 
   204 
   205 - (BOOL) acceptConnection: (CFSocketNativeHandle)socket
   206 {
   207     IPAddress *addr = [IPAddress addressOfSocket: socket];
   208     if( ! addr )
   209         return NO;
   210     if( [_delegate respondsToSelector: @selector(listener:shouldAcceptConnectionFrom:)]
   211        && ! [_delegate listener: self shouldAcceptConnectionFrom: addr] )
   212         return NO;
   213     
   214     Assert(_connectionClass);
   215     TCPConnection *conn = [[self.connectionClass alloc] initIncomingFromSocket: socket
   216                                                                       listener: self];
   217     if( ! conn )
   218         return NO;
   219     
   220     if( _sslProperties ) {
   221         conn.SSLProperties = _sslProperties;
   222         [conn setSSLProperty: $true forKey: (id)kCFStreamSSLIsServer];
   223     }
   224     [conn open];
   225     [self tellDelegate: @selector(listener:didAcceptConnection:) withObject: conn];
   226     return YES;
   227 }
   228 
   229 
   230 static void TCPListenerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) 
   231 {
   232     TCPListener *server = (TCPListener *)info;
   233     if (kCFSocketAcceptCallBack == type) { 
   234         CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;
   235         BOOL accepted = NO;
   236         @try{
   237             accepted = [server acceptConnection: nativeSocketHandle];
   238         }catchAndReport(@"TCPListenerAcceptCallBack");
   239         if( ! accepted )
   240             close(nativeSocketHandle);
   241     }
   242 }
   243 
   244 
   245 #pragma mark -
   246 #pragma mark BONJOUR:
   247 
   248 
   249 - (void) _openBonjour
   250 {
   251     if( self.isOpen && _bonjourServiceType && !_netService) {
   252         // instantiate the NSNetService object that will advertise on our behalf.
   253         _netService = [[NSNetService alloc] initWithDomain: @"local." 
   254                                                       type: _bonjourServiceType
   255                                                       name: _bonjourServiceName ?:@""
   256                                                       port: _port];
   257         if( _netService ) {
   258             [_netService setDelegate:self];
   259             if( _bonjourTXTRecord )
   260                 [self _updateTXTRecord];
   261             [_netService publish];
   262         } else {
   263             self.bonjourError = -1;
   264             Warn(@"%@: Failed to create NSNetService",self);
   265         }
   266     }
   267 }
   268 
   269 - (void) _closeBonjour
   270 {
   271     if( _netService ) {
   272         [_netService stop];
   273         [_netService release];
   274         _netService = nil;
   275         self.bonjourPublished = NO;
   276     }
   277     if( self.bonjourError )
   278         self.bonjourError = 0;
   279 }
   280 
   281 
   282 - (NSString*) bonjourServiceName {return _bonjourServiceName;}
   283 
   284 - (void) setBonjourServiceName: (NSString*)name
   285 {
   286     if( ! $equal(name,_bonjourServiceName) ) {
   287         [self _closeBonjour];
   288         setObj(&_bonjourServiceName,name);
   289         [self _openBonjour];
   290     }
   291 }
   292 
   293 
   294 - (NSDictionary*) bonjourTXTRecord
   295 {
   296     return _bonjourTXTRecord;
   297 }
   298 
   299 - (void) setBonjourTXTRecord: (NSDictionary*)txt
   300 {
   301     if( ifSetObj(&_bonjourTXTRecord,txt) )
   302         [self _updateTXTRecord];
   303 }
   304 
   305 - (void) _updateTXTRecord
   306 {
   307     if( _netService ) {
   308         NSData *data;
   309         if( _bonjourTXTRecord ) {
   310             data = [NSNetService dataFromTXTRecordDictionary: _bonjourTXTRecord];
   311             if( data )
   312                 LogTo(BLIP,@"%@: Set %u-byte TXT record", self,data.length);
   313             else
   314                 Warn(@"TCPListener: Couldn't convert txt dict to data: %@",_bonjourTXTRecord);
   315         } else
   316             data = nil;
   317         [_netService setTXTRecordData: data];
   318     }
   319 }
   320 
   321 
   322 - (void)netServiceWillPublish:(NSNetService *)sender
   323 {
   324     LogTo(BLIP,@"%@: Advertising %@",self,sender);
   325     self.bonjourPublished = YES;
   326 }
   327 
   328 - (void)netService:(NSNetService *)sender didNotPublish:(NSDictionary *)errorDict
   329 {
   330     self.bonjourError = [[errorDict objectForKey:NSNetServicesErrorCode] intValue];
   331     LogTo(BLIP,@"%@: Failed to advertise %@: error %i",self,sender,self.bonjourError);
   332     [_netService release];
   333     _netService = nil;
   334 }
   335 
   336 - (void)netServiceDidStop:(NSNetService *)sender
   337 {
   338     LogTo(BLIP,@"%@: Stopped advertising %@",self,sender);
   339     self.bonjourPublished = NO;
   340     [_netService release];
   341     _netService = nil;
   342 }
   343 
   344 
   345 @end
   346 
   347 
   348 
   349 /*
   350  Copyright (c) 2008, Jens Alfke <jens@mooseyard.com>. All rights reserved.
   351  
   352  Redistribution and use in source and binary forms, with or without modification, are permitted
   353  provided that the following conditions are met:
   354  
   355  * Redistributions of source code must retain the above copyright notice, this list of conditions
   356  and the following disclaimer.
   357  * Redistributions in binary form must reproduce the above copyright notice, this list of conditions
   358  and the following disclaimer in the documentation and/or other materials provided with the
   359  distribution.
   360  
   361  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
   362  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
   363  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRI-
   364  BUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
   365  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
   366   PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
   367  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
   368  THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   369  */