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;
}
ありがとう!
-ケン