Re: Implementing a TCP port listener in Cocoa
Re: Implementing a TCP port listener in Cocoa
- Subject: Re: Implementing a TCP port listener in Cocoa
- From: Andrew Bowman <email@hidden>
- Date: Wed, 28 Jun 2006 14:35:14 -0700
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
_______________________________________________
Do not post admin requests to the list. They will be ignored.
Cocoa-dev mailing list (email@hidden)
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden