live555在Linux环境下会因为客户端异常掉线后,偶现导致部分通道不能再被访问的问题,我们描述一下这个问题修复的过程。
本来是分析服务端CLOSE_WAIT的问题,在调试的过程中, 却又发现了阻塞,认真分析了live555的事件调用流程后,发现只要从客户端收到数据,就会执行GenericMediaServer::ClientSession::noteLiveness(),在该函数中会删除上一次的超时事件,然后新建一个超时事件放入调度队列中,而客户端突然掉线,正常情况是会调用该超时任务, 在该任务中删除ClientSession, 在大量的客户端连接的情况下(128路)突然掉线, 就有几率会出现阻塞,从而导致没有调用到该超时事件,进而导致socket不能释放了。
销毁上一次的超时任务,创建一个新的超时任务:
void GenericMediaServer::ClientSession::noteLiveness() {
if (fOurServerMediaSession != NULL) fOurServerMediaSession->noteLiveness();
if (fOurServer.fReclamationSeconds > 0) {
pEnvironment->taskScheduler().rescheduleDelayedTask(fLivenessCheckTask,
fOurServer.fReclamationSeconds*1000000,
(TaskFunc*)livenessTimeoutTask, this);
}
}
超时任务实现如下:
void GenericMediaServer::ClientSession::livenessTimeoutTask(ClientSession* clientSession) {
delete clientSession;
}
加了N多的打印日志, 最终分析为epoll检测到了socket有事件,并且调用了相应的处理函数, 在该函数中,调用了recvfrom而导致阻塞… 恍然大悟, 赶紧搜一下SO_RCVTIMEO,果然,在live555所有代码中,没有搜到该关键字。
解决问题
在创建socket后,设置接收数据超时时间即可。
#if defined(__WIN32__) || defined(_WIN32)
DWORD msto = (DWORD)500;
setsockopt(newSocket, SOL_SOCKET, SO_SNDTIMEO, (char *)&msto, sizeof(msto) );
setsockopt(newSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&msto, sizeof(msto) );
#else
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500*1000;
setsockopt(newSocket, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof tv);
setsockopt(newSocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof tv);
#endif