TCPServer.m
/*  | 
File: TCPServer.m  | 
Abstract: TCPServer class.  | 
Version: 1.0  | 
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple  | 
 Inc. ("Apple") in consideration of your agreement to the following | 
terms, and your use, installation, modification or redistribution of  | 
this Apple software constitutes acceptance of these terms. If you do  | 
not agree with these terms, please do not use, install, modify or  | 
redistribute this Apple software.  | 
In consideration of your agreement to abide by the following terms, and  | 
subject to these terms, Apple grants you a personal, non-exclusive  | 
license, under Apple's copyrights in this original Apple software (the  | 
"Apple Software"), to use, reproduce, modify and redistribute the Apple  | 
Software, with or without modifications, in source and/or binary forms;  | 
provided that if you redistribute the Apple Software in its entirety and  | 
without modifications, you must retain this notice and the following  | 
text and disclaimers in all such redistributions of the Apple Software.  | 
Neither the name, trademarks, service marks or logos of Apple Inc. may  | 
be used to endorse or promote products derived from the Apple Software  | 
without specific prior written permission from Apple. Except as  | 
expressly stated in this notice, no other rights or licenses, express or  | 
implied, are granted by Apple herein, including but not limited to any  | 
patent rights that may be infringed by your derivative works or by other  | 
works in which the Apple Software may be incorporated.  | 
The Apple Software is provided by Apple on an "AS IS" basis. APPLE  | 
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION  | 
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS  | 
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND  | 
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.  | 
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL  | 
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF  | 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS  | 
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION,  | 
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED  | 
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE),  | 
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE  | 
POSSIBILITY OF SUCH DAMAGE.  | 
Copyright (C) 2009 Apple Inc. All Rights Reserved.  | 
*/  | 
/*  | 
File: TCPServer.m  | 
Abstract: Interface description for a basic TCP/IP server Foundation class  | 
*/  | 
#import "TCPServer.h"  | 
#include <sys/socket.h>  | 
#include <netinet/in.h>  | 
#include <unistd.h>  | 
NSString * const TCPServerErrorDomain = @"TCPServerErrorDomain";  | 
@implementation TCPServer  | 
- (id)init { | 
return self;  | 
}  | 
- (void)dealloc { | 
[self stop];  | 
[domain release];  | 
[name release];  | 
[type release];  | 
[super dealloc];  | 
}  | 
- (id)delegate { | 
return delegate;  | 
}  | 
- (void)setDelegate:(id)value { | 
delegate = value;  | 
}  | 
- (NSString *)domain { | 
return domain;  | 
}  | 
- (void)setDomain:(NSString *)value { | 
    if (domain != value) { | 
[domain release];  | 
domain = [value copy];  | 
}  | 
}  | 
- (NSString *)name { | 
return name;  | 
}  | 
- (void)setName:(NSString *)value { | 
    if (name != value) { | 
[name release];  | 
name = [value copy];  | 
}  | 
}  | 
- (NSString *)type { | 
return type;  | 
}  | 
- (void)setType:(NSString *)value { | 
    if (type != value) { | 
[type release];  | 
type = [value copy];  | 
}  | 
}  | 
- (uint16_t)port { | 
return port;  | 
}  | 
- (void)setPort:(uint16_t)value { | 
port = value;  | 
}  | 
- (void)handleNewConnectionFromAddress:(NSData *)addr inputStream:(NSInputStream *)istr outputStream:(NSOutputStream *)ostr { | 
// if the delegate implements the delegate method, call it  | 
    if (delegate && [delegate respondsToSelector:@selector(TCPServer:didReceiveConnectionFrom:inputStream:outputStream:)]) {  | 
[delegate TCPServer:self didReceiveConnectionFromAddress:addr inputStream:istr outputStream:ostr];  | 
}  | 
}  | 
// This function is called by CFSocket when a new connection comes in.  | 
// We gather some data here, and convert the function call to a method  | 
// invocation on TCPServer.  | 
static void TCPServerAcceptCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) { | 
TCPServer *server = (TCPServer *)info;  | 
    if (kCFSocketAcceptCallBack == type) {  | 
// for an AcceptCallBack, the data parameter is a pointer to a CFSocketNativeHandle  | 
CFSocketNativeHandle nativeSocketHandle = *(CFSocketNativeHandle *)data;  | 
uint8_t name[SOCK_MAXADDRLEN];  | 
socklen_t namelen = sizeof(name);  | 
NSData *peer = nil;  | 
        if (0 == getpeername(nativeSocketHandle, (struct sockaddr *)name, &namelen)) { | 
peer = [NSData dataWithBytes:name length:namelen];  | 
}  | 
CFReadStreamRef readStream = NULL;  | 
CFWriteStreamRef writeStream = NULL;  | 
CFStreamCreatePairWithSocket(kCFAllocatorDefault, nativeSocketHandle, &readStream, &writeStream);  | 
        if (readStream && writeStream) { | 
CFReadStreamSetProperty(readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);  | 
CFWriteStreamSetProperty(writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);  | 
[server handleNewConnectionFromAddress:peer inputStream:(NSInputStream *)readStream outputStream:(NSOutputStream *)writeStream];  | 
        } else { | 
// on any failure, need to destroy the CFSocketNativeHandle  | 
// since we are not going to use it any more  | 
close(nativeSocketHandle);  | 
}  | 
if (readStream) CFRelease(readStream);  | 
if (writeStream) CFRelease(writeStream);  | 
}  | 
}  | 
- (BOOL)start:(NSError **)error { | 
    CFSocketContext socketCtxt = {0, self, NULL, NULL, NULL}; | 
ipv4socket = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&TCPServerAcceptCallBack, &socketCtxt);  | 
ipv6socket = CFSocketCreate(kCFAllocatorDefault, PF_INET6, SOCK_STREAM, IPPROTO_TCP, kCFSocketAcceptCallBack, (CFSocketCallBack)&TCPServerAcceptCallBack, &socketCtxt);  | 
    if (NULL == ipv4socket || NULL == ipv6socket) { | 
if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerNoSocketsAvailable userInfo:nil];  | 
if (ipv4socket) CFRelease(ipv4socket);  | 
if (ipv6socket) CFRelease(ipv6socket);  | 
ipv4socket = NULL;  | 
ipv6socket = NULL;  | 
return NO;  | 
}  | 
int yes = 1;  | 
setsockopt(CFSocketGetNative(ipv4socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));  | 
setsockopt(CFSocketGetNative(ipv6socket), SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));  | 
// set up the IPv4 endpoint; if port is 0, this will cause the kernel to choose a port for us  | 
struct sockaddr_in addr4;  | 
memset(&addr4, 0, sizeof(addr4));  | 
addr4.sin_len = sizeof(addr4);  | 
addr4.sin_family = AF_INET;  | 
addr4.sin_port = htons(port);  | 
addr4.sin_addr.s_addr = htonl(INADDR_ANY);  | 
NSData *address4 = [NSData dataWithBytes:&addr4 length:sizeof(addr4)];  | 
    if (kCFSocketSuccess != CFSocketSetAddress(ipv4socket, (CFDataRef)address4)) { | 
if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerCouldNotBindToIPv4Address userInfo:nil];  | 
if (ipv4socket) CFRelease(ipv4socket);  | 
if (ipv6socket) CFRelease(ipv6socket);  | 
ipv4socket = NULL;  | 
ipv6socket = NULL;  | 
return NO;  | 
}  | 
    if (0 == port) { | 
// now that the binding was successful, we get the port number  | 
// -- we will need it for the v6 endpoint and for the NSNetService  | 
NSData *addr = [(NSData *)CFSocketCopyAddress(ipv4socket) autorelease];  | 
memcpy(&addr4, [addr bytes], [addr length]);  | 
port = ntohs(addr4.sin_port);  | 
}  | 
// set up the IPv6 endpoint  | 
struct sockaddr_in6 addr6;  | 
memset(&addr6, 0, sizeof(addr6));  | 
addr6.sin6_len = sizeof(addr6);  | 
addr6.sin6_family = AF_INET6;  | 
addr6.sin6_port = htons(port);  | 
memcpy(&(addr6.sin6_addr), &in6addr_any, sizeof(addr6.sin6_addr));  | 
NSData *address6 = [NSData dataWithBytes:&addr6 length:sizeof(addr6)];  | 
    if (kCFSocketSuccess != CFSocketSetAddress(ipv6socket, (CFDataRef)address6)) { | 
if (error) *error = [[NSError alloc] initWithDomain:TCPServerErrorDomain code:kTCPServerCouldNotBindToIPv6Address userInfo:nil];  | 
if (ipv4socket) CFRelease(ipv4socket);  | 
if (ipv6socket) CFRelease(ipv6socket);  | 
ipv4socket = NULL;  | 
ipv6socket = NULL;  | 
return NO;  | 
}  | 
// set up the run loop sources for the sockets  | 
CFRunLoopRef cfrl = CFRunLoopGetCurrent();  | 
CFRunLoopSourceRef source4 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, ipv4socket, 0);  | 
CFRunLoopAddSource(cfrl, source4, kCFRunLoopCommonModes);  | 
CFRelease(source4);  | 
CFRunLoopSourceRef source6 = CFSocketCreateRunLoopSource(kCFAllocatorDefault, ipv6socket, 0);  | 
CFRunLoopAddSource(cfrl, source6, kCFRunLoopCommonModes);  | 
CFRelease(source6);  | 
// we can only publish the service if we have a type to publish with  | 
    if (nil != type) { | 
NSString *publishingDomain = domain ? domain : @"";  | 
NSString *publishingName = nil;  | 
        if (nil != name) { | 
publishingName = name;  | 
        } else { | 
NSString * thisHostName = [[NSProcessInfo processInfo] hostName];  | 
            if ([thisHostName hasSuffix:@".local"]) { | 
publishingName = [thisHostName substringToIndex:([thisHostName length] - 6)];  | 
}  | 
}  | 
netService = [[NSNetService alloc] initWithDomain:publishingDomain type:type name:publishingName port:port];  | 
[netService publish];  | 
}  | 
return YES;  | 
}  | 
- (BOOL)stop { | 
[netService stop];  | 
[netService release];  | 
netService = nil;  | 
CFSocketInvalidate(ipv4socket);  | 
CFSocketInvalidate(ipv6socket);  | 
CFRelease(ipv4socket);  | 
CFRelease(ipv6socket);  | 
ipv4socket = NULL;  | 
ipv6socket = NULL;  | 
return YES;  | 
}  | 
@end  | 
Copyright © 2009 Apple Inc. All Rights Reserved. Terms of Use | Privacy Policy | Updated: 2009-10-27