RTSP协议是一个流媒体协议,可以实现直播和点播形式的音频与视频流的播放。RTSP协议定义了多种服务器-客户端之间交互的接口,主要有OPTIONS,DESCRIBE,SETUP,PLAY,TEARDOWN,RECORD,ANNOUNCE。网络上已经有很多针对RTSP协议的文章,我这里不准备进行过多介绍。RTSP并不包括具体数据的传输,该功能一般由RTP与RTCP协议来实现,并可以通过TCP或UDP两种底层传输方式进行。

下图是典型的RTSP直播过程中服务端-客户端主要交互过程:

EasyRTSPServer是一套稳定、高效、可靠、多平台支持的RTSP-Server组件, 接口调用非常简单成熟,今天为大家分享关于EasyRTSPServer客户端异常掉线导致部分通道阻塞的问题

提出问题:

EasyRTSPServer在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
02-12 09:14