nginx rtmp模块握手过程

RTMP规范要求

  上一篇文章,我们分析了rtmp模块接受用户请求的流程,主要介绍了一个用户请求到达server端之后,nginx rtmp模块如何接收请求,并为用户创建session,以及session的初始化等一系列操作;本章,我们将分析rtmp模块中的握手流程。

  根据rtmp协议的规定,rtmp存在三次握手的过程,简单的来说,就是客户端发送C0、C1请求,服务端收到之后,发送S0、S1给客户端,客户端在收到S1之后需要发送C2,服务端在收到C1之后需要发送S2;只有当客户端收到S2之后,并且服务端收到C2之后,握手才算完成,接下来,就是发送正常的数据;主要流程如下图: 在这里插入图片描述
   好了,上面的流程图提现了rtmp的握手交互过程,需要注意的是,C1和S0、S1没有严格发送的顺序,server可以在接收到C1之后的发送S0、S1,也可以在接收到C1之前就发送;

Nginx rtmp模块握手实现

RTMP服务端握手

   上面主要介绍了rtmp的一些协议要求规范,接下来我们看一下nginx rtmp模块是如何实现这个握手的过程的。
   nginx rtmp模块中,实现这一过程的代码主要存放在ngx_rtmp_handshake.c文件中,在介绍用户连接建立的文章中,我们可以看到,在用户连接建立完成之后,会调用方法ngx_rtmp_handshake,来设置基于该连接的后续处理方法。让我们来看一下该函数中做了哪些操作:

void ngx_rtmp_handshake(ngx_rtmp_session_t *s)
{
    ngx_connection_t           *c;
	//由于传入的参数是用户的session,而网络方法调用是基于连接来实现的,所以需要从session中获取到连接对象
    c = s->connection;
    c->read->handler =  ngx_rtmp_handshake_recv; //为连接对象设置接收数据的回调函数
    c->write->handler = ngx_rtmp_handshake_send;  //为连接对象设置发送数据的回调函数

    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "handshake: start server handshake");

    s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);	//为握手分配数据存储空间
    s->hs_stage = NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE;   //作为服务端,设置当前初始状态
    ngx_rtmp_handshake_recv(c->read);	//开始接受数据第一个发送给该连接的数据包
}

   需要注意的是,nginx rtmp模块的握手,是使用状态机来实现的,它定义了几个状态,当收到消息之后,就会触发状态的改变,不同的状态,会触发相应的行为事件;

   接下来我们看一下ngx_rtmp_handshake_recv方法:

static void ngx_rtmp_handshake_recv(ngx_event_t *rev)
{
    ssize_t                     n;
    ngx_connection_t           *c;
    ngx_rtmp_session_t         *s;
    ngx_buf_t                  *b;
	
    c = rev->data;	//由于传进来的参数是一个read的事件结构体,我们现在需要获取到该事件上的连接句柄
    s = c->data;  //通过连接句柄获取到该连接上的session对象

    if (c->destroyed) {	//针对于连接进行一个判断,查看连接是否关闭
        return;
    }

    if (rev->timedout) {	//该事件是否已经超时
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
                "handshake: recv: client timed out");
        c->timedout = 1;
        ngx_rtmp_finalize_session(s);
        return;
    }

    if (rev->timer_set) {	//是否设置过超时事件,如果设置过,则去除掉定时事件
        ngx_del_timer(rev);
    }

    b = s->hs_buf;	//获取握手数据缓存区

    while (b->last != b->end) {
        n = c->recv(c, b->last, b->end - b->last);	//接受握手数据

        if (n == NGX_ERROR || n == 0) {
            ngx_rtmp_finalize_session(s);
            return;
        }

        if (n == NGX_AGAIN) {
            ngx_add_timer(rev, s->timeout);
            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                ngx_rtmp_finalize_session(s);
            }
            return;
        }

        b->last += n;
    }

    if (rev->active) {	//由于数据已经读取,所以删除read事件
        ngx_del_event(rev, NGX_READ_EVENT, 0);
    }

    ++s->hs_stage;	//由于接收到了数据,所以需要改变当前握手的状态
    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "handshake: stage %ui", s->hs_stage);

    switch (s->hs_stage) {   //根据当前的状态,进行接下来的操作
        case NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE:	
        	/*解析握手数据包*/
            if (ngx_rtmp_handshake_parse_challenge(s,
                    &ngx_rtmp_client_partial_key,
                    &ngx_rtmp_server_full_key) != NGX_OK)
            {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                        "handshake: error parsing challenge");
                ngx_rtmp_finalize_session(s);
                return;
            }
            if (s->hs_old) {  //当前缓存区中,解析完之后,剩余了部分原始数据,这是由于rtmp是基于tcp协议进行传输的,存在粘包的情况,所以需要进行分包处理,
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                        "handshake: old-style challenge");
                s->hs_buf->pos = s->hs_buf->start;
                s->hs_buf->last = s->hs_buf->end;
            } else if (ngx_rtmp_handshake_create_challenge(s,  /*生成响应的数据包*/
                        ngx_rtmp_server_version,
                        &ngx_rtmp_server_partial_key) != NGX_OK)
            {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                        "handshake: error creating challenge");
                ngx_rtmp_finalize_session(s);
                return;
            }
            ngx_rtmp_handshake_send(c->write);	//发送返回包
            break;

        case NGX_RTMP_HANDSHAKE_SERVER_DONE:
            ngx_rtmp_handshake_done(s);
            break;

        case NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE:
            if (ngx_rtmp_handshake_parse_challenge(s,
                    &ngx_rtmp_server_partial_key,
                    &ngx_rtmp_client_full_key) != NGX_OK)
            {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                        "handshake: error parsing challenge");
                ngx_rtmp_finalize_session(s);
                return;
            }
            s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;
            ngx_rtmp_handshake_recv(c->read);
            break;

        case NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE:
            if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                        "handshake: response error");
                ngx_rtmp_finalize_session(s);
                return;
            }
            ngx_rtmp_handshake_send(c->write);
            break;
    }
}

 上面是作为接收到消息时的处理方式,接下来我们看一下,当发送消息的时候,会做什么,发送消息的代码在ngx_rtmp_handshake_send方法中;

static void
ngx_rtmp_handshake_send(ngx_event_t *wev)
{
    ngx_int_t                   n;
    ngx_connection_t           *c;
    ngx_rtmp_session_t         *s;
    ngx_buf_t                  *b;

    c = wev->data;	//由于该函数传输进来的参数是写事件,所以获取该事件的连接对象
    s = c->data;	//通过连接对象获取到会话session

    if (c->destroyed) {
        return;
    }

    if (wev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
                "handshake: send: client timed out");
        c->timedout = 1;
        ngx_rtmp_finalize_session(s);
        return;
    }

    if (wev->timer_set) {
        ngx_del_timer(wev);
    }

    b = s->hs_buf;

    while(b->pos != b->last) {
        n = c->send(c, b->pos, b->last - b->pos);  //发送握手数据

        if (n == NGX_ERROR) {
            ngx_rtmp_finalize_session(s);
            return;
        }

        if (n == NGX_AGAIN || n == 0) {
            ngx_add_timer(c->write, s->timeout);
            if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
                ngx_rtmp_finalize_session(s);
            }
            return;
        }

        b->pos += n;
    }

    if (wev->active) {
        ngx_del_event(wev, NGX_WRITE_EVENT, 0);
    }

    ++s->hs_stage;	//设置当前状态
    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "handshake: stage %ui", s->hs_stage);

    switch (s->hs_stage) {//根据不同的状态做不同的处理
        case NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE:
            if (s->hs_old) {
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                        "handshake: old-style response");
                s->hs_buf->pos = s->hs_buf->start + 1;
                s->hs_buf->last = s->hs_buf->end;
            } else if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {
                ngx_log_error(NGX_LOG_INFO, c->log, 0,
                        "handshake: response error");
                ngx_rtmp_finalize_session(s);
                return;
            }
            ngx_rtmp_handshake_send(wev);
            break;

        case NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE:
            s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;
            ngx_rtmp_handshake_recv(c->read);
            break;

        case NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE:
            s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start;
            ngx_rtmp_handshake_recv(c->read);
            break;

        case NGX_RTMP_HANDSHAKE_CLIENT_DONE:
            ngx_rtmp_handshake_done(s);
            break;
    }
}

  上面的两个方法是nginx-rtmp模块实现握手的主要流程的主要代码;在上面的代码中,我们可以看到,使用了状态机方式,由于rtmp协议中,针对于服务端和客户端握手消息做了规定,所以只需要根据消息到达可发送的顺序,就可以知道,当前处于的握手状态;基于这个原因,nginx的rtmp模块非常巧妙的使用了状态机来处理;

server端状态机变化:
   NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE
   NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE
   NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE
   NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE
   NGX_RTMP_HANDSHAKE_SERVER_DONE

RTMP客户端握手

  在rtmp模块中,不仅实现了服务端的握手功能,还实现了客户端的握手功能;
  实现服务端的握手功能很好理解,毕竟nginx是一个高性能的服务程序;但是作为客户端这是为啥呢?那是因为,nginx rtmp模块,可以作为cdn使用,当它作为cdn的边缘节点时候,它就需要去向根节点请求数据,这个时候,他就是一个客户端,所以它需要实现客户端的握手功能;接下来我们看一下作为服务端的实现:

void ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async)
{
    ngx_connection_t           *c;

    c = s->connection;	//从session中获取到连接句柄
    c->read->handler =  ngx_rtmp_handshake_recv;	//为read事件设置回调函数,该方法和上面作为server端的时候的回调
    c->write->handler = ngx_rtmp_handshake_send;	//为write事件设置回调函数,该方法和上面作为server端的时候的回调

    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "handshake: start client handshake");

    s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);	//分配握手需要的数据缓存区
    s->hs_stage = NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE; //设置初始化句柄
	//创建C0数据包
    if (ngx_rtmp_handshake_create_challenge(s,
                ngx_rtmp_client_version,
                &ngx_rtmp_client_partial_key) != NGX_OK)
    {
        ngx_rtmp_finalize_session(s);
        return;
    }
    
    if (async) {//异步网络发送判断
        ngx_add_timer(c->write, s->timeout);
        if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
            ngx_rtmp_finalize_session(s);
        }
        return;
    }

	//发送数据到对端,这个方法和上面server端的代码相同用
    ngx_rtmp_handshake_send(c->write);
}

从上面的rtmp作为客户端的代码来看,可以看出来,其实和作为server端调用的函数是相同的,作为server端和作为client端的主要区别就是第一个调用方法不同,作为client端,首先调用的是ngx_rtmp_client_handshake方法,而作为serevr端,首先调用的是ngx_rtmp_handshake端;

client状态机变化:
   NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE
   NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE
   NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE
   NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE
   NGX_RTMP_HANDSHAKE_CLIENT_DONE

在握手完成中之后,会调用方法ngx_rtmp_handshake_done

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

brid.huang

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值