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