1

我正在尝试将大中央调度与 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"); 时被调用。没有被注释掉。这很奇怪。我没有测试读取回调。发送需要首先工作。

那么这里发生了什么?

4

2 回答 2

0

这里有很多东西不见了。我不确定到底是哪一个导致了奇怪的行为,但是当我完成所有缺失的事情时,它似乎“按预期”工作,并且我的 write 事件处理程序被可靠且重复地调用。一般来说,在将这样的套接字传递给 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 %s\n", 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.

于 2013-10-03T17:51:39.567 回答
0

我发现了错误:

在较新的 SDK 中,调度源受到自动引用计数的影响,尽管它们不是 Objective-C 对象。

因此,当 start 方法结束时,ARC 会处理调度源并且它们永远不会被调用。

NSLog 以调度源在源被释放之前触发的方式延迟 start 方法的结束。

于 2013-10-04T20:03:36.040 回答