在我的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
答案 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