0

I am writing a custom RTSP server over TCP in C++. Below is my code.

int main(int argc, char const *argv[])
{
  int server_fd, new_socket, valread;
  struct sockaddr_in address;
  int opt = 1;
  int addrlen = sizeof(address);
  char buffer[1024] = {0};

  if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0)
  {
    perror("socket failed");
    exit(EXIT_FAILURE);
  }

  if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt)))
  {
    perror("setsockopt");
    exit(EXIT_FAILURE);
  }

  address.sin_family = AF_INET;
  address.sin_addr.s_addr = INADDR_ANY;
  address.sin_port = htons(8000);

  if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0)
  {
    perror("bind");
    exit(EXIT_FAILURE);
  }

  if (listen(server_fd, 3) < 0)
  {
    perror("listen");
    exit(EXIT_FAILURE);
  }

  if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t *)&addrlen)) < 0)
  {
    perror("accept");
    exit(EXIT_FAILURE);
  }

  read(new_socket, buffer, 1024);
  std::string request_message(data);
  std::string request_type = request_message.substr(0, request_message.find(" "));
  std::string response_data;
  std::string sequence_number;
  std::string sub_str;

  if (request_type == "OPTIONS")
  {
    char current_date[200];
    time_t tt = time(NULL);
    strftime(current_date, sizeof current_date, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt));
    std::string current_date_str(current_date);
    sub_str = request_message.substr(0, request_message.find("User-Agent:"));
    sequence_number = sub_str.substr(sub_str.find("CSeq: ") + 6);
    response_data = "RTSP/1.0 200 OK\r\nCSeq: " + sequence_number + "\r\nPublic: DESCRIBE, GET_PARAMETER, SETUP, SET_PARAMETER, PLAY, RECORD, PAUSE, TREADOWN\r\nServer: My Custom RSTP Server\r\nDate: " + current_date_str + "\r\n";
   send(new_socket, response_data , response_data.size(), 0);
  }
  else if (request_type == "DESCRIBE")
  {
    sub_str = request_message.substr(0, request_message.find("User-Agent:"));
    sequence_number = sub_str.substr(sub_str.find("CSeq: ") + 6);
    std::string cnt = "v=0\r\no=- " + std::to_string(rand()) + " 1 IN IP4 my_ip_address\r\ns=\r\nt=0 0\r\nm=video 0 RTP/AVP 26\r\nc=IN IP4 0.0.0.0\r\n";
    response_data = "RTSP/1.0 200 OK\r\nCSeq: " + sequence_number + "\r\nCache-control: no-cache\r\nContent-Type: application/sdp\r\nContent-Base: rtsp://my_ip_address:8000\r\nContent-length: " + std::to_string(cnt.size()) + "\r\n" + cnt;
    send(new_socket, response_data , response_data.size(), 0);
  }
  else if (request_type == "SETUP")
  {
    char current_date2[200];
    time_t tt2 = time(NULL);
    strftime(current_date2, sizeof current_date2, "%a, %b %d %Y %H:%M:%S GMT", gmtime(&tt2));
    std::string current_date_str2(current_date2);
    std::string transport = request_message.substr(request_message.find("Transport:"));
    transport = transport.substr(0, transport.find("\r\n"));
    sub_str = request_message.substr(0, request_message.find("Transport:"));
    sequence_number = sub_str.substr(sub_str.find("CSeq: ") + 6);
    response_data = "RTSP/1.0 200 OK\r\nCSeq: " + sequence_number + "\r\n" + transport + ";server_port=8000-8001;ssrc=4b003a1e\r\nDate: " + current_date_str2 + "\r\nSession: 1185d20035702ca\r\n";
          send(new_socket, response_data , response_data.size(), 0);
  }
  else if (request_type == "PLAY")
  {
    std::string transport = request_message.substr(request_message.find("User-Agent:"));
    transport = transport.substr(0, transport.find("\r\n"));
    sub_str = request_message.substr(0, request_message.find("User-Agent:"));
    sequence_number = sub_str.substr(sub_str.find("CSeq: ") + 6);
    response_data = "RTSP/1.0 200 OK\r\nCSeq: " + sequence_number + "\r\nSession: 1185d20035702ca\r\n";
    send(new_socket, response_data , response_data.size(), 0);

    for(;;)
    {
      sendFrameData();
    }
  }
}

I use VLC as the RTSP client to connect to the server via rtsp://my_ip_address:8000 and use Wireshark to analyze the communication. There is no error at all. As result, they did not communicate over RTSP. They only communicated over TCP. VLC did not send requests over RTSP e.g. OPTIONS, DESCRIBE, SETUP or PLAY but it sent these requests over TCP. So the server also did not send RTSP responses.

But the server did get the message body of the VLC requests OPTIONS, DESCRIBE and SETUP.

What did I do wrong? I am not looking for a complete solution. A right direction is also thankful!

O Connor
  • 4,236
  • 15
  • 50
  • 91
  • 1
    just for clearification rtsp is a protocol on top of tcp so rtsp packages are sent over tcp, media goes the over rtp/tcp or rtp/udp, just look into rtsp rfc for deeper knowledge about that also you can look in source codes of existing rtsp servers for deeper understanding. – Christoph Nov 09 '22 at 11:57

1 Answers1

0

You've probably got this figured out by now, but regardless. There are a few issues here, one of which was already pointed out by someone else.

RTSP is a protocol on top of TCP/UDP. It kinda uses both, but that is out of scope for the question.

The second thing I noticed, this application should have given you a compiling error: std::string request_message(data); the variable data does not exist anywhere in your code. You are reading the contents of the socket into buffer. So the contents of the buffer never get parsed. Did you intend std::string request_message(buffer);?

The last part I'd like to point out is that you are using a streaming socket. This is important, because it means that all data is not guaranteed to be sent in one packet, even if it is very small. In fact, it could be 1 byte at a time, although very unlikely, but best to write your code as if it could happen. You could also receive multiple packets at once in a single read() call. With a streaming socket you must use a concept called framing to reassemble the packets using a known boundary system so you can detect the start/end of each packet you receive and reassemble them chunk by chunk.

So why did your server not respond? likely because you only have one call to read() and this socket type does not guarantee getting all the data at once. You absolutely need to implement both framing and a means to reassemble the packets when using a stream socket like TCP. I've implemented many protocols/standards like this, but i've not done RTSP yet. I did glance at it, and it seems to follow something similar to HTTP. There are many ways to implement this, but the general idea is:

  1. Keep on calling read()
  2. Parse the data after each read() and check for the header ending sequence (\r\n\r\n), if you dont find it goto #1 and read() some more until you do
  3. When you find the ending sequence, extract the header attributes and look for "Content-Length: 126"
  4. Extract the content length size. if it doesnt exist, or is 0, then you are done reading goto #6. Otherwise goto #5
  5. If Content-Length is not 0, you must continue reading your buffer and the socket until you get the remaining bytes. There is no ending delimiter in the message body, it is completely dictated by the Content-Length value. Keep calling read() and checking the buffer until you get it all
  6. Process the packet that you've assembled
  7. If there is still data in your buffer, you must go back to #2 for processing because it is the start of another packet. Otherwise, if your buffer is empty after processing then goto #1 and wait for more data

I would recommend boost::asio::streambuf for your buffer, because it makes this process much easier to implement.

deviantgeek
  • 121
  • 3