live555峯哥的私房菜(三)-----RTSP會話的建立

RTSPServer*
RTSPServer::createNew(UsageEnvironment& env, Port ourPort,
		      UserAuthenticationDatabase* authDatabase,
		      unsigned reclamationTestSeconds) {
  //建立TCP Socket
  int ourSocket = setUpOurSocket(env, ourPort);
  if (ourSocket == -1) return NULL;
  //創建RTSPServer 偵聽客戶端的socket連接
  return new RTSPServer(env, ourSocket, ourPort, authDatabase, reclamationTestSeconds);
}

RTSPServer::RTSPServer(UsageEnvironment& env,
		       int ourSocket, Port ourPort,
		       UserAuthenticationDatabase* authDatabase,
		       unsigned reclamationTestSeconds)
  : Medium(env),
    fRTSPServerSocket(ourSocket), fRTSPServerPort(ourPort),
    fHTTPServerSocket(-1), fHTTPServerPort(0), fClientSessionsForHTTPTunneling(NULL),
    fAuthDB(authDatabase), fReclamationTestSeconds(reclamationTestSeconds),
    fServerMediaSessions(HashTable::create(STRING_HASH_KEYS)) 
{
  //忽略信號,這樣本機上的客戶端被中斷的時候,服務端不會被中斷
  ignoreSigPipeOnSocket(ourSocket); // so that clients on the same host that are killed don't also kill us

  // Arrange to handle connections from others:
    //將incomingConnectionHandlerRTSP加入任務調度
  env.taskScheduler().turnOnBackgroundReadHandling(
                      fRTSPServerSocket,
                      (TaskScheduler::BackgroundHandlerProc*)&incomingConnectionHandlerRTSP,
                      this);
}
void RTSPServer::incomingConnectionHandlerRTSP(void* instance, int /*mask*/) {
  RTSPServer* server = (RTSPServer*)instance;
  server->incomingConnectionHandlerRTSP1();
}

void RTSPServer::incomingConnectionHandlerRTSP1() {
  incomingConnectionHandler(fRTSPServerSocket);
}

當收到客戶的連接時(accept接收),需保存下代表客戶端的新socket ,以後用這個 socket 與這個客戶通訊。
每個客戶將來會對應一個rtp 會話,而且各客戶的RTSP 請求只控制自己的 rtp 會話,那麼
最好建立一個會話類,代表各客戶的rtsp 會話。於是類 RTSPServer::RTSPClientSession

產生,它保存的代表客戶的socket 。下爲RTSPClientSession 的創建過程 。   

void RTSPServer::incomingConnectionHandler(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;
  }
   //設置 socket 的參數    
  makeSocketNonBlocking(clientSocket);
  increaseSendBufferTo(envir(), clientSocket, 50*1024);
#ifdef DEBUG
  envir() << "accept()ed connection from " << AddressString(clientAddr).val() << "\n";
#endif
  // Create a new object for this RTSP session.
  // (Choose a random 32-bit integer for the session id (it will be encoded as a 8-digit hex number).  We don't bother checking for
  //  a collision; the probability of two concurrent sessions getting the same session id is very low.)
  // (We do, however, avoid choosing session id 0, because that has a special use (by "OnDemandServerMediaSubsession").)
  unsigned sessionId;
  do 
  { 
   sessionId = (unsigned)our_random32(); 
  } while (sessionId == 0);
  //創建 RTSPClientSession ,注意傳入的參數    
  (void)createNewClientSession(sessionId, clientSocket, clientAddr);
}

RTSPServer::createNewClientSession(unsigned sessionId, int clientSocket, struct sockaddr_in clientAddr) {
  return new RTSPClientSession(*this, sessionId, clientSocket, clientAddr);
}

RTSPClientSession 要提供什麼功能呢?可以想象:需要監聽客戶端的rtsp 請求並回應它,
需要在DESCRIBE請求中返回所請求的流的信息,需要在SETUP請求中建立起RTP會話,
需要在TEARDOWN 請求中關閉RTP 會話,等等... 
RTSPClientSession 要偵聽客戶端的請求,就需把自己的 socket handler 加入計劃任務。

RTSPServer::RTSPClientSession
::RTSPClientSession(RTSPServer& ourServer, unsigned sessionId, int clientSocket, struct sockaddr_in clientAddr)
  : fOurServer(ourServer), fOurSessionId(sessionId),
    fOurServerMediaSession(NULL),
    fClientInputSocket(clientSocket), fClientOutputSocket(clientSocket), fClientAddr(clientAddr),
    fSessionCookie(NULL), fLivenessCheckTask(NULL),
    fIsMulticast(False), fSessionIsActive(True), fStreamAfterSETUP(False),
    fTCPStreamIdCount(0), fNumStreamStates(0), fStreamStates(NULL), fRecursionCount(0) 
{
  // Arrange to handle incoming requests:
  resetRequestBuffer();
  //將RTSP會話偵聽功能加入任務計劃中
  envir().taskScheduler().turnOnBackgroundReadHandling(fClientInputSocket,
     (TaskScheduler::BackgroundHandlerProc*)&incomingRequestHandler, this);
  noteLiveness();
}


void RTSPServer::RTSPClientSession::incomingRequestHandler(void* instance, int /*mask*/) {
  RTSPClientSession* session = (RTSPClientSession*)instance;
  session->incomingRequestHandler1();
}
void RTSPServer::RTSPClientSession::incomingRequestHandler1() {
  struct sockaddr_in dummy; // 'from' address, meaningless in this case
  int bytesRead = readSocket(envir(), fClientInputSocket, &fRequestBuffer[fRequestBytesAlreadySeen], fRequestBufferBytesLeft, dummy);
  handleRequestBytes(bytesRead);
}


void RTSPServer::RTSPClientSession::handleRequestBytes(int newBytesRead) 
{
  int numBytesRemaining = 0;
  ++fRecursionCount;

  do 
  {
    noteLiveness();
	//socket讀取失敗的處理
    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, "RTSPClientSession[%p]::handleRequestBytes() read %d new bytes (of %d); terminating connection!\n", this, newBytesRead, fRequestBufferBytesLeft);
      #endif
      fSessionIsActive = False;
      break;
    }
    
    Boolean endOfMsg = False;
    unsigned char* ptr = &fRequestBuffer[fRequestBytesAlreadySeen];
    #ifdef DEBUG
    ptr[newBytesRead] = '\0';
    fprintf(stderr, "RTSPClientSession[%p]::handleRequestBytes() %s %d new bytes:%s\n",
	    this, numBytesRemaining > 0 ? "processing" : "read", newBytesRead, ptr);
    #endif
    //在端口不相等的時候,我們認爲是HTTP端口傳過來的數據
    //並認爲數據經過了BASE64加密,所有我們解碼。
    if (fClientOutputSocket != fClientInputSocket) {
      // 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):
      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), 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 + newBase64RemainderCount; // adjust to allow for the size of the new decoded data (+ remainder)
	  delete[] decodedBytes;
      }
      fBase64RemainderCount = newBase64RemainderCount;
      if (fBase64RemainderCount > 0) break; // because we know that we have more input bytes still to receive
    }
    
    // Look for the end of the message: <CR><LF><CR><LF>
    unsigned char *tmpPtr = fLastCRLF + 2;
    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

	//解析客戶端的RTSP會話命令,DESCRIBE 等等。。。
    // 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];
    unsigned contentLength;
    *fLastCRLF = '\0'; // temporarily, for parsing
    Boolean parseSucceeded = parseRTSPRequestString((char*)fRequestBuffer, fRequestBytesAlreadySeen,
						    cmdName, sizeof cmdName,
						    urlPreSuffix, sizeof urlPreSuffix,
						    urlSuffix, sizeof urlSuffix,
						    cseq, sizeof cseq,
						    contentLength);
    *fLastCRLF = '\r';
	//解析成功則根據對應的命令,進入相應的處理模塊
    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 (strcmp(cmdName, "OPTIONS") == 0) {
	handleCmd_OPTIONS(cseq);
      } else if (strcmp(cmdName, "DESCRIBE") == 0) {
	handleCmd_DESCRIBE(cseq, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
      } else if (strcmp(cmdName, "SETUP") == 0) {
	handleCmd_SETUP(cseq, urlPreSuffix, urlSuffix, (char const*)fRequestBuffer);
      } else if (strcmp(cmdName, "TEARDOWN") == 0
		 || strcmp(cmdName, "PLAY") == 0
		 || strcmp(cmdName, "PAUSE") == 0
		 || strcmp(cmdName, "GET_PARAMETER") == 0
		 || strcmp(cmdName, "SET_PARAMETER") == 0) {
	handleCmd_withinSession(cmdName, urlPreSuffix, urlSuffix, cseq, (char const*)fRequestBuffer);
      } else {
	handleCmd_notSupported(cseq);
      }
    } else {
#ifdef DEBUG
      fprintf(stderr, "parseRTSPRequestString() failed\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
      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 (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:
	    fSessionIsActive = False;
	    break;
	  }
	} else {
	  isValidHTTPCmd = False;
	}
	if (!isValidHTTPCmd) {
	  handleHTTPCmd_notSupported();
	}
      } else {
#ifdef DEBUG
	fprintf(stderr, "parseHTTPRequestString() failed!\n");
#endif
	handleCmd_bad(cseq);
      }
    }
    
#ifdef DEBUG
    fprintf(stderr, "sending response: %s", fResponseBuffer);
#endif
    //向客戶端發送響應的數據
    send(fClientOutputSocket, (char const*)fResponseBuffer, strlen((char*)fResponseBuffer), 0);
    //特殊情況,客戶端在SETUP後馬上要求視頻開始
    if (strcmp(cmdName, "SETUP") == 0 && fStreamAfterSETUP) {
      // The client has asked for streaming to commence now, rather than after a
      // subsequent "PLAY" command.  So, simulate the effect of a "PLAY" command:
      handleCmd_withinSession("PLAY", urlPreSuffix, urlSuffix, cseq,
			      (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 (!fSessionIsActive) {
    if (fRecursionCount > 0) closeSockets(); else delete this;
    // Note: The "fRecursionCount" test is for a pathological situation where we got called recursively while handling a command.
    // In such a case we don't want to actually delete ourself until we leave the outermost call.
  }
}

void RTSPServer::RTSPClientSession
::handleCmd_DESCRIBE(char const* cseq,
		     char const* urlPreSuffix, char const* urlSuffix,
		     char const* fullRequestStr) 
{
  char* sdpDescription = NULL;
  char* rtspURL = NULL;
  do 
  {
    //整理RTSP地址
    char urlTotalSuffix[RTSP_PARAM_STRING_MAX];
    if (strlen(urlPreSuffix) + strlen(urlSuffix) + 2 > sizeof urlTotalSuffix) 
	{
      handleCmd_bad(cseq);
      break;
    }
    urlTotalSuffix[0] = '\0';
    if (urlPreSuffix[0] != '\0') {
      strcat(urlTotalSuffix, urlPreSuffix);
      strcat(urlTotalSuffix, "/");
    }
    strcat(urlTotalSuffix, urlSuffix);
      //驗證用戶名 密碼
    if (!authenticationOK("DESCRIBE", cseq, urlTotalSuffix, fullRequestStr)) break;
    
    // We should really check that the request contains an "Accept:" #####
    // for "application/sdp", because that's what we're sending back #####
    
    // Begin by looking up the "ServerMediaSession" object for the specified "urlTotalSuffix":
	 /*跟據流的名字查找 ServerMediaSession,如果找不到,會創建一個。每
//一個serverMediaSession中至少要包含一個   ServerMediaSubsession。一個ServerMediaSession對應一個媒體,可
//以認爲是Server 上的一個文件,或一個實時獲取設備。其包含的每個
//ServerMediaSubSession代表媒體中的一個Track。所以一個
//ServerMediaSession對應一個媒體,如果客戶請求的媒體名相同,就使用已存
//在的ServerMediaSession,如果不同,就創建一個新的。一個流對應一個
//StreamState,StreamState 與ServerMediaSubsession 相關,但代表的是動態
//的,而ServerMediaSubsession 代表靜態的。*/

	ServerMediaSession* session = fOurServer.lookupServerMediaSession(urlTotalSuffix);
    if (session == NULL) {
      handleCmd_notFound(cseq);
      break;
    }
    
    // Then, assemble a SDP description for this session:
     //獲取SDP字符串,在函數內會依次獲取每個 ServerMediaSubSession的
//字符串然連接起來
    sdpDescription = session->generateSDPDescription();
    if (sdpDescription == NULL) {
      // This usually means that a file name that was specified for a
      // "ServerMediaSubsession" does not exist.
      snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
	       "RTSP/1.0 404 File Not Found, Or In Incorrect Format\r\n"
	       "CSeq: %s\r\n"
	       "%s\r\n",
	       cseq,
	       dateHeader());
      break;
    }
    unsigned sdpDescriptionSize = strlen(sdpDescription);
    
    // Also, generate our RTSP URL, for the "Content-Base:" header
    // (which is necessary to ensure that the correct URL gets used in
    // subsequent "SETUP" requests).
    rtspURL = fOurServer.rtspURL(session, fClientInputSocket);
      //形成響應 DESCRIBE請求的RTSP 字符串。     
    snprintf((char*)fResponseBuffer, sizeof fResponseBuffer,
	     "RTSP/1.0 200 OK\r\nCSeq: %s\r\n"
	     "%s"
	     "Content-Base: %s/\r\n"
	     "Content-Type: application/sdp\r\n"
	     "Content-Length: %d\r\n\r\n"
	     "%s",
	     cseq,
	     dateHeader(),
	     rtspURL,
	     sdpDescriptionSize,
	     sdpDescription);
  } while (0);

  delete[] sdpDescription;
  delete[] rtspURL;
  
}

以上爲RTSP建立的流程,其基本流程如下圖所示:



簡單總結下:

           RTSP服務端(RTSPServer),在偵聽到客戶端的socket請求時,會創建一個RTSPClientSession類與客戶端進行RTSP會話,包括"OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, GET_PARAMETER, SET_PARAMETER"。而且會在DESCRIBE中創建serverMediaSession提供媒體服務。













發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章