libupnp下的ios多线程套接字挂在send()上

时间:2013-03-18 21:08:17

标签: ios multithreading sockets hang dlna

在我的iOS应用程序(DLNA媒体播放器)中,我看到了一个我不明白的悬念......我希望有人可以了解它。

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

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

编辑:我对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对Play命令作出响应。

在另一个线程上,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;
}

谢谢!

-Ken

1 个答案:

答案 0 :(得分:1)

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

我的代码有两个问题:

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

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

如果定义了-DSO_NOSIGPIPE,则每次尝试发送或recv时,都会执行select,并且只有/ 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);

    ...

比我更敏锐的想法“Moron,你在一个地方处理一些SIGPIPE,还有另一个地方!”

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

我删除了上面的sighandler,然后移动了“#ifdef SO_NOSIGPIPE”部分,其中SO_NOSIGPIPE属性应用于select上方,问题完全消失了。

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

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

但是,我当然再次享受可靠的网络通信。

希望这有助于某人。

-Ken