Skip navigation.
 
mlRe: Implementing a TCP port listener in Cocoa
FROM : Andrew Bowman
DATE : Wed Jun 28 23:35:14 2006

On Jun 28, 2006, at 12:16 PM, Jakob Olesen wrote:

>
> On 28/06/2006, at 16.06, Rick Hoge wrote:
>

>>
>> I would like to implement an internet service from within a Cocoa 
>> application such that when a remote client requests a connection 
>> on a certain port the connection and subsequent service will be 
>> handled by the Cocoa application.
>>
>> I want to do this using Cocoa Foundation classes if possible, but 
>> it's not really clear from the documentation how to set this up. 
>> Most of the documentation is geared towards Distributed Objects 
>> (DO) and not provision of a TCP port service in the classic Unix 
>> sense.

>
> I think you need to use Core Foundation for a listening socket. 
> CFSocket lets you create a socket that listens for incoming 
> connections in a run loop. You get a callback for each connection. 
> Check out http://developer.apple.com/samplecode/CFLocalServer/
> index.html It is for UNIX domain sockets, but you can do the same 
> for INET sockets.
>
> Once you get a connection, you are passed a bsd-style file 
> descriptor in the accept callback. Pass that to 
> CFStreamCreatePairWithSocket() to get a CFReadStreamRef
> +CFWriteStreamRef pair. These are toll-free bridged to 
> NSInputStream and NSOutputStream.
>
>
> The code below is a bit messy - it is part of a Ruby wrapper for 
> CFSocket. Apple's sample code is probably better.
>
> static void
> accept_callback(CFSocketRef s, CFSocketCallBackType callbackType, 
> CFDataRef address, const void *data, void *info)
> {
>    if (callbackType!=kCFSocketAcceptCallBack)
>        return;
>    CFSocketNativeHandle fd = *(CFSocketNativeHandle*)data;
>    // pass fd to CFStreamCreatePairWithSocket...
> }
>
> // ServerSocket.new(:host, port)
> static VALUE
> socket_initialize(int argc, VALUE *argv, VALUE slf)
> {
>    VALUE ahost, aport;
>    rb_scan_args(argc, argv, "02", &ahost, &aport);
>
>    Socket *s = DATA_PTR(slf);
>    struct addrinfo hint;
>    memset(&hint, 0, sizeof(hint));
>    hint.ai_family = PF_UNSPEC;
>    hint.ai_socktype = SOCK_STREAM;
>    hint.ai_protocol = IPPROTO_TCP;
>    hint.ai_flags = AI_PASSIVE | AI_CANONNAME;
>    const char *hostname = NIL_P(ahost) ? 0 : StringValuePtr(ahost);
>    const char *servname = NIL_P(aport) ? 0 : RSTRING
> (rb_obj_as_string(aport))->ptr;
>    struct addrinfo *res = 0;
>    if (getaddrinfo(hostname, servname, &hint, &res))
>        rb_sys_fail("getaddrinfo");
>    if (!res)
>        rb_raise(rb_eIOError, "getaddrinfo(%s,%s) failed", 
> hostname, servname);
>
>    CFSocketContext ctx = { 0, (void*)slf, cf_ruby_retain, 
> cf_ruby_release, cf_ruby_copy_description };
>    s->sock = CFSocketCreate(kCFAllocatorDefault, res->ai_family, 
> res->ai_socktype, res->ai_protocol, kCFSocketAcceptCallBack, 
> accept_callback, &ctx);
>    if (!s->sock) {
>        freeaddrinfo(res);
>        rb_sys_fail("couldn't create socket");
>    }
>
>    CFDataRef addr = CFDataCreate(kCFAllocatorDefault, (UInt8*)res-
> >ai_addr, res->ai_addrlen);
>    //NSLog(@"getaddrinfo(%s,%s) -> %s %@", hostname, servname, res-
> >ai_canonname, addr);
>    freeaddrinfo(res);
>
>    int yes = 1;
>    setsockopt(CFSocketGetNative(s->sock), SOL_SOCKET, 
> SO_REUSEADDR, (void *)&yes, sizeof(yes));
>
>    // now bind
>    CFSocketError err = CFSocketSetAddress(s->sock, addr);
>    CFRelease(addr);
>    if (err!=kCFSocketSuccess) {
>        rb_sys_fail("couldn't bind socket");
>    }
>    return slf;
> }
>
> static VALUE
> m_accept(VALUE slf)
> {
> //    NSLog(@"accept slf=%p", slf);
>    Socket *s = DATA_PTR(slf);
>    s->block = rb_block_proc();
>    CFRunLoopSourceRef rls = CFSocketCreateRunLoopSource
> (kCFAllocatorDefault, s->sock, 0);
>    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, 
> kCFRunLoopCommonModes);
>    CFRelease(rls);
>    return Qnil;
> }
>
>



Is there something wrong with using NSSocketPort and asynchronous 
callbacks with NSFileHandle?  Assuming serverSocket and serverHandle 
are instance variables, something like:

serverSocket = [[NSSocketPort alloc] initWithTCPPort:myPortNumber];

serverHandle = [[NSFileHandle alloc] initWithFileDescriptor:
[serverSocket socket] closeOnDealloc:YES];

[[NSNotificationCenter defaultCenter] addObserver:self 
selector:@selector(acceptConnection:) 
name:NSFileHandleConnectionAcceptedNotification object:serverHandle];

[serverHandle acceptConnectionInBackgroundAndNotify];


Your acceptConnection method (or whatever you choose to name it) 
would look like:


- (void) acceptConnection: (NSNotification *)note {
   NSFileHandle *serviceHandle = [[note userInfo] 
objectForKey:NSFileHandleNotificationFileHandleItem];

   // any extra code here for dealing with new connections and 
retaining serviceHandle, probably in some collection
   
   [[NSNotificationCenter defaultCenter] addObserver:self 
selector:@selector(readData:) 
name:NSFileHandleReadCompletionNotification object:serviceHandle];

   [serviceHandle readInBackgroundAndNotify];

   
   [serverHandle acceptConnectionInBackgroundAndNotify]; // in order to 
keep listening
}


Your readData method (or whatever you choose to name it) would look 
something like:


- (void) readData: (NSNotification *)note {
   NSData *dataBytes = [[note userInfo] 
objectForKey:NSFileHandleNotificationDataItem];

   if ([dataBytes length] != 0) {
       // your code here

       // if you need to read more, obtain the appropriate file handle and 
send another readInBackgroundAndNotify: message.
   }
   else {
       // data length of 0 means connection is closed
       // cleanup here, if necessary
   }
}


Seems like this is an easier way to go.


- Andrew Bowman

Related mailsAuthorDate
mlImplementing a TCP port listener in Cocoa Rick Hoge Jun 28, 16:06
mlRe: Implementing a TCP port listener in Cocoa Jakob Olesen Jun 28, 21:16
mlRe: Implementing a TCP port listener in Cocoa Andrew Bowman Jun 28, 23:35
mlRe: Implementing a TCP port listener in Cocoa Jakob Olesen Jun 28, 23:58
mlRe: Implementing a TCP port listener in Cocoa Douglas Davidson Jun 29, 00:00
mlRe: Implementing a TCP port listener in Cocoa Andrew Bowman Jun 29, 00:30
mlRe: Implementing a TCP port listener in Cocoa Douglas Davidson Jun 29, 00:39
mlRe: Implementing a TCP port listener in Cocoa Rick Hoge Jun 29, 03:00
mlRe: Implementing a TCP port listener in Cocoa Dustin Voss Jun 29, 03:35
mlRe: Implementing a TCP port listener in Cocoa glenn andreas Jun 29, 05:11
mlRe: Implementing a TCP port listener in Cocoa Rick Hoge Jun 29, 05:34
mlRe: Implementing a TCP port listener in Cocoa Jakob Olesen Jun 29, 11:40
mlRe: Implementing a TCP port listener in Cocoa Rick Hoge Jun 29, 14:44
mlRe: Implementing a TCP port listener in Cocoa Greg Herlihy Jun 30, 07:52