Live555库是一个使用开放标准协议如RTP/RTCP、RTSP、SIP等实现多媒体流式传输的开源C 库集。这些函数库可以在Unix、Windows、QNX等操作系统下编译使用,基于此建立RTSP/SIP服务器和客户端来实现多媒体流的传输。下面给出具体实现过程:

(1)客户端发起RTSP OPTION请求,目的是得到服务器提供什么方法。RTSP提供的方法一般包括OPTIONS、DESCRIBE、SETUP、TEARDOWN、PLAY、PAUSE、SCALE、GET_PARAMETER。

(2)服务器对RTSP OPTION回应,服务器实现什么方法就回应哪些方法。在此系统中,我们只对DESCRIBE、SETUP、TEARDOWN、PLAY、PAUSE方法做了实现。

(3)客户端发起RTSP DESCRIBE请求,服务器收到的信息主要有媒体的名字,解码类型,视频分辨率等描述,目的是为了从服务器那里得到会话描述信息(SDP)。

(4)服务器对RTSP DESCRIBE响应,发送必要的媒体参数,在传输H.264文件时,主要包括SPS/PPS、媒体名、传输协议等信息。

(5)客户端发起RTSP SETUP请求,目的是请求会话建立并准备传输。请求信息主要包括传输协议和客户端端口号。

(6)服务器对RTSP SETUP响应,发出相应服务器端的端口号和会话标识符。

(7)客户端发出了RTSP PLAY的请求,目的是请求播放视频流。

(8)服务器对RTSP PLAY响应,响应的消息包括会话标识符,RTP包的序列号,时间戳。此时服务器对H264视频流封装打包进行传输。

(9)客户端发出RTSP TEARDOWN请求,目的是关闭连接,终止传输。

(10)服务器关闭连接,停止传输。

 RTSPServer类用于构建一个RTSP服务器,该类同时在其内部定义了一个RTSPClientSession类,用于处理单独的客户会话。

  首先创建RTSP服务器(具体实现类是DynamicRTSPServer),在创建过程中,先建立Socket(ourSocket)在TCP的554端口进行监听,然后把连接处理函数句柄(RTSPServer::incomingConnectionHandler)和socket句柄传给任务调度器(taskScheduler)。

      任务调度器把socket句柄放入后面select调用中用到的socket句柄集(fReadSet)中,同时将socket句柄和incomingConnectionHandler句柄关联起来。接着,主程序开始进入任务调度器的主循环(doEventLoop),在主循环中调用系统函数select阻塞,等待网络连接。

      当RTSP客户端输入(rtsp://192.168.0.1/1.mpg)连接服务器时,select返回对应的socket,进而根据前面保存的对应关系,可找到对应处理函数句柄,这里就是前面提到的incomingConnectionHandler了。在incomingConnectionHandler中创建了RTSPClientSession,开始对这个客户端的会话进行处理。


废话不多说,直接上源码。RTSP服务器的程序入口在文件 mediaServer/live555MediaServer.cpp。下面我们先来看下其main()函数里到底做了什么:(注意中文注释

int main(int argc, char** argv) 
{
  //下面两句话主要做了三件事:1、初始化scheduler及env实例并在scheduler里面新建一个空延时队列任务,2、初始化env的打印缓冲区 3、将scheduler与env关联(成员变量的方式)
  // Begin by setting up our usage environment:
  TaskScheduler* scheduler = BasicTaskScheduler::createNew();
  UsageEnvironment* env = BasicUsageEnvironment::createNew(*scheduler);

  UserAuthenticationDatabase* authDB = NULL;

//如果要设置服务器仅对有权限的客户端开放,则添加指定的用户名及密码。实际上就是把用户密码添加到//authDB的HASH表中,之后服务器在接收客户端的SETUP请求时,做相应的判断
#ifdef ACCESS_CONTROL
  // To implement client access control to the RTSP server, do the following:
  authDB = new UserAuthenticationDatabase;
  authDB->addUserRecord("username1", "password1"); // replace these with real strings
  // Repeat the above with each <username>, <password> that you wish to allow
  // access to the server.
#endif

  //创建一个server实例,并设置客户端请求到达时的回调函数
  // 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);
  }

  // Also, attempt to create a HTTP server for RTSP-over-HTTP tunneling.
  // Try first with the default HTTP port (80), and then with the alternative HTTP
  // port numbers (8000 and 8080).

  //尝试让RTSP报文通过HTTP端口(即80端口)通信,并重置客户端请求到达时的回调函数。
  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
}

初始化scheduler及env实例没什么可说的,下面我们继续深入查看创建服务器实例DynamicRTSPServer::createNew的代码:

DynamicRTSPServer* DynamicRTSPServer::createNew(UsageEnvironment& env, Port ourPort,
                 UserAuthenticationDatabase* authDatabase,
                 unsigned reclamationTestSeconds)
 {
  //创建一个不阻塞、保活的套接字并使其处于监听状态
  int ourSocket = setUpOurSocket(env, ourPort);
  if (ourSocket == -1) return NULL;

  //创建服务器实例
  return new DynamicRTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);
}

//经过多个继承的关系走到这里,之前什么都没有做
//初始化列表中的参数主要都是用于RTSP-OVER-HTTP的,先记住这些参数已经初始化即可
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) 
{

}

GenericMediaServer::GenericMediaServer(UsageEnvironment& env, int ourSocket, Port ourPort,
             unsigned reclamationSeconds)
  : Medium(env),
    /*设置服务器的socket及port*/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
  
  //设置服务器请求到来时,回调函数为:incomingConnectionHandler 并添加到scheduler的handler中
  // Arrange to handle connections from others:
  env.taskScheduler().turnOnBackgroundReadHandling(fServerSocket, incomingConnectionHandler, this);
}

下面我们继续往下走,看下RTSPServer::setUpTunnelingOverHTTP()的实现:

Boolean RTSPServer::setUpTunnelingOverHTTP(Port httpPort) 
{
  //尝试新建HTTP套接字
  fHTTPServerSocket = setUpOurSocket(envir(), httpPort);
  if (fHTTPServerSocket >= 0) {
    fHTTPServerPort = httpPort;
    //如果使用了RTSP-OVER-HTTP,则修改服务器收到请求后的处理函数
    envir().taskScheduler().turnOnBackgroundReadHandling(fHTTPServerSocket,
                             incomingConnectionHandlerHTTP, this);
    return True;
  }

至此整个流程大概通了,为了帮助记忆,我用简单的语言进行整理一下:

系统环境初始化->创建服务器实例(端口号:554),并设置客户端连接请求的回调函数->尝试设置RTSP-OVER-HTTP服务,并修改服务器端口号及客户端请求的回调函数->事件循环

下面我们再来看下,客户端连接请求的回调函数里面到底做了什么?以HTTP为例

//什么都没有做,往下走
void RTSPServer::incomingConnectionHandlerHTTP(void* instance, int /*mask*/) {
  RTSPServer* server = (RTSPServer*)instance;
  server->incomingConnectionHandlerHTTP();
}
//什么都没有做,继续往下
void RTSPServer::incomingConnectionHandlerHTTP() {
  incomingConnectionHandlerOnSocket(fHTTPServerSocket);
}
//这里才是正文
void GenericMediaServer::incomingConnectionHandlerOnSocket(int serverSocket) {
  struct sockaddr_in clientAddr;
  SOCKLEN_T clientAddrLen = sizeof clientAddr;
  //接受客户端的请求
  int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
  if (clientSocket < 0) {
    int err = envir().getErrno();
    if (err != EWOULDBLOCK) {
      envir().setResultErrMsg("accept() failed: ");
    }
    return;
  }
  //确保同一台主机的客户端进程被杀后,服务器正常
  ignoreSigPipeOnSocket(clientSocket); // so that clients on the same host that are killed don't also kill us
  //设置套接字属性为不阻塞,并增大其发送缓冲区的字节数5K
  makeSocketNonBlocking(clientSocket);
  increaseSendBufferTo(envir(), clientSocket, 50*1024);
  
#ifdef DEBUG
  envir() << "accept()ed connection from " << AddressString(clientAddr).val() << "\n";
#endif
  //依据客户端的socket,创建一个客户端交互的实例,之后客户端发来的数据直接交给它处理
  // Create a new object for handling this connection:
  (void)createNewClientConnection(clientSocket, clientAddr);
}

我们接着来看看服务器如何创建客户端交互实例的,如果不想看代码的 话,我这里直接总结一下吧:其实就是为某一个客户端连接创建一个客户端交互的实例,所有从客户端发来的数据,都经过这个客户端交互实例进行处理:

//仅仅一层封装,我们继续往下
RTSPServerSupportingHTTPStreaming::createNewClientConnection(int clientSocket, struct sockaddr_in clientAddr) {
  return new RTSPClientConnectionSupportingHTTPStreaming(*this, clientSocket, clientAddr);
}
//还是一层封装,我们继续往下
RTSPServerSupportingHTTPStreaming::RTSPClientConnectionSupportingHTTPStreaming
::RTSPClientConnectionSupportingHTTPStreaming(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
  : RTSPClientConnection(ourServer, clientSocket, clientAddr),
    fClientSessionId(0), fStreamSource(NULL), fPlaylistSource(NULL), fTCPSink(NULL) {
}

//初始化一些变量,然后还是一层封装,我们继续往下
RTSPServer::RTSPClientConnection
::RTSPClientConnection(RTSPServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
  : GenericMediaServer::ClientConnection(ourServer, clientSocket, clientAddr),
    fOurRTSPServer(ourServer), fClientInputSocket(fOurSocket), fClientOutputSocket(fOurSocket),
    fIsActive(True), fRecursionCount(0), fOurSessionCookie(NULL) {
  //初始化客户端数据缓冲区
  resetRequestBuffer();
}

//初始化一些变量,然后还是一层封装,我们继续往下看incomingRequestHandler()函数
GenericMediaServer::ClientConnection
::ClientConnection(GenericMediaServer& ourServer, int clientSocket, struct sockaddr_in clientAddr)
  : fOurServer(ourServer), fOurSocket(clientSocket), fClientAddr(clientAddr) {
  // Add ourself to our 'client connections' table:
  fOurServer.fClientConnections->Add((char const*)this, this);
  
  // Arrange to handle incoming requests:
  //初始化客户端数据缓冲区
  resetRequestBuffer();
  //加入到调度任务中,当有数据可读时,将调用incomingRequestHandler函数进行处理
  envir().taskScheduler()
    .setBackgroundHandling(fOurSocket, SOCKET_READABLE|SOCKET_EXCEPTION, incomingRequestHandler, this);
}

//数据到来时,该函数将被调度任务调用
void GenericMediaServer::ClientConnection::incomingRequestHandler(void* instance, int /*mask*/) {
  ClientConnection* connection = (ClientConnection*)instance;
  connection->incomingRequestHandler();
}

//这里才是重点
void GenericMediaServer::ClientConnection::incomingRequestHandler() {
  struct sockaddr_in dummy; // 'from' address, meaningless in this case
  //读出客户端发送的数据,放到待处理的位置
  int bytesRead = readSocket(envir(), fOurSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy);
//处理客户端的数据内容
  handleRequestBytes(bytesRead);
}

至此客户端的请求已经接受了,也收到了客户端的请求数据了,接下来来看看这个客户端实例是如何处理客户端的数据的,带着这个疑问,我们进行深入看下handleRequestBytes()函数。需要说明的是无论是RTSP还是HTTP,最终的数据处理函数都是handleRequestBytes()函数。

void RTSPServer::RTSPClientConnection::handleRequestBytes(int newBytesRead) {
  //剩余个数数据处理
  int numBytesRemaining = 0;
  ++fRecursionCount;
  
  do {
    RTSPServer::RTSPClientSession* clientSession = NULL;
    //如果待读的数据大于缓冲区,则直接报错
    if (newBytesRead < 0 || (unsigned)newBytesRead >= fRequestBufferBytesLeft) {
      // Either the client socket has died, or the request was too big for us.
      // Terminate this connection:
#ifdef DEBUG
      fprintf(stderr, "RTSPClientConnection[%p]::handleRequestBytes() read %d new bytes (of %d); terminating connection!\n", this, newBytesRead, fRequestBufferBytesLeft);
#endif
      fIsActive = False;
      break;
    }
    
    Boolean endOfMsg = False;
    unsigned char* ptr = &fRequestBuffer[fRequestBytesAlreadySeen];
#ifdef DEBUG
    ptr[newBytesRead] = '\0';
    fprintf(stderr, "RTSPClientConnection[%p]::handleRequestBytes() %s %d new bytes:%s\n",
        this, numBytesRemaining > 0 ? "processing" : "read", newBytesRead, ptr);
#endif
    //这个不用管,base64的东西,很少用到
    if (fClientOutputSocket != fClientInputSocket && numBytesRemaining == 0) {
      // We're doing RTSP-over-HTTP tunneling, and input commands are assumed to have been Base64-encoded.
      // We therefore Base64-decode as much of this new data as we can (i.e., up to a multiple of 4 bytes).
      
      // But first, we remove any whitespace that may be in the input data:
      unsigned toIndex = 0;
      for (int fromIndex = 0; fromIndex < newBytesRead; ++fromIndex) {
    char c = ptr[fromIndex];
    if (!(c == ' ' || c == '\t' || c == '\r' || c == '\n')) { // not 'whitespace': space,tab,CR,NL
      ptr[toIndex++] = c;
    }
      }
      newBytesRead = toIndex;
      
      unsigned numBytesToDecode = fBase64RemainderCount + newBytesRead;
      unsigned newBase64RemainderCount = numBytesToDecode%4;
      numBytesToDecode -= newBase64RemainderCount;
      if (numBytesToDecode > 0) {
    ptr[newBytesRead] = '\0';
    unsigned decodedSize;
    unsigned char* decodedBytes = base64Decode((char const*)(ptr-fBase64RemainderCount), numBytesToDecode, decodedSize);
#ifdef DEBUG
    fprintf(stderr, "Base64-decoded %d input bytes into %d new bytes:", numBytesToDecode, decodedSize);
    for (unsigned k = 0; k < decodedSize; ++k) fprintf(stderr, "%c", decodedBytes[k]);
    fprintf(stderr, "\n");
#endif
    
    // Copy the new decoded bytes in place of the old ones (we can do this because there are fewer decoded bytes than original):
    unsigned char* to = ptr-fBase64RemainderCount;
    for (unsigned i = 0; i < decodedSize; ++i) *to++ = decodedBytes[i];
    
    // Then copy any remaining (undecoded) bytes to the end:
    for (unsigned j = 0; j < newBase64RemainderCount; ++j) *to++ = (ptr-fBase64RemainderCount+numBytesToDecode)[j];
    
    newBytesRead = decodedSize - fBase64RemainderCount + newBase64RemainderCount;
      // adjust to allow for the size of the new decoded data (+ remainder)
    delete[] decodedBytes;
      }
      fBase64RemainderCount = newBase64RemainderCount;
    }
    
    unsigned char* tmpPtr = fLastCRLF + 2;
    if (fBase64RemainderCount == 0) { // no more Base-64 bytes remain to be read/decoded
      //依据\r\n\r\n找到有效RTSP报文的结尾
      // Look for the end of the message: <CR><LF><CR><LF>
      if (tmpPtr < fRequestBuffer) tmpPtr = fRequestBuffer;
      while (tmpPtr < &ptr[newBytesRead-1]) {
    if (*tmpPtr == '\r' && *(tmpPtr+1) == '\n') {
      if (tmpPtr - fLastCRLF == 2) { // This is it:
        endOfMsg = True;
        break;
      }
      fLastCRLF = tmpPtr;
    }
    ++tmpPtr;
      }
    }
    //记录剩余缓冲区字节数及正在处理的字节数
    fRequestBufferBytesLeft -= newBytesRead;
    fRequestBytesAlreadySeen += newBytesRead;
    
    if (!endOfMsg) break; // subsequent reads will be needed to complete the request
    
    // Parse the request string into command name and 'CSeq', then handle the command:
    fRequestBuffer[fRequestBytesAlreadySeen] = '\0';
    char cmdName[RTSP_PARAM_STRING_MAX];
    char urlPreSuffix[RTSP_PARAM_STRING_MAX];
    char urlSuffix[RTSP_PARAM_STRING_MAX];
    char cseq[RTSP_PARAM_STRING_MAX];
    char sessionIdStr[RTSP_PARAM_STRING_MAX];
    unsigned contentLength = 0;
    Boolean playAfterSetup = False;
    fLastCRLF[2] = '\0'; // temporarily, for parsing
   //解析报文,解析命令行、url、seq、报文长度以及session
    Boolean parseSucceeded = parseRTSPRequestString((char*)fRequestBuffer, fLastCRLF+2 - fRequestBuffer,
                            cmdName, sizeof cmdName,
                            urlPreSuffix, sizeof urlPreSuffix,
                            urlSuffix, sizeof urlSuffix,
                            cseq, sizeof cseq,
                            sessionIdStr, sizeof sessionIdStr,
                            contentLength);
    fLastCRLF[2] = '\r'; // restore its value
    //如果报文内含有"Content-Length"字段,则依据此字段,判断报文是否接收完毕
    // Check first for a bogus "Content-Length" value that would cause a pointer wraparound:
    if (tmpPtr + 2 + contentLength < tmpPtr + 2) {
#ifdef DEBUG
      fprintf(stderr, "parseRTSPRequestString() returned a bogus \"Content-Length:\" value: 0x%x (%d)\n", contentLength, (int)contentLength);
#endif
      contentLength = 0;
      parseSucceeded = False;
    }
    //报文解析成功
    if (parseSucceeded) {
#ifdef DEBUG
      fprintf(stderr, "parseRTSPRequestString() succeeded, returning cmdName \"%s\", urlPreSuffix \"%s\", urlSuffix \"%s\", CSeq \"%s\", Content-Length %u, with %d bytes following the message.\n", cmdName, urlPreSuffix, urlSuffix, cseq, contentLength, ptr + newBytesRead - (tmpPtr + 2));
#endif
      // If there was a "Content-Length:" header, then make sure we've received all of the data that it specified:
      if (ptr + newBytesRead < tmpPtr + 2 + contentLength) break; // we still need more data; subsequent reads will give it to us 
      
      // If the request included a "Session:" id, and it refers to a client session that's
      // current ongoing, then use this command to indicate 'liveness' on that client session:
      Boolean const requestIncludedSessionId = sessionIdStr[0] != '\0';
      //如果报文内含有session,则依据此session找到客户端交互实例
      if (requestIncludedSessionId) {
    clientSession
      = (RTSPServer::RTSPClientSession*)(fOurRTSPServer.lookupClientSession(sessionIdStr));
    if (clientSession != NULL) clientSession->noteLiveness();
      }
    
      // We now have a complete RTSP request.
      // Handle the specified command (beginning with commands that are session-independent):
      fCurrentCSeq = cseq;
      if (strcmp(cmdName, "OPTIONS") == 0) {
    // If the "OPTIONS" command included a "Session:" id for a session that doesn't exist,
    // then treat this as an error:
    if (requestIncludedSessionId && clientSession == NULL) {
#ifdef DEBUG
      fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 1)\n");
#endif
      handleCmd_sessionNotFound();
    } else {
         //处理“OPTIONS”请求
      // Normal case:
      handleCmd_OPTIONS();
    }
      } else if (urlPreSuffix[0] == '\0' && urlSuffix[0] == '*' && urlSuffix[1] == '\0') {
    // The special "*" URL means: an operation on the entire server.  This works only for GET_PARAMETER and SET_PARAMETER:
         
    if (strcmp(cmdName, "GET_PARAMETER") == 0) {
          //处理“GET_PARAMETER”请求
      handleCmd_GET_PARAMETER((char const*)fRequestBuffer);
    } else if (strcmp(cmdName, "SET_PARAMETER") == 0) {
         //处理“SET_PARAMETER”请求
      handleCmd_SET_PARAMETER((char const*)fRequestBuffer);
    } else {
      handleCmd_notSupported();
    }
      } else if (strcmp(cmdName, "DESCRIBE") == 0) {
       //处理“DESCRIBE”请求
    handleCmd_DESCRIBE(urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
      } else if (strcmp(cmdName, "SETUP") == 0) {
    Boolean areAuthenticated = True;

    if (!requestIncludedSessionId) {
      // No session id was present in the request.
      // So create a new "RTSPClientSession" object for this request.

      // But first, make sure that we're authenticated to perform this command:
      char urlTotalSuffix[2*RTSP_PARAM_STRING_MAX];
          // enough space for urlPreSuffix/urlSuffix'\0'
      urlTotalSuffix[0] = '\0';
      if (urlPreSuffix[0] != '\0') {
        strcat(urlTotalSuffix, urlPreSuffix);
        strcat(urlTotalSuffix, "/");
      }
      strcat(urlTotalSuffix, urlSuffix);
          //这里就是上文说到的客户端认证
      if (authenticationOK("SETUP", urlTotalSuffix, (char const*)fRequestBuffer)) {
        clientSession
          = (RTSPServer::RTSPClientSession*)fOurRTSPServer.createNewClientSessionWithId();
      } else {
        areAuthenticated = False;
      }
    }
    if (clientSession != NULL) {
         //处理“SETUP”请求
      clientSession->handleCmd_SETUP(this, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
      playAfterSetup = clientSession->fStreamAfterSETUP;
    } else if (areAuthenticated) {
#ifdef DEBUG
      fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 2)\n");
#endif
      handleCmd_sessionNotFound();
    }
      }
      //其他请求处理
      else if (strcmp(cmdName, "TEARDOWN") == 0
         || strcmp(cmdName, "PLAY") == 0
         || strcmp(cmdName, "PAUSE") == 0
         || strcmp(cmdName, "GET_PARAMETER") == 0
         || strcmp(cmdName, "SET_PARAMETER") == 0) {
    if (clientSession != NULL) {
      clientSession->handleCmd_withinSession(this, cmdName, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
    } else {
#ifdef DEBUG
      fprintf(stderr, "Calling handleCmd_sessionNotFound() (case 3)\n");
#endif
      handleCmd_sessionNotFound();
    }
      } else if (strcmp(cmdName, "REGISTER") == 0 || strcmp(cmdName, "DEREGISTER") == 0) {
    // Because - unlike other commands - an implementation of this command needs
    // the entire URL, we re-parse the command to get it:
    char* url = strDupSize((char*)fRequestBuffer);
    if (sscanf((char*)fRequestBuffer, "%*s %s", url) == 1) {
      // Check for special command-specific parameters in a "Transport:" header:
      Boolean reuseConnection, deliverViaTCP;
      char* proxyURLSuffix;
      parseTransportHeaderForREGISTER((const char*)fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix);

      handleCmd_REGISTER(cmdName, url, urlSuffix, (char const*)fRequestBuffer, reuseConnection, deliverViaTCP, proxyURLSuffix);
      delete[] proxyURLSuffix;
    } else {
      handleCmd_bad();
    }
    delete[] url;
      } else {
    // The command is one that we don't handle:
    handleCmd_notSupported();
      }
    } 
    //下面是HTTP请求的处理,
    else {
#ifdef DEBUG
      fprintf(stderr, "parseRTSPRequestString() failed; checking now for HTTP commands (for RTSP-over-HTTP tunneling)...\n");
#endif
      // The request was not (valid) RTSP, but check for a special case: HTTP commands (for setting up RTSP-over-HTTP tunneling):
      char sessionCookie[RTSP_PARAM_STRING_MAX];
      char acceptStr[RTSP_PARAM_STRING_MAX];
      *fLastCRLF = '\0'; // temporarily, for parsing
     //解析HTTP报文
      parseSucceeded = parseHTTPRequestString(cmdName, sizeof cmdName,
                          urlSuffix, sizeof urlPreSuffix,
                          sessionCookie, sizeof sessionCookie,
                          acceptStr, sizeof acceptStr);
      *fLastCRLF = '\r';
      if (parseSucceeded) {
#ifdef DEBUG
    fprintf(stderr, "parseHTTPRequestString() succeeded, returning cmdName \"%s\", urlSuffix \"%s\", sessionCookie \"%s\", acceptStr \"%s\"\n", cmdName, urlSuffix, sessionCookie, acceptStr);
#endif
    // Check that the HTTP command is valid for RTSP-over-HTTP tunneling: There must be a 'session cookie'.
    Boolean isValidHTTPCmd = True;
    if (strcmp(cmdName, "OPTIONS") == 0) {
      handleHTTPCmd_OPTIONS();
    } else if (sessionCookie[0] == '\0') {
      // There was no "x-sessioncookie:" header.  If there was an "Accept: application/x-rtsp-tunnelled" header,
      // then this is a bad tunneling request.  Otherwise, assume that it's an attempt to access the stream via HTTP.
      if (strcmp(acceptStr, "application/x-rtsp-tunnelled") == 0) {
        isValidHTTPCmd = False;
      } else {
        handleHTTPCmd_StreamingGET(urlSuffix, (char const*)fRequestBuffer);
      }
    } else if (strcmp(cmdName, "GET") == 0) {
      handleHTTPCmd_TunnelingGET(sessionCookie);
    } else if (strcmp(cmdName, "POST") == 0) {
      // We might have received additional data following the HTTP "POST" command - i.e., the first Base64-encoded RTSP command.
      // Check for this, and handle it if it exists:
      unsigned char const* extraData = fLastCRLF+4;
      unsigned extraDataSize = &fRequestBuffer[fRequestBytesAlreadySeen] - extraData;
      if (handleHTTPCmd_TunnelingPOST(sessionCookie, extraData, extraDataSize)) {
        // We don't respond to the "POST" command, and we go away:
        fIsActive = False;
        break;
      }
    } else {
      isValidHTTPCmd = False;
    }
    if (!isValidHTTPCmd) {
      handleHTTPCmd_notSupported();
    }
      } else {
#ifdef DEBUG
    fprintf(stderr, "parseHTTPRequestString() failed!\n");
#endif
    handleCmd_bad();
      }
    }
    
#ifdef DEBUG
    fprintf(stderr, "sending response: %s", fResponseBuffer);
#endif
    //将组装好的响应包发送出去
    send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
    
    if (playAfterSetup) {
      // The client has asked for streaming to commence now, rather than after a
      // subsequent "PLAY" command.  So, simulate the effect of a "PLAY" command:
      clientSession->handleCmd_withinSession(this, "PLAY", urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
    }
    
    // Check whether there are extra bytes remaining in the buffer, after the end of the request (a rare case).
    // If so, move them to the front of our buffer, and keep processing it, because it might be a following, pipelined request.
    unsigned requestSize = (fLastCRLF+4-fRequestBuffer) + contentLength;
    numBytesRemaining = fRequestBytesAlreadySeen - requestSize;
    resetRequestBuffer(); // to prepare for any subsequent request
    //恢复客户端缓冲区
    if (numBytesRemaining > 0) {
      memmove(fRequestBuffer, &fRequestBuffer[requestSize], numBytesRemaining);
      newBytesRead = numBytesRemaining;
    }
  } while (numBytesRemaining > 0);
  
  --fRecursionCount;
  if (!fIsActive) {
    if (fRecursionCount > 0) closeSockets(); else delete this;
    // Note: The "fRecursionCount" test is for a pathological situation where we reenter the event loop and get called recursively
    // while handling a command (e.g., while handling a "DESCRIBE", to get a SDP description).
    // In such a case we don't want to actually delete ourself until we leave the outermost call.
  }
}

代码有点长,我这里我再把这个处理流程整理一下:收到客户端缓冲区数据后->判断当前缓冲区是否已满,若已满,则拒绝->找到报文的起点及终点->以RTSP方式解析报文,若成功,则依据报文内容进行相应的处理并组装相应报文,若失败,则按HTTP方式解析->将组装好的报文发送出去,并整理客户端缓冲区