调度源仅在我首先执行 NSLog() 时被调用



我正在尝试将大中央调度与 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 之前,在设置这样的套接字时,您需要做很多事情。它们是:

  1. 创建套接字
  2. 将其绑定到本地地址(代码中缺少(
  3. 将其设置为非阻塞(代码中缺少(

下面是一个我能够放在一起的小示例,其中写入处理程序被重复调用,如预期的那样:

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 延迟启动方法的结束,其方式是调度源在释放源之前触发。

最新更新