librtmp 源码分析笔记 ReadN

无限缅怀雷神,R.I.P 

https://blog.csdn.net/leixiaohua1020/article/details/15814587

为了能更好的使用librtmp,特将librtmp源码的个人分析记录下来,方便日后查看回顾。

rtmp是基于tcp的,无论协议写的如何天花乱坠,本质还是发包和收包,最本质的是二进制数据的交换,所以先查看下发送、接受阵营的两位苦工,ReadN,WriteN函数

略去对http方式、加密相关代码的分析(其实是看不懂),只看最基础最重要的tcp收发流程。

ReadN

ReadN函数顾名思义,就是从socket中读取N个字节出来,如果不足N个字节,那么我就阻塞等,如果超过N个字节,我也只取前N个字节。

static int
ReadN(RTMP *r, char *buffer, int n)
{
  int nOriginalSize = n; 
  int avail;
  char *ptr;

  r->m_sb.sb_timedout = FALSE;

#ifdef _DEBUG
  memset(buffer, 0, n);
#endif

  ptr = buffer;
  while (n > 0)
    {
      int nBytes = 0, nRead;
      if (r->Link.protocol & RTMP_FEATURE_HTTP)
        {
	  while (!r->m_resplen)
	    {
	      if (r->m_sb.sb_size < 144)
	        {
		  if (!r->m_unackd)
		    HTTP_Post(r, RTMPT_IDLE, "", 1);
		  if (RTMPSockBuf_Fill(&r->m_sb) < 1)
		    {
		      if (!r->m_sb.sb_timedout)
		        RTMP_Close(r);
		      return 0;
		    }
		}
	      HTTP_read(r, 0);
	    }
	  if (r->m_resplen && !r->m_sb.sb_size)
	    RTMPSockBuf_Fill(&r->m_sb);
          avail = r->m_sb.sb_size;
	  if (avail > r->m_resplen)
	    avail = r->m_resplen;
	}
      else
        {
          avail = r->m_sb.sb_size;
	  if (avail == 0)
	  {
	      if (RTMPSockBuf_Fill(&r->m_sb) < 1)
	        {
	          if (!r->m_sb.sb_timedout)
	            RTMP_Close(r);
	          return 0;
		}
	      avail = r->m_sb.sb_size;
	   }

    此处首先查看当前的socketbuffer中有没有数据,如果没有,那么尝试接受并填充socketbuffer,此处调用了RTMPSockBuf_Fill,这其实才是真正接收数据的地方,后文分析。

       }
       nRead = ((n < avail) ? n : avail);

n是还想要读取的字节数,avail是buffer中已有的字节数,nRead就是将要拷贝到输出地址的字节数

      if (nRead > 0)
	{
	  memcpy(ptr, r->m_sb.sb_start, nRead);
	  r->m_sb.sb_start += nRead;
	  r->m_sb.sb_size -= nRead;
	  nBytes = nRead;
	  r->m_nBytesIn += nRead;

拷贝出去后,缓冲区中nRead字节的数据已被消费完,做出相应的调整

	  if (r->m_bSendCounter
	      && r->m_nBytesIn > r->m_nBytesInSent + r->m_nClientBW / 2)
	    SendBytesReceived(r);
	}
      /*RTMP_Log(RTMP_LOGDEBUG, "%s: %d bytes\n", __FUNCTION__, nBytes); */
#ifdef _DEBUG
      fwrite(ptr, 1, nBytes, netstackdump_read);
#endif

      if (nBytes == 0)
	{
	  RTMP_Log(RTMP_LOGDEBUG, "%s, RTMP socket closed by peer", __FUNCTION__);
	  /*goto again; */
	  RTMP_Close(r);
	  break;
	}

      if (r->Link.protocol & RTMP_FEATURE_HTTP)
	r->m_resplen -= nBytes;

#ifdef CRYPTO
      if (r->Link.rc4keyIn)
	{
	  RC4_encrypt(r->Link.rc4keyIn, nBytes, ptr);
	}
#endif

      n -= nBytes;
      ptr += nBytes;

因为已经读了nBytes字节的数据了,那么接下来还需要读的数据长度需要减去nBytes,输出地址的指针也需要相应的移动nBytes

    }

  return nOriginalSize - n;

返回已读取的字节数,不发生异常情况,都是返回nOriginalSize,即函数参数中的n,代表圆满完成了读取n个字节的任务

}

接下来看下更下层的苦工RTMPSockBuf_Fill

int
RTMPSockBuf_Fill(RTMPSockBuf *sb)
{
  int nBytes;

  if (!sb->sb_size)
    sb->sb_start = sb->sb_buf;

这句意思就是,如果buffer里面的未消费的字节是0,那么赶紧把buffer的内容起始指针还原为buffer的指针


  while (1)
    {
      nBytes = sizeof(sb->sb_buf) - sb->sb_size - (sb->sb_start - sb->sb_buf);

nBytes是指当前buffer能读取的最大字节数,

#if defined(CRYPTO) && !defined(NO_SSL)
      if (sb->sb_ssl)
	{
	  nBytes = TLS_read(sb->sb_ssl, sb->sb_start + sb->sb_size, nBytes);
	}
      else
#endif
	{
	  nBytes = recv(sb->sb_socket, sb->sb_start + sb->sb_size, nBytes, 0);

此处才真正调用了系统调用recv接收数据

	}
      if (nBytes != -1)
	{
	  sb->sb_size += nBytes;
	}
      else
	{
	  int sockerr = GetSockError();
	  RTMP_Log(RTMP_LOGDEBUG, "%s, recv returned %d. GetSockError(): %d (%s)",
	      __FUNCTION__, nBytes, sockerr, strerror(sockerr));
	  if (sockerr == EINTR && !RTMP_ctrlC)
	    continue;

	  if (sockerr == EWOULDBLOCK || sockerr == EAGAIN)
	    {
	      sb->sb_timedout = TRUE;
	      nBytes = 0;
	    }
	}
      break;
    }

  return nBytes;
}

另附上RTMPSockBuf结构体

  typedef struct RTMPSockBuf
  {
    int sb_socket;
    int sb_size;		/* number of unprocessed bytes in buffer */
    char *sb_start;		/* pointer into sb_pBuffer of next byte to process */
    char sb_buf[RTMP_BUFFER_CACHE_SIZE];	/* data read from socket */
    int sb_timedout;
    void *sb_ssl;
  } RTMPSockBuf;

其中,

/* needs to fit largest number of bytes recv() may return */
#define RTMP_BUFFER_CACHE_SIZE (16*1024)


总结一下,ReadN就是想从当前socket中尽可能读取N字节,其中会调用RTMPSockBuf_Fill不停的接收数据存入buffer,然后消费buffer,直到读够了N字节。

整体流程还是比较简洁清晰的,其实这儿的ReadN可以应用到任何同步阻塞读写的tcp socket应用中。最重要的还是这个RTMpSockBuf的设计思想,简单却又实用,毕竟很多使用到librtmp的著名应用最底层靠的还是它。





猜你喜欢

转载自blog.csdn.net/lucytheslayer/article/details/79737890