我正在尝试将大中央调度与 bsd 套接字结合使用来发送 icmp ping。我将DISPATCH_SOURCE_TYPE_WRITE和DISPATCH_SOURCE_TYPE_READ添加为调度源以异步读取和写入。
所以这是我创建 bsd 套接字并安装调度源的方法:
- (void)start
{
int err;
const struct sockaddr * addrPtr;
assert(self.hostAddress != nil);
// Open the socket.
addrPtr = (const struct sockaddr *) [self.hostAddress bytes];
fd = -1;
err = 0;
switch (addrPtr->sa_family) {
case AF_INET: {
fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
if (fd < 0) {
err = errno;
}
} break;
case AF_INET6:
assert(NO);
// fall through
default: {
err = EPROTONOSUPPORT;
} break;
}
if (err != 0) {
[self didFailWithError:[NSError errorWithDomain:NSPOSIXErrorDomain code:err userInfo:nil]];
} else {
dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_event_handler(writeSource, ^{
abort(); // testing
// call call method here to send a ping
});
dispatch_resume(writeSource);
//NSLog(@"testout");
dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_event_handler(readSource, ^{
unsigned long bytesAvail = dispatch_source_get_data(readSource);
NSLog(@"bytes available: %lu", bytesAvail);
});
dispatch_resume(readSource);
}
}
你看到//NSLog(@"testout"(;?有趣的是,只有在//NSLog(@"testout"(;没有被注释掉。这很奇怪。我没有测试读取回调。发送需要首先工作。
这到底是怎么回事呢?
这里缺少很多东西。我不确定究竟是哪一个导致了奇怪的行为,但是当我执行所有缺失的操作时,它似乎"按预期"工作,并且我的写入事件处理程序被可靠且重复地调用。一般来说,在将套接字传递给 GCD 之前,在设置这样的套接字时,您需要做很多事情。它们是:
- 创建套接字
- 将其绑定到本地地址(代码中缺少(
- 将其设置为非阻塞(代码中缺少(
下面是一个我能够放在一起的小示例,其中写入处理程序被重复调用,如预期的那样:
int DoStuff()
{
int fd = -1;
// Create
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
perror("cannot create socket");
return 0;
}
// Bind
struct sockaddr_in *localAddressPtr = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
memset((char *)localAddressPtr, 0, sizeof(*localAddressPtr));
localAddressPtr->sin_family = AF_INET;
localAddressPtr->sin_addr.s_addr = htonl(INADDR_ANY);
localAddressPtr->sin_port = htons(0);
if (bind(fd, (struct sockaddr *)localAddressPtr, sizeof(*localAddressPtr)) < 0) {
perror("bind failed");
return 0;
}
// Set non-blocking
int flags;
if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
flags = 0;
if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK))
{
perror("Couldnt set non-blocking");
return 0;
}
// Do a DNS lookup...
struct hostent *hp;
struct sockaddr_in *remoteAddressPtr = malloc(sizeof(struct sockaddr_in));
// Fill in the server's address and data
memset((char*)remoteAddressPtr, 0, sizeof(*remoteAddressPtr));
remoteAddressPtr->sin_family = AF_INET;
remoteAddressPtr->sin_port = htons(12345);
// Look up the address of the server by name
const char* host = "www.google.com";
hp = gethostbyname(host);
if (!hp) {
fprintf(stderr, "could not obtain address of %sn", host);
return 0;
}
// Copy the host's address into the remote address structure
memcpy((void *)&remoteAddressPtr->sin_addr, hp->h_addr_list[0], hp->h_length);
dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
dispatch_source_set_event_handler(writeSource, ^{
// Send message
const char* my_message = "the only thing we have to fear is fear itself.";
unsigned long len = strlen(my_message);
if (sendto(fd, my_message, len, 0, (struct sockaddr *)remoteAddressPtr, sizeof(*remoteAddressPtr)) != len) {
perror("sendto failed");
dispatch_source_cancel(writeSource);
}
});
dispatch_source_set_cancel_handler(writeSource, ^{
close(fd);
free(localAddressPtr);
free(remoteAddressPtr);
});
dispatch_resume(writeSource);
return 1;
}
注意:在我的示例中,如果没有发送操作中的错误,就无法处理 writeSource。这是一个微不足道的例子...
关于为什么NSLog
触发处理程序在您的情况下触发的一般理论是,它将执行保持在或低于该堆栈帧足够长的时间,以便后台线程出现并调用处理程序,但没有该NSLog
,您的函数返回,并且在处理程序被调用之前某些东西有机会死亡。事实上,如果您使用的是 ARC,则可能是writeSource
本身被解除分配,因为我没有看到您在此功能范围之外的任何地方对它进行强烈引用。(我的例子在块中捕获了对它的强引用,从而使它保持活动状态。您可以通过存储对writeSource.
的强引用来在代码中对此进行测试
我发现了错误:
在较新的SDK中,调度源受到自动引用计数的约束,尽管它们不是Objective-C对象。
因此,当启动方法结束时,ARC 会释放调度源,并且它们永远不会被调用。
NSLog 延迟启动方法的结束,其方式是调度源在释放源之前触发。