0

在我的 iOS 应用程序(DLNA 媒体播放器)中,我看到了一个我不明白的挂起......我希望有人能解释一下。

我的应用程序是在位于 C++ 库之上的 Objective C 中构建的,其中一部分是 libupnp。为了记录,在查看下面的代码时,设置了编译标志 SO_NOSIGPIPE。

从广义上讲,该应用程序运行良好,至少在运行 iOS 6 的 iPod 和我的 iPad 上运行良好。它可以处理所有媒体播放器之类的事情。

编辑:我对 iPhone 4 上的操作系统有误,我认为它是 6.x,但它是 5.1.1,值得。

当我开始在 iPhone 4 (iOS 5.1.1) 和 iPhone 5 (iOS 6) 上测试我的应用程序时,就会出现问题......这告诉我我的代码中存在时间问题。

用户选择要在远程数字媒体接收器 (DMR) 上播放/显示的媒体项目。

我的代码调用libupnp,创建soap 命令来实现这一点。然后它调用 http_RequestAndResponse(),它创建套接字,connect()s 到主机,并调用 http_SendMessage,它调用 sock_read_write(我将在后面的消息中包含这个函数)来发送我构建的请求(POST 命令播放 DMR 上的媒体)。然后,使用同一个套接字,调用 http_RecvMessage(它再次调用 sock_read_write() 来接收字节)。此时,调用 select() 等待 DMR 对播放命令做出响应。

在不同的线程上,libupnp 的 Web 服务器收到对我们刚才说要播放的媒体文件位的请求。所以在另一个线程上,我用字节调用 http_SendMessage 来响应请求,它调用 sock_read_write() 将字节写入客户端。

sock_read_write 中的这个 send() 挂起。它不仅挂起 libupnp,而且意味着任何线程上的套接字上都没有更多的通信。

这些挂起的套接字似乎没有超时、死亡或以其他方式终止。当然,它是我正在构建的 DLNA 媒体播放器,关于世界状态的许多命令和报告都通过这些套接字传输,所以我的应用程序实际上变成了僵尸:它响应鼠标点击等等,但是你不能做任何有意义的事情。

我试过让 send() 非阻塞。我已经尝试调用 fcntrl(sock,F_SETFL, O_NONBLOCK) 将其设置为非阻塞,并在调用 send() 之前如果因任何原因失败则返回。

我已经尝试在 send() 上使用类似 MSG_NOWAIT(对 iOS 没有影响)的标志来发送()。

这似乎是一个时间问题。在 iPad 和 iPod 上,我可以一直播放音乐,直到奶牛回家。在 iPhone 4 和 iPhone 5 上,我遇到了挂起。

有什么建议么?(如果您告诉我哪些具体回答了这个问题,我们很乐意接受对 RTFM、阅读手册页、阅读书籍等的建议……)

哦,sock_read_write() 的代码(来自 libupnp 1.6.18):

/*!
 * \brief Receives or sends data. Also returns the time taken to receive or
 * send data.
 *
 * \return
 *  \li \c numBytes - On Success, no of bytes received or sent or
 *  \li \c UPNP_E_TIMEDOUT - Timeout
 *  \li \c UPNP_E_SOCKET_ERROR - Error on socket calls
 */
static int sock_read_write(
    /*! [in] Socket Information Object. */
    SOCKINFO *info,
    /*! [out] Buffer to get data to or send data from. */
    char *buffer,
    /*! [in] Size of the buffer. */
    size_t bufsize,
    /*! [in] timeout value. */
    int *timeoutSecs,
    /*! [in] Boolean value specifying read or write option. */
    int bRead)
{
    int retCode;
    fd_set readSet;
    fd_set writeSet;
    struct timeval timeout;
    long numBytes;
    time_t start_time = time(NULL);
    SOCKET sockfd = info->socket;
    long bytes_sent = 0;
    size_t byte_left = (size_t)0;
    ssize_t num_written;

    if (*timeoutSecs < 0)
        return UPNP_E_TIMEDOUT;
    FD_ZERO(&readSet);
    FD_ZERO(&writeSet);
    if (bRead)
        FD_SET(sockfd, &readSet);
    else
        FD_SET(sockfd, &writeSet);
    timeout.tv_sec = *timeoutSecs;
    timeout.tv_usec = 0;
    while (TRUE) {
        if (*timeoutSecs == 0)
            retCode = select(sockfd + 1, &readSet, &writeSet,
                NULL, NULL);
        else
            retCode = select(sockfd + 1, &readSet, &writeSet,
                NULL, &timeout);
        if (retCode == 0)
            return UPNP_E_TIMEDOUT;
        if (retCode == -1) {
            if (errno == EINTR)
                continue;
            return UPNP_E_SOCKET_ERROR;
        } else
            /* read or write. */
            break;
    }
#ifdef SO_NOSIGPIPE
    {
        int old;
        int set = 1;
        socklen_t olen = sizeof(old);
        getsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &old, &olen);
        setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &set, sizeof(set));
#endif
        if (bRead) {
            /* read data. */
            numBytes = (long)recv(sockfd, buffer, bufsize, MSG_NOSIGNAL);
        } else {
            byte_left = bufsize;
            bytes_sent = 0;
            while (byte_left != (size_t)0) {
                /* write data. */
                num_written = send(sockfd,
                    buffer + bytes_sent, byte_left,
                    MSG_DONTROUTE | MSG_NOSIGNAL);
                if (num_written == -1) {
#ifdef SO_NOSIGPIPE
                    setsockopt(sockfd, SOL_SOCKET,
                        SO_NOSIGPIPE, &old, olen);
#endif
                    return (int)num_written;
                }
                byte_left -= (size_t)num_written;
                bytes_sent += num_written;
            }
            numBytes = bytes_sent;
        }
#ifdef SO_NOSIGPIPE
        setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &old, olen);
    }
#endif
    if (numBytes < 0)
        return UPNP_E_SOCKET_ERROR;
    /* subtract time used for reading/writing. */
    if (*timeoutSecs != 0)
        *timeoutSecs -= (int)(time(NULL) - start_time);

    return (int)numBytes;
}

谢谢!

-肯

4

1 回答 1

1

嗯,现在是不是很有趣……

我的代码有两个问题:

1) 有人更改了配置文件,并方便地从编译中删除了 -DSO_NOSIGPIPE。总是值得检查细节。

2) libupnp 中的 sock_read_write() 似乎有一个错误。

如果定义了 -DSO_NOSIGPIPE,则每次尝试发送或接收时,都会完成一次选择,并且只有 /then/ 是应用于套接字的 SO_NOSIGPIPE 选项。操作完成后,套接字的原始状态将再次设置。

当我第一次测试 -DSO_NOSIGPIPE 时,有时我仍然会得到 SIGPIPE。我最终通过在我的 main.m 中做这样的事情来解决它:

void sighandler(int signum)
{
    NSLog(@"Caught signal %d",signum);
}

int main(int argc, char *argv[])
{
    signal(SIGPIPE,sighandler);

    ...

比我更聪明的人在想“白痴,你在一个地方处理一些 SIGPIPE,而在另一个地方处理一些!”

原来 select() 语句也可以返回 SIGPIPE。

我删除了上面的 sighandler,然后将 SO_NOSIGPIPE 属性应用到选择上方的“#ifdef SO_NOSIGPIPE”部分移动,问题就完全消失了。

如果 select() 由于 EPIPE 而失败,则 select() 返回 -1,它在接下来的几行中被捕获,并且函数以 UPNP_E_SOCKET_ERROR 退出,o 它可以被正确处理,而不是简单地被忽略。

我完全有可能完全误解了这里发生的事情,在这种情况下,我绝对期待接受教育。

但是,我当然又享受到了可靠的网络通信。

希望这可以帮助某人。

-肯

于 2013-03-20T23:46:15.943 回答