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
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






Cocoa mail archive

