live555 源码分析(六:mediaServer分析(上) )

“ LIVE555媒体服务器”是完整的RTSP服务器应用程序。 它可以流式传输几种媒体文件。详情可以回去看第一篇的1.5节。

6.1 main函数

mediaServer是一个完整RTSP服务器程序,所以我们还是从main函数开始看起,然后通过创建的类的对象,画一个类图,把前面学习过的类全部串联起来,这样才清楚明白很多。

我把打印的信息去掉

int main(int argc, char** argv) {
  // Begin by setting up our usage environment:
  TaskScheduler* scheduler = BasicTaskScheduler::createNew();
  UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);

  UserAuthenticationDatabase* authDB = NULL;
#ifdef ACCESS_CONTROL
  // 要对RTSP服务器实施客户端访问控制,请执行以下操作:
  authDB = new UserAuthenticationDatabase;
  authDB->addUserRecord("username1", "password1"); 
  // 用真实的字符串替换它们。对要允许访问服务器的每个<用户名>,<密码>重复上述操作。
#endif

  // Create the RTSP server.  Try first with the default port number (554),
  // and then with the alternative port number (8554):
  RTSPServer* rtspServer;
  portNumBits rtspServerPortNum = 554;
  rtspServer = DynamicRTSPServer::createNew(*env, rtspServerPortNum, authDB);
  if (rtspServer == NULL) {
    rtspServerPortNum = 8554;
    rtspServer = DynamicRTSPServer::createNew(*env, rtspServerPortNum, authDB);
  }
  if (rtspServer == NULL) {
    *env << "Failed to create RTSP server: " << env->getResultMsg() << "\n";
    exit(1);
  }

  *env << "LIVE555 Media Server\n";
  *env << "\tversion " << MEDIA_SERVER_VERSION_STRING
       << " (LIVE555 Streaming Media library version "
       << LIVEMEDIA_LIBRARY_VERSION_STRING << ").\n";

  char* urlPrefix = rtspServer->rtspURLPrefix();

  // 另外,尝试为HTTP上的RTSP隧道创建HTTP服务器。
  // Try first with the default HTTP port (80), and then with the alternative HTTP
  // port numbers (8000 and 8080).

  if (rtspServer->setUpTunnelingOverHTTP(80) || rtspServer->setUpTunnelingOverHTTP(8000) || rtspServer->setUpTunnelingOverHTTP(8080)) {
    *env << "(We use port " << rtspServer->httpServerPortNum() << " for optional RTSP-over-HTTP tunneling, or for HTTP live streaming (for indexed Transport Stream files only).)\n";
  } else {
    *env << "(RTSP-over-HTTP tunneling is not available.)\n";
  }

  env->taskScheduler().doEventLoop(); // does not return

  return 0; // only to prevent compiler warning
}

main函数比较简单,做的事情不多:

  1. 创建了延时调度任务的对象,BasicTaskScheduler。
  2. 创建基础打印对象,BasicUsageEnvironment。
  3. 定义ACCESS_CONTROL宏,就需要访问权限,创建访问权限的对象UserAuthenticationDatabase。
  4. 创建RTSP对象,这个以前没看过,现在要仔细分析分析,先尝试端口554,然后再尝试8554.
  5. 创建HTTP隧道端口,也是先尝试80,然后8000,最后8080。
  6. 最后调用循环框架doEventLoop。

6.2 基础部分

基础部分,前面几节已经分析过了,现在提供一个类图,根据这个类图就会很好的记住,前面分析的类,类太多了,还是需要用图的方式来记忆,这个靠谱。

live555 源码分析(六:mediaServer分析(上) )_第1张图片
这个图内容比较多,我们前面就是讲了这么多个类,这里也刚好回忆回忆。

6.3 RTSP对象

这次学聪明了,先搞类图,通过类图分析。

live555 源码分析(六:mediaServer分析(上) )_第2张图片
这个过程就是在创建RTSP的对象的时候,执行的路径。

我们现在一个一个看。

6.3.1 DynamicRTSPServer::createNew

DynamicRTSPServer*
DynamicRTSPServer::createNew(UsageEnvironment& env, Port ourPort,
			     UserAuthenticationDatabase* authDatabase,
			     unsigned reclamationTestSeconds) {
  int ourSocket = setUpOurSocket(env, ourPort);     //创建一个socket,这个等下再看
  if (ourSocket == -1) return NULL; 				//  判断这个socket是否有效

  //调用构造函数
  return new DynamicRTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);
}

reclamationTestSeconds:有一个默认参数的=65

6.3.2 DynamicRTSPServer::DynamicRTSPServer

DynamicRTSPServer::DynamicRTSPServer(UsageEnvironment& env, int ourSocket,
				     Port ourPort,
				     UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds)
  : RTSPServerSupportingHTTPStreaming(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds) {
}

这个构造函数就是往父类的构造函数执行,一路执行。

6.3.3 RTSPServerSupportingHTTPStreaming::RTSPServerSupportingHTTPStreaming

RTSPServerSupportingHTTPStreaming
::RTSPServerSupportingHTTPStreaming(UsageEnvironment& env, int ourSocket, Port rtspPort,
				    UserAuthenticationDatabase* authDatabase, unsigned reclamationTestSeconds)
  : RTSPServer(env, ourSocket, rtspPort, authDatabase, reclamationTestSeconds) {
}

由调用父类的构造函数。

6.3.4 RTSPServer::RTSPServer

RTSPServer::RTSPServer(UsageEnvironment& env,
		       int ourSocket, Port ourPort,
		       UserAuthenticationDatabase* authDatabase,
		       unsigned reclamationSeconds)
  : GenericMediaServer(env, ourSocket, ourPort, reclamationSeconds),
    fHTTPServerSocket(-1), fHTTPServerPort(0),
    fClientConnectionsForHTTPTunneling(NULL), // will get created if needed
    fTCPStreamingDatabase(HashTable::create(ONE_WORD_HASH_KEYS)),
    fPendingRegisterOrDeregisterRequests(HashTable::create(ONE_WORD_HASH_KEYS)),
    fRegisterOrDeregisterRequestCounter(0), fAuthDB(authDatabase), fAllowStreamingRTPOverTCP(True) {
}

这个构造函数也调用了父类的构造函数,通过也初始化了一些变量。
fHTTPServerSocket
fHTTPServerPort
fClientConnectionsForHTTPTunneling
fTCPStreamingDatabase
fPendingRegisterOrDeregisterRequests
fRegisterOrDeregisterRequestCounter
fAuthDB:访问权限变量
fAllowStreamingRTPOverTCP

6.3.5 GenericMediaServer::GenericMediaServer

GenericMediaServer
::GenericMediaServer(UsageEnvironment& env, int ourSocket, Port ourPort,
		     unsigned reclamationSeconds)
  : Medium(env),
    fServerSocket(ourSocket), fServerPort(ourPort), fReclamationSeconds(reclamationSeconds),
    fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)),
    fClientConnections(HashTable::create(ONE_WORD_HASH_KEYS)),
    fClientSessions(HashTable::create(STRING_HASH_KEYS)),
    fPreviousClientSessionId(0)
{
  ignoreSigPipeOnSocket(fServerSocket); // so that clients on the same host that are killed don't also kill us
  
  // Arrange to handle connections from others:
  env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);
}

int fServerSocket
Port fServerPort
unsigned fReclamationSeconds
HashTable* fServerMediaSessions
HashTable* fClientConnections
HashTable* fClientSessions
u_int32_t fPreviousClientSessionId

env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);

要监听的 Server socket 及该 socket 上的 I/O 事件处理程序,注册给任务调度器。事件循环中检测到 socket 上出现 I/O 事件时,该处理程序会被调用到。注册的事件处理程序为 GenericMediaServer::incomingConnectionHandler() 。
(会把用到底层这些数据结构的都画在一张图中,这样比较简单明了)
live555 源码分析(六:mediaServer分析(上) )_第3张图片

6.3.6 ignoreSigPipeOnSocket

这是一个单独的函数,网络有关的,可以看一下其他博客

void ignoreSigPipeOnSocket(int socketNum) {
  #ifdef USE_SIGNALS
  #ifdef SO_NOSIGPIPE
  int set_option = 1;
  setsockopt(socketNum, SOL_SOCKET, SO_NOSIGPIPE, &set_option, sizeof set_option);
  #else
  signal(SIGPIPE, SIG_IGN);
  #endif
  #endif
}

已发现一个问题,对一个对端已经关闭的socket调用两次write, 第二次将会生成SIGPIPE信号, 该信号默认结束进程.
https://blog.csdn.net/weixin_33841722/article/details/92183262
https://blog.csdn.net/xinguan1267/article/details/17357093

6.3.7 Medium::Medium

Medium::Medium(UsageEnvironment& env)
	: fEnviron(env), fNextTask(NULL) {
  // First generate a name for the new medium:
  MediaLookupTable::ourMedia(env)->generateNewName(fMediumName, mediumNameMaxLen);
  env.setResultMsg(fMediumName);

  // Then add it to our table:
  MediaLookupTable::ourMedia(env)->addNew(this, fMediumName);
}

Medium类中也保存了一个env的变量,不知道是做啥用的,先不管,往下走。
UsageEnvironment& fEnviron
char fMediumName[mediumNameMaxLen]: //存储名字和编号的
TaskToken fNextTask

6.3.8 MediaLookupTable::ourMedia

接下来就分析一下Medium的构造函数。
先看第一个函数,

MediaLookupTable* MediaLookupTable::ourMedia(UsageEnvironment& env) {
  _Tables* ourTables = _Tables::getOurTables(env);
  if (ourTables->mediaTable == NULL) {
    // 创建一个新表来记录要在此环境中创建的媒体:
    ourTables->mediaTable = new MediaLookupTable(env);   //这是构造函数
  }
  return ourTables->mediaTable;
}

这里就有一个中间类,_Table类,我现在也不知道这个类是干嘛的,不过先分析,分析到后面,自然就知道了。

class _Tables {
public:
  static _Tables* getOurTables(UsageEnvironment& env, Boolean createIfNotPresent = True);
      // returns a pointer to a "_Tables" structure (creating it if necessary)
  void reclaimIfPossible();
      // used to delete ourselves when we're no longer used

  MediaLookupTable* mediaTable;
  void* socketTable;

protected:
  _Tables(UsageEnvironment& env);
  virtual ~_Tables();

private:
  UsageEnvironment& fEnv;
};

这个_Table类也是挺简单的,就是有一个fEnv的变量,这个_Table也存储了一个env变量。

下面我们来看看实际操作的函数:

_Tables* _Tables::getOurTables(UsageEnvironment& env, Boolean createIfNotPresent) {
  if (env.liveMediaPriv == NULL && createIfNotPresent) {
    env.liveMediaPriv = new _Tables(env);
  }
  return (_Tables*)(env.liveMediaPriv);
}

void _Tables::reclaimIfPossible() {
  if (mediaTable == NULL && socketTable == NULL) {
    fEnv.liveMediaPriv = NULL;
    delete this;
  }
}

_Tables::_Tables(UsageEnvironment& env)
  : mediaTable(NULL), socketTable(NULL), fEnv(env) {
}

_Tables::~_Tables() {
}

这些函数也挺简单的,就要注意两个变量:
env.liveMediaPriv:这个是env中的一个指针,当初不知道是咋回事,现在知道在这里赋值了,所以要记住。
UsageEnvironment& fEnv:还有一个就是他自己的env,这个env也是上层一层一层传下来的,变量是同一个。
MediaLookupTable mediaTable*:
void socketTable*:

6.3.9 MediaLookupTable::MediaLookupTable

不知道不觉中来了到构造函数。这个MediaLookupTable是负责管理Medium的。

MediaLookupTable::MediaLookupTable(UsageEnvironment& env)
  : fEnv(env), fTable(HashTable::create(STRING_HASH_KEYS)), fNameGenerator(0) {
}

UsageEnvironment& fEnv:还是env那个变量
HashTable fTable*: 创建了一个哈希表
unsigned fNameGenerator: liveMedium名字的编号

可以看看MediaLookupTable这个类定义:

// 通过字符串名称查找Medium的数据结构.
// (它仅用于实现“ Medium”,但是如果开发人员希望使用它来迭代我们创建的整个“ Medium”对象集,则在此处将其可见.)
class MediaLookupTable {
public:
  static MediaLookupTable* ourMedia(UsageEnvironment& env);
  HashTable const& getTable() { return *fTable; }

protected:
  MediaLookupTable(UsageEnvironment& env);
  virtual ~MediaLookupTable();

private:
  friend class Medium;

  Medium* lookup(char const* name) const;
  // Returns NULL if none already exists

  void addNew(Medium* medium, char* mediumName);
  void remove(char const* name);

  void generateNewName(char* mediumName, unsigned maxLen);

private:
  UsageEnvironment& fEnv;
  HashTable* fTable;
  unsigned fNameGenerator;   // liveMedium名字的编号
};

6.3.10 MediaLookupTable::generateNewName

void MediaLookupTable::generateNewName(char* mediumName,
				       unsigned /*maxLen*/) {
  // 我们确实应该在这里使用snprintf(),但并非所有系统都拥有它
  sprintf(mediumName, "liveMedia%d", fNameGenerator++);
}

这个函数比较简单,就是用sprintf设置一个名字。

6.3.11 MediaLookupTable::addNew

然后我们来到最后一个函数

void MediaLookupTable::addNew(Medium* medium, char* mediumName) {
  fTable->Add(mediumName, (void*)medium);
}

这个函数就是把medium对象的指针,和medium名字存储到哈希表中。
在需要用到的时候使用。
live555 源码分析(六:mediaServer分析(上) )_第4张图片
虽然把这个创建的过程追踪下来了,但是这一层一层的继承,关系太多了,变量也太多了,所以还不是太明白,不过先这样分析,以后多分析分析,可能就会顿悟了,加油。

你可能感兴趣的:(LIVE555)