RTSP协议简单实现

RTSP是一个实时传输流协议,是一个应用层的协议。通常说的RTSP 包括RTSP协议、RTP协议、RTCP协议,对于这些协议的作用简单的理解如下

RTSP协议:负责服务器与客户端之间的请求与响应
RTP协议: 负责服务器与客户端之间传输媒体数据
RTCP协议:负责提供有关RTP传输质量的反馈,就是确保RTP传输的质量

三者的关系: rtsp并不会发送媒体数据,只是完成服务器和客户端之间的信令交互rtp协议负责媒体数据传输rtcp负责rtp数据包的监视和反馈。rtp和rtcp并没有规定传输层的类型,可以选择udp和tcp。Rtsp的传输层则要求是基于tcp。

RTSP 交互流程示例

在这里插入图片描述

以一个典型的 RTSP 点播流程 为例(如客户端点播 rtsp://example.com/video):
抓包
在这里插入图片描述
步骤 1:OPTIONS(查询支持的方法)
客户端请求:

OPTIONS rtsp://example.com/video RTSP/1.0
CSeq: 1
User-Agent: VLC

在这里插入图片描述

服务器响应:

RTSP/1.0 200 OK
CSeq: 1
Public: OPTIONS, DESCRIBE, SETUP, PLAY, TEARDOWN

在这里插入图片描述

步骤 2:DESCRIBE(获取媒体描述)
客户端请求

DESCRIBE rtsp://example.com/video RTSP/1.0
CSeq: 2
Accept: application/sdp

在这里插入图片描述

服务器响应(返回 SDP 描述)

RTSP/1.0 200 OK
CSeq: 2
Content-Type: application/sdp
Content-Length: 123

v=0
o=- 123456 1 IN IP4 192.168.1.1
s=Example Video
m=video 0 RTP/AVP 96
a=rtpmap:96 H264/90000

在这里插入图片描述

步骤 3:SETUP(建立传输通道)
客户端请求(告知服务器自己的 RTP/RTCP 端口):

SETUP rtsp://example.com/video/track1 RTSP/1.0
CSeq: 3
Transport: RTP/AVP;unicast;client_port=8000-8001

在这里插入图片描述

服务器响应(确认传输参数):

RTSP/1.0 200 OK
CSeq: 3
Transport: RTP/AVP;unicast;client_port=8000-8001;server_port=9000-9001
Session: 123456

在这里插入图片描述

步骤 4:PLAY(开始播放)
客户端请求:

PLAY rtsp://example.com/video RTSP/1.0
CSeq: 4
Session: 123456
Range: npt=0-

在这里插入图片描述
服务器响应:

RTSP/1.0 200 OK
CSeq: 4
Session: 123456
RTP-Info: url=rtsp://example.com/video/track1;seq=123;rtptime=123456

在这里插入图片描述

步骤 5:RTP 数据传输
服务器通过 UDP 向客户端的 8000 端口发送 RTP 数据包。
客户端通过 8001 端口发送 RTCP 反馈(如丢包统计)。

步骤 6:TEARDOWN(终止会话)
客户端请求:

TEARDOWN rtsp://example.com/video RTSP/1.0
CSeq: 5
Session: 1234

服务器响应

RTSP/1.0 200 OK
CSeq: 5
Session: 123456

模拟整体代码

//
// Created by bxc on 2022/11/30.
//

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <windows.h>
#include <string>
#pragma comment(lib, "ws2_32.lib")
#include <stdint.h>

#pragma warning( disable : 4996 )

#define SERVER_PORT      8554

#define SERVER_RTP_PORT  55532
#define SERVER_RTCP_PORT 55533

static int createTcpSocket()
{
    int sockfd;
    int on = 1;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
        return -1;

    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));

    return sockfd;
}

static int bindSocketAddr(int sockfd, const char* ip, int port)
{
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip);

    if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0)
        return -1;

    return 0;
}

static int acceptClient(int sockfd, char* ip, int* port)
{
    int clientfd;
    socklen_t len = 0;
    struct sockaddr_in addr;

    memset(&addr, 0, sizeof(addr));
    len = sizeof(addr);

    clientfd = accept(sockfd, (struct sockaddr*)&addr, &len);
    if (clientfd < 0)
        return -1;

    strcpy(ip, inet_ntoa(addr.sin_addr));
    *port = ntohs(addr.sin_port);

    return clientfd;
}

static int handleCmd_OPTIONS(char* result, int cseq)
{
    sprintf(result, "RTSP/1.0 200 OK\r\n"
        "CSeq: %d\r\n"
        "Public: OPTIONS, DESCRIBE, SETUP, PLAY\r\n"
        "\r\n",
        cseq);

    return 0;
}

static int handleCmd_DESCRIBE(char* result, int cseq, char* url)
{
    char sdp[500];
    char localIp[100];

    sscanf(url, "rtsp://%[^:]:", localIp);

    sprintf(sdp, "v=0\r\n"
        "o=- 9%ld 1 IN IP4 %s\r\n"
        "t=0 0\r\n"
        "a=control:*\r\n"
        "m=video 0 RTP/AVP 96\r\n"
        "a=rtpmap:96 H264/90000\r\n"
        "a=control:track0\r\n",
        time(NULL), localIp);

    sprintf(result, "RTSP/1.0 200 OK\r\nCSeq: %d\r\n"
        "Content-Base: %s\r\n"
        "Content-type: application/sdp\r\n"
        "Content-length: %zu\r\n\r\n"
        "%s",
        cseq,
        url,
        strlen(sdp),
        sdp);

    return 0;
}

static int handleCmd_SETUP(char* result, int cseq, int clientRtpPort)
{
    sprintf(result, "RTSP/1.0 200 OK\r\n"
        "CSeq: %d\r\n"
        "Transport: RTP/AVP;unicast;client_port=%d-%d;server_port=%d-%d\r\n"
        "Session: 66334873\r\n"
        "\r\n",
        cseq,
        clientRtpPort,
        clientRtpPort + 1,
        SERVER_RTP_PORT,
        SERVER_RTCP_PORT);

    return 0;
}

static int handleCmd_PLAY(char* result, int cseq)
{
    sprintf(result, "RTSP/1.0 200 OK\r\n"
        "CSeq: %d\r\n"
        "Range: npt=0.000-\r\n"
        "Session: 66334873; timeout=10\r\n\r\n",
        cseq);

    return 0;
}

static void doClient(int clientSockfd, const char* clientIP, int clientPort) {

    // 定义请求相关的变量
    char method[40];   // 存储请求方法 (OPTIONS, DESCRIBE, SETUP, PLAY)
    char url[100];     // 存储请求的URL
    char version[40];  // 存储RTSP协议版本
    int CSeq;          // 存储CSeq序列号

    // RTP 和 RTCP 客户端端口
    int clientRtpPort, clientRtcpPort;

    // 分配接收缓冲区(用于接收客户端数据)
    char* rBuf = (char*)malloc(10000);  // rBuf:接收数据缓冲区
    char* sBuf = (char*)malloc(10000);  // sBuf:发送数据缓冲区

    // 开始循环处理客户端请求
    while (true) {
        int recvLen;

        // 从客户端socket读取数据,最多读取2000字节
        recvLen = recv(clientSockfd, rBuf, 2000, 0);

        // 如果接收失败或连接断开,退出循环
        if (recvLen <= 0) {
            break;
        }

        // 添加字符串结束符,防止数据污染
        rBuf[recvLen] = '\0';

        // 将接收到的数据转成string方便查看
        std::string recvStr = rBuf;
        printf(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n");
        printf("%s rBuf = %s \n", __FUNCTION__, rBuf);

        // 分割符,用于解析RTSP请求的每一行
        const char* sep = "\n";
        char* line = strtok(rBuf, sep);  // 逐行解析请求数据
        while (line) {

            // 解析请求方法 (OPTIONS, DESCRIBE, SETUP, PLAY)
            if (strstr(line, "OPTIONS") ||
                strstr(line, "DESCRIBE") ||
                strstr(line, "SETUP") ||
                strstr(line, "PLAY")) {

                // 解析请求方法、URL 和协议版本,例如: "OPTIONS rtsp://127.0.0.1/live.sdp RTSP/1.0"
                if (sscanf(line, "%s %s %s\r\n", method, url, version) != 3) {
                    // 如果解析失败,跳过
                    // error
                }
            }
            // 解析 CSeq 字段 (CSeq: 1)
            else if (strstr(line, "CSeq")) {
                if (sscanf(line, "CSeq: %d\r\n", &CSeq) != 1) {
                    // error
                }
            }
            // 解析 Transport 字段,获取 RTP 和 RTCP 端口
            else if (!strncmp(line, "Transport:", strlen("Transport:"))) {
                // 解析示例:
                // Transport: RTP/AVP/UDP;unicast;client_port=13358-13359
                // Transport: RTP/AVP;unicast;client_port=13358-13359
                if (sscanf(line, "Transport: RTP/AVP/UDP;unicast;client_port=%d-%d\r\n",
                    &clientRtpPort, &clientRtcpPort) != 2) {
                    // 解析失败
                    printf("parse Transport error \n");
                }
            }

            // 继续解析下一行
            line = strtok(NULL, sep);
        }

        // 根据解析的请求方法,调用相应的处理函数
        if (!strcmp(method, "OPTIONS")) {
            // 处理 OPTIONS 请求
            if (handleCmd_OPTIONS(sBuf, CSeq)) {
                printf("failed to handle options\n");
                break;  // 处理失败,退出
            }
        }
        else if (!strcmp(method, "DESCRIBE")) {
            // 处理 DESCRIBE 请求
            if (handleCmd_DESCRIBE(sBuf, CSeq, url)) {
                printf("failed to handle describe\n");
                break;
            }
        }
        else if (!strcmp(method, "SETUP")) {
            // 处理 SETUP 请求
            if (handleCmd_SETUP(sBuf, CSeq, clientRtpPort)) {
                printf("failed to handle setup\n");
                break;
            }
        }
        else if (!strcmp(method, "PLAY")) {
            // 处理 PLAY 请求
            if (handleCmd_PLAY(sBuf, CSeq)) {
                printf("failed to handle play\n");
                break;
            }
        }
        else {
            // 处理未定义的请求方法
            printf("未定义的method = %s \n", method);
            break;
        }

        // 打印发送缓冲区的响应内容
        printf("<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n");
        printf("%s sBuf = %s \n", __FUNCTION__, sBuf);

        // 发送处理结果到客户端
        send(clientSockfd, sBuf, strlen(sBuf), 0);

        // 如果是 PLAY 请求,进入 RTP 数据传输
        if (!strcmp(method, "PLAY")) {

            // 开始播放
            printf("start play\n");
            printf("client ip:%s\n", clientIP);
            printf("client port:%d\n", clientRtpPort);

            // 发送RTP包的无限循环
            while (true) {

                // 休眠 40 毫秒,模拟每秒 25 帧的视频传输
                Sleep(40);
                // usleep(40000); // 另一种实现(Linux),单位微秒

            }

            // 结束 RTP 发送
            break;
        }

        // 清空变量,为下一次解析做准备
        memset(method, 0, sizeof(method) / sizeof(char));
        memset(url, 0, sizeof(url) / sizeof(char));
        CSeq = 0;
    }

    // 关闭客户端的 socket
    closesocket(clientSockfd);

    // 释放动态分配的内存
    free(rBuf);
    free(sBuf);
}


int main(int argc, char* argv[])
{
    // 启动windows socket start
    WSADATA wsaData;
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        printf("PC Server Socket Start Up Error \n");
        return -1;
    }
    // 启动windows socket end

    int serverSockfd;

    serverSockfd = createTcpSocket();
    if (serverSockfd < 0)
    {
        WSACleanup();
        printf("failed to create tcp socket\n");
        return -1;
    }

    if (bindSocketAddr(serverSockfd, "0.0.0.0", SERVER_PORT) < 0)
    {
        printf("failed to bind addr\n");
        return -1;
    }

    if (listen(serverSockfd, 10) < 0)
    {
        printf("failed to listen\n");
        return -1;
    }

    printf("%s rtsp://127.0.0.1:%d\n", __FILE__, SERVER_PORT);

    while (true) {
        int clientSockfd;
        char clientIp[40];
        int clientPort;

        clientSockfd = acceptClient(serverSockfd, clientIp, &clientPort);
        if (clientSockfd < 0)
        {
            printf("failed to accept client\n");
            return -1;
        }

        printf("accept client;client ip:%s,client port:%d\n", clientIp, clientPort);

        doClient(clientSockfd, clientIp, clientPort);
    }
    closesocket(serverSockfd);
    return 0;
}

客户端连接
在这里插入图片描述
服务器
在这里插入图片描述

<think>好的,用户让我简单介绍一下RTSP协议。首先,我需要回想一下RTSP的基本概念,确保自己理解正确。RTSP全称是实时流协议,主要用于控制流媒体服务器,比如播放、暂停这些操作。不过它本身不传输数据,这点可能需要强调,避免误解。 接下来,用户可能想知道RTSP和其他类似协议的区别,比如RTP和RTCP。这里需要解释清楚RTSP的作用是控制,而数据传输由RTP/RTCP负责。同时,可能需要提到RTSP通常使用TCP或UDP,默认端口554,这些细节对理解协议有帮助。 用户可能对应用场景感兴趣,比如监控摄像头、视频会议,这些都是常见用途。需要举例说明,让用户更容易理解。另外,基本命令如DESCRIBE、SETUP、PLAY、PAUSE、TEARDOWN,这些是RTSP的核心操作,逐一解释每个命令的作用会有助于用户掌握流程。 特性部分需要分点列出,比如控制独立性、状态性、扩展性,每个特性简要说明。可能还要对比HTTP,指出RTSP是双向的,而HTTP是单向的,这样用户能更好区分两者的不同。 不过要注意避免技术术语过多,保持解释简单明了。是否需要提到SDP?在DESCRIBE响应中通常会包含SDP描述媒体信息,这点可能对用户有用,但简要带过即可。另外,确保语法正确,比如使用$$...$$格式时,公式要正确,不过这里可能用不上数学公式,但用户给的系统指令里提到过,所以如果有数学相关的内容要注意格式。 最后检查结构是否清晰,分步骤介绍协议的基本概念、工作原理、应用场景、命令和特性。确保使用中文,口语化,避免使用Markdown格式,符合用户的要求。可能需要提醒用户注意RTSP的现状,比如在浏览器支持有限,通常需要插件或专用播放器,这也是实际应用中需要注意的点。</think>RTSP(Real Time Streaming Protocol)是一种用于控制流媒体服务器的应用层协议,以下是逐步解析: 1. **基本概念** - 协议定位:RTSP工作在OSI模型的应用层(基于TCP或UDP) - 核心功能:控制媒体流的播放、暂停、定位等操作(类似"流媒体遥控器") - 标准定义:RFC 2326(1998年发布) 2. **工作原理** - 采用客户端-服务器架构 - 典型交互流程: ``` 1. 客户端发送DESCRIBE请求 → 服务器返回媒体描述信息(SDP格式) 2. 客户端发送SETUP请求 → 服务器建立传输通道 3. 客户端发送PLAY请求 → 服务器开始传输媒体流 4. 客户端可发送PAUSE/TERADOWN等控制命令 ``` 3. **关键特性** - 网络协议依赖: - 控制信令:通常使用TCP(端口554) - 媒体传输:通过RTP协议(基于UDP) - 质量控制:配合RTCP协议监控传输质量 - 状态保持:维护会话状态(不同于无状态的HTTP) - 扩展性:支持自定义方法头和参数 4. **应用场景** - IP摄像头监控系统 - 视频点播服务(如网络电视) - 视频会议系统 - 无人机实时画面传输 5. **与相关协议对比** | 协议 | 功能定位 | 传输内容 | 典型端口 | |---|---|---|---| | RTSP | 流媒体控制 | 控制指令 | 554 | | RTP | 数据传输 | 音视频流 | 动态端口 | | RTCP | 传输监控 | 质量反馈 | 动态端口 | 6. **现状与发展** - 优势:精确的流控制能力 - 局限:现代浏览器原生支持有限 - 替代方案:WebRTC、HLS、DASH等新兴协议 注意:实际使用中常需要配套组件(如FFmpeg、VLC播放器)实现完整功能,且需注意NAT穿透等网络配置问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值