0

iOS 用のアプリ (DLNA メディア プレーヤー) で、理解できないハングが発生しています...誰かがそれを明らかにしてくれることを願っています。

私のアプリは C++ ライブラリの上にある Objective C で構築されており、その一部は libupnp です。以下のコードを見ると、コンパイル フラグ SO_NOSIGPIPE が設定されています。

大まかに言えば、このアプリは、少なくとも iOS 6 を実行している iPod と私の iPad ではかなりうまく機能する。

編集: iPhone 4 の OS について間違っていました。6.x だと思っていましたが、5.1.1 でした。

この問題は、iPhone 4 (iOS 5.1.1) および iPhone 5 (iOS 6) でアプリのテストを開始したときに発生します。コードにタイミングの問題があることがわかります。

ユーザーは、リモートのデジタル メディア レシーバー (DMR) で再生/表示するメディアのアイテムを選択します。

私のコードは libupnp を呼び出し、これを実現するための soap コマンドを作成します。次に、ソケットを作成する http_RequestAndResponse() を呼び出し、ホストに connect() を呼び出し、sock_read_write を呼び出す http_SendMessage を呼び出し (この関数は後でメッセージに含めます)、作成した要求 (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 には影響しなかった) のようなフラグを send() にしようとしました。

どうやらタイミングの問題のようです。iPad と iPod では、牛が帰ってくるまで音楽を再生できます。iPhone 4 と iPhone 5 ではハングします。

助言がありますか?(RTFM への提案、man ページを読む、本を読むなどの提案は、具体的にどれがこれに答えているかを教えてくれれば、喜んで受け入れられます...)

ああ、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

さて、これは興味深いものではありません...

私のコードには2つの問題がありました:

1) 誰かが構成ファイルを変更し、都合よく -DSO_NOSIGPIPE をコンパイルから削除しました。常に詳細をチェックする価値があります。

2) libupnp の sock_read_write() にバグがあるようです。

-DSO_NOSIGPIPE が定義されている場合、send または recv が試行されるたびに選択が行われ、/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 を 1 つの場所で処理していて、別の場所で処理している!」と考えています。

select() ステートメントも SIGPIPE を返すことができます。

上記の sighandler を削除し、SO_NOSIGPIPE プロパティが適用される "#ifdef SO_NOSIGPIPE" セクションを select の上に移動したところ、問題は完全に解消されました。

select() が EPIPE のために失敗した場合、select() は -1 を返します。これは次の数行でキャッチされ、関数は UPNP_E_SOCKET_ERROR で終了します。単に無視されるのではなく、適切に処理できます。

ここで起こっていることを完全に誤解している可能性は十分にあります。その場合、教育を受けることを楽しみにしています。

しかし、確実にまた信頼できるネットワーク通信を楽しんでいます。

これが誰かに役立つことを願っています。

-ケン

于 2013-03-20T23:46:15.943 に答える