WebRTC源码分析之流-Stream


C++大牛们一直吐槽标准库中输入输出流设计是失败的。仅仅做一些简单的输入输出用起来还挺简单的,但要精细的控制输入输出时,使用起来确实很麻烦。WebRTC利用C标准库和系统接口封装了一系列便于使用的流。

流接口-StreamInterface

StreamInterface是一个虚基类,充当接口。其成员函数大部分也都是纯虚函数,需要在子类中被覆写。

类的关系图

在这里插入图片描述

StreamInterface源码分析

StreamInterface类所在的文件位置:src\rtc_base\stream.h stream.cc

/*循环的写入数据,直到写完指定的数据。*/
StreamResult StreamInterface::WriteAll(const void* data,size_t data_len,size_t* written,int* error) 
{
  StreamResult result = SR_SUCCESS;
  size_t total_written = 0, current_written;

  while (total_written < data_len) 
  {
    result = Write(static_cast<const char*>(data) + total_written,data_len - total_written, &current_written, error);
    if (result != SR_SUCCESS)
      break;  /*写入失败,重新写。*/

    total_written += current_written;   /*记录写了多少数据*/
  }

  if (written)
    *written = total_written;

  return result;
}

若直接调用Write()函数,可能写入部分数据后就返回了。在WriteAll()函数中,循环的调用Write()函数,直到将指定的数据写完。

Write()函数会在子类中覆写,在接口类StreamInterface中调用,会发生多态。

文件流-FileStream

FileStream底层使用了fread()、fwrite()函数,所以可以用于处理文本数据二进制数据

FileStream使用示例

工程

如何创建工程,参考《WebRTC源码分析之工程-project》,在src\examples\BUILD.gn中添加如下内容:

rtc_executable("webrtc_learn"){
    testonly = true
  sources = [
    "webrtclearn/main.cc"         
  ]
  deps = [
    "../rtc_base:rtc_base"       
  ]
}

代码

#include "rtc_base/stream.h"
#include <iostream>

using namespace std;
using namespace rtc;

int main() 
{
    /*创建文件流*/
    FileStream fs;

    /*打开文件*/
    bool ret = fs.Open("C:\\Users\\study\\Desktop\\123.txt", "w+", nullptr);
    if (!ret)
    {
        cout << "open failed" << endl;
    }

    /*往文件中写入数据*/
    size_t writeCount = 0;
    StreamResult sr = fs.Write("hello world", 11, &writeCount, nullptr);
    if (sr != SR_SUCCESS)
    {
        cout << "write failed,write count = " << writeCount << endl;
    }

    /*刷缓冲*/
    fs.Flush(); 

    /*将读写位置指向文件开头*/
    fs.SetPosition(0);

    /*读取刚才写入的数据*/
    size_t readCount = 0;
    char buf[1024];
    sr = fs.Read(buf, 1024, &readCount, nullptr);
    if (sr == SR_ERROR)
    {
        cout << "read failed,read count = " << readCount << endl;
    }
    else if (sr == SR_EOS)
    {
        cout << "end... " << endl;
    }

    buf[readCount] = '\0';

    cout << "buf = " << buf << endl;

    /*关闭文件*/
    fs.Close();

    return 0;
}

在这里插入图片描述在这里插入图片描述

FileStream源码分析

FileStream类所在的文件位置:src\rtc_base\stream.h stream.cc

这个类仅是对C语言文件操作函数的简单封装,没有什么难度。仅分析两个重要的函数Read()、Write()函数。

Read函数
StreamResult FileStream::Read(void* buffer,size_t buffer_len,size_t* read,int* error) 
{
  if (!file_)
    return SR_EOS;

  size_t result = fread(buffer, 1, buffer_len, file_);

  if ((result == 0) && (buffer_len > 0)) 
  {
    if (feof(file_))  
      return SR_EOS;   /*读到文件末尾*/

    if (error)
      *error = errno;

    return SR_ERROR;
  }

  if (read)
    *read = result;

  return SR_SUCCESS;
}

fread()函数返回0,有可能是出错,也可能是读到文件末尾了没有读取到数据。errno是C语言标准库中提供的全局变量,用于记录错误值。

Write函数
StreamResult FileStream::Write(const void* data,size_t data_len,size_t* written,int* error) 
{
  if (!file_)
    return SR_EOS;

  size_t result = fwrite(data, 1, data_len, file_);

  if ((result == 0) && (data_len > 0)) 
  {
    if (error)
      *error = errno;
      
    return SR_ERROR;
  }

  if (written)
    *written = result;

  return SR_SUCCESS;
}

fwrite()可以用于写二进制数据或文本数据。这个函数使用上不会太方便,指定的数据可能写不完就出错返回了,导致出错的原因可能是不重要的错误,所以可以继续写。若想把指定的数据全部写入文件,可以调用父类中的WriteAll()函数。

旋转文件流-FileRotatingStream

FileRotatingStream类会创建指定数量的文件,并且每个文件大小固定。在往文件写数据的时候,这些文件会轮流依次被写满,并且一直循环下去。例如,创建了3个文件,每个文件1MB,往文件中写入10MB的数据,只有最后3MB的数据被保存下来,其余数据就丢失了。

CallSessionFileRotatingStream类是对FileRotatingStream类的包装,FileRotatingStream类在超出文件大小时,总是保存最后的数据。有时在保存的数据中,开头的数据和结尾的数据是很重要的,而中间的数据不重要可以舍弃。CallSessionFileRotatingStream类就是这样保存数据的,当数据多的保存不下时,可以只保存开头和结尾的数据,将中间的数据丢掉。例如,要创建3个文件,总大小为8MB,在其中一个文件是总大小的一半4MB,剩下一半的空间声音文件均分,则剩下的两个文件每个大小为2MB。4MB大小的文件用于记录数据的前4MB,剩余的2个文件记录数据的最后4MB。若往文件中写入20MB的数据,则前4MB数据在4MB大小的文件中,最后4MB的数据在剩余的两个文件中,中间的12MB数据就丢失了。

FileRotatingStream使用示例

工程

如何创建工程,参考《WebRTC源码分析之工程-project》,在src\examples\BUILD.gn中添加如下内容:

rtc_executable("webrtc_learn"){
    testonly = true
  sources = [
    "webrtclearn/main.cc"         
  ]
  deps = [
    "../rtc_base:rtc_base"       
  ]
}

代码

#include "rtc_base/file_rotating_stream.h"
#include <iostream>
#include <string>
#include <string.h>

using namespace std;
using namespace rtc;

int main() 
{
    /*文件所在的目录*/
    string dirPath = "C:\\Users\\study\\Desktop\\log";

    /*文件名的前缀*/
    string filePrefix = "webrtc_log";

    /*创建3个10字节大小的文件*/
    FileRotatingStream frs(dirPath, filePrefix, 10, 3);

    /*打开文件*/
    frs.Open();

    char buf[1024] = "0123456789abcdefghijklmnopqrstuvwxyz";
    
    StreamResult sr = frs.WriteAll(buf, strlen(buf), nullptr, nullptr);
    if (sr != SR_SUCCESS)
    {
        cout << "write failed" << endl;
    }
    
    /*关闭文件*/
    frs.Close();

    return 0;
}

运行结果如下图:

在这里插入图片描述
写文件的过程如下图:
在这里插入图片描述

当有3个文件的时候,文件旋转的过程是这样的:

  • 先创建0号文件,0号文件写满后,重命名为1号文件,再创建0号文件。
  • 将0号文件写满后,将1号文件重名为2号文件,将0号文件重名为1号文件,再创建0号文件。
  • 将0号文件写满后,将2号文件删除,将1号文件重名为2号文件,将0号文件重名为1号文件,再创建0号文件,继续写。0号文件写满后,重复本步骤。
    在这里插入图片描述

每次都是写0号文件,0号文件写满后,将文件依次往后重命名,并将最后一个文件删除。

CallSessionFileRotatingStream使用示例

工程

如何创建工程,参考《WebRTC源码分析之工程-project》,在src\examples\BUILD.gn中添加如下内容:

rtc_executable("webrtc_learn"){
    testonly = true
  sources = [
    "webrtclearn/main.cc"         
  ]
  deps = [
    "../rtc_base:rtc_base"       
  ]
}

代码

#include "rtc_base/file_rotating_stream.h"
#include <iostream>
#include <string>
#include <string.h>

using namespace std;
using namespace rtc;

int main() 
{
    string dirPath = "C:\\Users\\study\\Desktop\\log";

    /*60字节为所有文件的大小*/
    CallSessionFileRotatingStream cs(dirPath, 60);

    cs.Open();

    char buf[1024] = "0123456789"
                     "abcdefghijklmnopqrstuvwxyz"
                     "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    StreamResult sr = cs.WriteAll(buf, strlen(buf), nullptr, nullptr);
    if (sr != SR_SUCCESS)
    {
        cout << "write failed" << endl;
    }

    cs.Close();

    return 0;
}

运行结果如下图:

在这里插入图片描述

CallSessionFileRotatingStream类有两个默认值,一个是文件的前缀,一个是文件大小。src\rtc_base\file_rotating_stream.ccconst char kCallSessionLogPrefix[] = “webrtc_log”;定义了默认的文件名前缀"webrtc_log"src\rtc_base\file_rotating_stream.ccconst size_t CallSessionFileRotatingStream::kRotatingLogFileDefaultSize = 1024 * 1024;中定义了文件大小是1MB,在本示例中需要将1024*1024改为10

写文件的过程如下图:
在这里插入图片描述
在定义CallSessionFileRotatingStream类对象时,传入了总的文件大小是60字节,且文件大小的默认值被修改为了10字节。总大小的一半用于定义一个文件,这个文件接收数据的前30个字节,剩余的30字节除以每个文件大小10字节,得到3个文件。总共四个文件,一个文件30字节用于接收数据的前30字节,剩余的3个文件个10字节,这30字节用于接收数据的最后30字节,中间的数据会被丢弃。

在这里插入图片描述
0号-2号文件各10字节,3号文件30字节。在文件命名循环中,只有0号-2号文件进行循环,而3号文件就不参与循环了。不过3号文件也是由0号文件名一步一步循环到3号文件名,到了3号文件名就固定不变了。

FileRotatingStreamReader使用示例

工程

如何创建工程,参考《WebRTC源码分析之工程-project》,在src\examples\BUILD.gn中添加如下内容:

rtc_executable("webrtc_learn"){
    testonly = true
  sources = [
    "webrtclearn/main.cc"         
  ]
  deps = [
    "../rtc_base:rtc_base"       
  ]
}

代码

#include <iostream>
#include <string.h>
#include <string>
#include "rtc_base/file_rotating_stream.h"

using namespace std;
using namespace rtc;

int main() 
{
    string dirPath = "C:\\Users\\study\\Desktop\\log";
    string filePrefix = "webrtc_log";

    /*创建5个10字节大小的文件*/
    FileRotatingStream frs(dirPath, filePrefix, 10, 5);

    frs.Open();
    StreamState ss = frs.GetState();
    if (ss == SS_CLOSED)
    {
        cout << "open failed" << endl;
    }

    char buf[1024] = "0123456789abcdefghijklmnopqrstuvwxyz";

    StreamResult sr = frs.WriteAll(buf, strlen(buf), nullptr, nullptr);
    if (sr != SR_SUCCESS) 
    {
    cout << "write failed" << endl;
    }

    frs.Close();

    /*以上代码会创建4个文件,并把buf中的内容写入文件。*/


    /*下面代码会从这4个文件中读取数据*/

    FileRotatingStreamReader frsr(dirPath, filePrefix);

    memset(buf, 0, 1024);

    size_t ret = frsr.ReadAll(buf, sizeof(buf));
    buf[ret] = '\0';

    cout << "buf = " << buf << endl;

    return 0;
}

在这里插入图片描述
从多个文件中读取数据

FileRotatingStream源码分析

FileRotatingStream类所在的文件位置:src\rtc_base\FileRotatingStream.h FileRotatingStream.cc

FileWrapper类

FileWrapper类所在的文件位置:src\rtc_base\systemfile_wrapper.h file_wrapper.cc

FileWrapper类是对FILE*的简单包装,FileRotatingStream底层对文件的操作使用的就是这个类。其代码很简单,在这里仅给出使用示例。

工程

如何创建工程,参考《WebRTC源码分析之工程-project》,在src\examples\BUILD.gn中添加如下内容:

rtc_executable("webrtc_learn"){
    testonly = true
  sources = [
    "webrtclearn/main.cc"         
  ]
  deps = [
    "../rtc_base/system:file_wrapper"      
  ]
}

代码

#include "rtc_base/system/file_wrapper.h"
#include <iostream>
#include <string.h>

using namespace std;
using namespace webrtc;

int main() 
{   
    /*写文件*/
    FileWrapper fwWrite = FileWrapper::OpenWriteOnly("C:\\Users\\study\\Desktop\\123.txt");

    char buf[1024] = "hello world";
    bool ret = fwWrite.Write(buf, strlen(buf));
    if (!ret)
    {
        cout << "write failed" << endl;
    }

    fwWrite.Close();

    /*读文件*/
    memset(buf, 0, 1024);    /*将缓存清空*/

    FileWrapper fwRead = FileWrapper::OpenReadOnly("C:\\Users\\study\\Desktop\\123.txt");

    size_t count = fwRead.Read(buf, sizeof(buf));
    buf[count] = '\0';

    cout << "buf = " << buf << endl;
    
    fwRead.Close();
    
    return 0;
}

在这里插入图片描述
FileWrapper类的创建只能通过其静态函数成员OpenReadOnly()、OpenWriteOnly()创建并且创建的类对象,只能读文件或者只能写文件。

全局函数

有些对目录或文件的处理被定义成了全局函数。

对文件的处理
/*删除指定文件*/
bool DeleteFile(const std::string& file) 
{
         /*调用系统接口,删除文件。*/
  return ::unlink(file.c_str()) == 0;
}

/*给文件重命名*/
bool MoveFile(const std::string& old_file, const std::string& new_file) 
{
  /*将old命名为new,若new存在,则将其删除。*/
  return ::rename(old_file.c_str(), new_file.c_str()) == 0;
}

/*判断是否是文件*/
bool IsFile(const std::string& file) 
{
  struct stat st;
  int res = ::stat(file.c_str(), &st);   /*获取文件属性*/

  return res == 0 && !S_ISDIR(st.st_mode);  /*不是目录就是文件。*/
}

/*获取文件的大小*/
absl::optional<size_t> GetFileSize(const std::string& file) 
{
  struct stat st;
  if (::stat(file.c_str(), &st) != 0)
    return absl::nullopt;   /*返回空*/

  return st.st_size;
}

以上函数都是对文件的处理,都是调用了linux提供的系统调用。

对目录的处理
/*在目录名的最后添加/ */
std::string AddTrailingPathDelimiterIfNeeded(std::string directory) 
{
  if (absl::EndsWith(directory, "/"))    
  {
    return directory;    /*若directory以/结尾,则直接返回。*/
  }

  return directory + "/";    /*否则,在最后添加/。*/
}

/*将指定目录下,指定前缀文件名存放到列表中。*/
std::vector<std::string> GetFilesWithPrefix(const std::string& directory,const std::string& prefix) 
{
  /*目录以/结尾*/
  RTC_DCHECK(absl::EndsWith(directory, "/"));

  /*打开目录文件   dir是目录文件的句柄 */
  DIR* dir = ::opendir(directory.c_str());  

  if (dir == nullptr)
    return {};

  std::vector<std::string> file_list;

  /*遍历目录文件中存放的文件名*/
  for (struct dirent* dirent = ::readdir(dir);dirent;dirent = ::readdir(dir)) 
  {
    /*获取文件名*/
    std::string name = dirent->d_name;

    /*若文件名的前缀匹配的话,就加入文件列表。*/
    if (name.compare(0, prefix.size(), prefix) == 0) 
    {
      file_list.emplace_back(directory + name);
    }
  }

  /*关闭目录文件*/
  ::closedir(dir);

  return file_list;   /*返回文件列表*/
}

/*判断是否是目录*/
bool IsFolder(const std::string& file) 
{
  struct stat st;
  int res = ::stat(file.c_str(), &st);
  return res == 0 && S_ISDIR(st.st_mode);
}

目录文件名必须以/结尾,GetFilesWithPrefix是一个很重要的函数,这个函数会把指定目录下与文件名前缀相匹配的所有文件都存放在文件列表中,并将其返回。

构造器与析构器
FileRotatingStream::FileRotatingStream(const std::string& dir_path,const std::string& file_prefix,size_t max_file_size,size_t num_files)
    : dir_path_(AddTrailingPathDelimiterIfNeeded(dir_path)),   /*目录必须以/结尾*/
      file_prefix_(file_prefix),
      max_file_size_(max_file_size),
      current_file_index_(0),
      rotation_index_(0),
      current_bytes_written_(0),
      disable_buffering_(false) 
{
  RTC_DCHECK_GT(max_file_size, 0);   /*文件大小大于0*/
  RTC_DCHECK_GT(num_files, 1);       /*文件数大于1*/
  RTC_DCHECK(IsFolder(dir_path));    /*dir_path必须是目录*/

  /*清空vector*/
  file_names_.clear();

  /*给这num_files个文件构建名字*/
  for (size_t i = 0; i < num_files; ++i) 
  {
    file_names_.push_back(GetFilePath(i, num_files));
  }

  /*旋转索引,记录文件的最大编号。*/
  rotation_index_ = num_files - 1;
}

FileRotatingStream::~FileRotatingStream() {}

旋转索引记录的是最后一个文件的索引,文件名从0开始到最大文件数(num_files)减一。在构造器中,会把所有文件的文件名构建出来存放在文件列表(file_names_)中,便于之后的使用,避免以后每次使用时现去构造。

/*传入文件索引,和文件数。组建index索引文件的文件名*/
std::string FileRotatingStream::GetFilePath(size_t index,size_t num_files) const 
{
  RTC_DCHECK_LT(index, num_files);   /*索引要小于文件数目*/

  const size_t buffer_size = 32;
  char file_postfix[buffer_size];
  
  /*计算文件名中有几个零填充*/
  const int max_digits = std::snprintf(nullptr, 0, "%zu", num_files - 1);

  RTC_DCHECK_LT(1 + max_digits, buffer_size);   /*数组内存是否溢出*/

  /*后缀等宽,以_开头,左补零。*/
  std::snprintf(file_postfix, buffer_size, "_%0*zu", max_digits, index);

  /*组建文件名:目录+文件名前缀+文件名后缀*/
  return dir_path_ + file_prefix_ + file_postfix;
}

获取第i个文件的文件名,所有的文件有统一的前缀,每个文件名最后都有编号,且编号的宽度都是一样的,前补零。若总共有数百个文件,编号宽度为3,则编号是:000、001、002、…、010、011、…、100、101…

打开文件
/*打开当前文件*/
bool FileRotatingStream::OpenCurrentFile() 
{
  /*关闭当前0号文件,重新创建0号文件。*/
  CloseCurrentFile();

  RTC_DCHECK_LT(current_file_index_, file_names_.size());
    
  /*根据当前文件索引,获取文件名。*/
  std::string file_path = file_names_[current_file_index_];    
    
  /*我们应该始终写入第0号文件*/
  RTC_DCHECK_EQ(current_file_index_, 0);
  int error;

  /*打开文件,即创建0号文件。*/
  file_ = webrtc::FileWrapper::OpenWriteOnly(file_path, &error);

  if (!file_.is_open()) 
  {
    std::fprintf(stderr, "Failed to open: %s Error: %d\n", file_path.c_str(),error);
    return false;
  }

  return true;
}

FileRotatingStream每次写操作,都是操作的0号文件,这个文件由类数据成员file_代表。current_file_index_的值应该一直为0。

bool FileRotatingStream::Open() 
{ 
  /*获取指定目录下,与文件前缀匹配的所有文件名。*/
  std::vector<std::string> matching_files = GetFilesWithPrefix(dir_path_, file_prefix_);

  /*删除匹配的文件*/
  for (const auto& matching_file : matching_files) 
  {
    if (!DeleteFile(matching_file)) 
    {
      std::fprintf(stderr, "Failed to delete: %s\n", matching_file.c_str());
    }
  }

         /*创建0号文件*/
  return OpenCurrentFile();
}

在创建文件之前,需要将指定目录下所有与文件名前缀匹配的文件全部删除。删除文件以后,再调用OpenCurrentFile()函数,创建0号文件,接下来将数据写入0号文件

读写操作
StreamResult FileRotatingStream::Read(void* buffer,size_t buffer_len,size_t* read,int* error) 
{
  RTC_DCHECK(buffer);
  RTC_NOTREACHED();   /*默认操作是结束进程*/
  return SR_EOS;
}

FileRotatingStream类是用于写文件的,对于读操作是什么也不做。

StreamResult FileRotatingStream::Write(const void* data,size_t data_len,size_t* written,int* error) 
{
  /*判断文件是否是打开的*/
  if (!file_.is_open())  
  {
    std::fprintf(stderr, "Open() must be called before Write.\n");
    return SR_ERROR;
  }

  /*当前文件还有富余*/
  RTC_DCHECK_LT(current_bytes_written_, max_file_size_);   

  /*计算可以写入当前文件的长度*/
  size_t remaining_bytes = max_file_size_ - current_bytes_written_;
  size_t write_length = std::min(data_len, remaining_bytes);

  /*写入文件*/
  if (!file_.Write(data, write_length)) 
  {
    return SR_ERROR;
  }

  /*刷缓冲。*/
  if (disable_buffering_ && !file_.Flush()) 
  {
    return SR_ERROR;
  }

  /*更新当前文件大小*/
  current_bytes_written_ += write_length;

  /*如果written变量不为null,则记录写入数据的长度。*/
  if (written) 
  {
    *written = write_length;
  }

  /*当前文件写满了,需要旋转文件。*/
  if (current_bytes_written_ >= max_file_size_)   
  {
    RTC_DCHECK_EQ(current_bytes_written_, max_file_size_);   

    /*旋转文件*/
    RotateFiles();
  }

  return SR_SUCCESS;
}

FileRotatingStream类一直操作0号文件的句柄,所有数据都写入了0号文件。但0号文件的空间总是有限的,不能一直写入数据,所以 RotateFiles()函数很重要。简单的说,当0号文件写满时, RotateFiles()函数会把0号文件重命名为1号文件,然后再创建一个0号文件,这样FileRotatingStream类就会有一个空的0号文件可以用了。

旋转文件
void FileRotatingStream::RotateFiles() 
{
  /*删除当前已满的0号文件*/
  CloseCurrentFile();

  RTC_DCHECK_LT(rotation_index_, file_names_.size());

  /*获取要被删除的文件*/
  std::string file_to_delete = file_names_[rotation_index_];

  /*判断是否是文件*/
  if (IsFile(file_to_delete)) 
  {
    /*将最后一个文件删除*/
    if (!DeleteFile(file_to_delete)) 
    {
      std::fprintf(stderr, "Failed to delete: %s\n", file_to_delete.c_str());
    }
  }

  /*删除了最后编号的文件,将之前编号的文件往后重命名。*/
  for (auto i = rotation_index_; i > 0; --i) 
  {
    std::string rotated_name = file_names_[i];
    std::string unrotated_name = file_names_[i - 1];

    if (IsFile(unrotated_name)) 
    {
      /*文件名往后移*/
      if (!MoveFile(unrotated_name, rotated_name))
      {
        std::fprintf(stderr, "Failed to move: %s to %s\n",unrotated_name.c_str(), rotated_name.c_str());
      }
    }
  }

  /*空出0号文件,再创建0号文件。*/
  OpenCurrentFile();

  /*调用预留的接口,调用子类的处理。*/
  OnRotation();
}

rotation_index_数据成员保存着最后一个文件的索引。当0号文件写满后,将最后一个文件删除,剩余的文件依次往后重命名。之前的0号文件被重命名为1号文件,这样0号文件就空出来了,调用OpenCurrentFile()再创建一个0号文件

其他函数
/*获取文件状态*/
StreamState FileRotatingStream::GetState() const 
{
  /*文件是否是打开状态,若是返回SS_OPEN,否则返回SS_CLOSED。*/
  return (file_.is_open() ? SS_OPEN : SS_CLOSED);
}

/*禁用缓存*/
bool FileRotatingStream::DisableBuffering() 
{
  disable_buffering_ = true;
  return true;
}

/*根据索引获取文件名*/
std::string FileRotatingStream::GetFilePath(size_t index) const 
{
  RTC_DCHECK_LT(index, file_names_.size());
  return file_names_[index];
}

/*关闭当前文件*/
void FileRotatingStream::CloseCurrentFile() 
{
  if (!file_.is_open()) 
  {
    return;
  }

  current_bytes_written_ = 0;
  file_.Close();   /*关闭文件*/
}

/*刷缓冲*/
bool FileRotatingStream::Flush() 
{
  /*如果文件关闭了,刷缓冲失败。*/
  if (!file_.is_open()) 
  {
    return false;
  }

  return file_.Flush();
}

/*关闭流文件*/
void FileRotatingStream::Close() 
{
  CloseCurrentFile();
}

剩余的这些函数没什么难点,注释的已经很清楚了。

CallSessionFileRotatingStream源码分析

CallSessionFileRotatingStream类所在的文件位置:src\rtc_base\FileRotatingStream.h FileRotatingStream.cc

CallSessionFileRotatingStream在定义类对象时,传入的是总文件的大小,并且有两个默认值。一个是文件名的前缀默认值是"webrtc_log",文件默认大小是1MB。在生成文件的时候,其中一个文件站总大小的一半,剩余大小除以1MB就是剩余文件的数量。

构造器
CallSessionFileRotatingStream::CallSessionFileRotatingStream(const std::string& dir_path,size_t max_total_log_size)
    : FileRotatingStream(dir_path,kCallSessionLogPrefix,max_total_log_size / 2,GetNumRotatingLogFiles(max_total_log_size) + 1),
      max_total_log_size_(max_total_log_size),
      num_rotations_(0) 
{
  RTC_DCHECK_GE(max_total_log_size, 4);
}

GetNumRotatingLogFiles(max_total_log_size) + 1中的+1,加的是最大的那个文件。

文件大小和数量
/*计算文件的个数*/
size_t CallSessionFileRotatingStream::GetNumRotatingLogFiles(size_t max_total_log_size) 
{
  return std::max((size_t)2,(max_total_log_size / 2) / kRotatingLogFileDefaultSize);
}

max_total_log_size / 2是总文件大小的一半,这一半用于保存数据的后半部分。

(max_total_log_size / 2) / kRotatingLogFileDefaultSize得到是文件的数量,但这个值需要和2比较取最大值,因为文件数量至少是2个。kRotatingLogFileDefaultSize的默认值是1024*1024,当总文件的大小小于2*1024*1024时,文件的数量是2个。

size_t CallSessionFileRotatingStream::GetRotatingLogSize(size_t max_total_log_size) 
{
  /*获取文件的个数*/
  size_t num_rotating_log_files = GetNumRotatingLogFiles(max_total_log_size);

  /*计算日志文件的大小        个数大于2,则默认是1MB*/
  size_t rotating_log_size = num_rotating_log_files > 2 ? kRotatingLogFileDefaultSize : max_total_log_size / 4;

  return rotating_log_size;
}

当文件的数量大于2时(这里的2个文件并不包含最大的那个文件),文件的大小是默认值。当文件的数量等于2时,max_total_log_size / 2等到总文件大小的一半,分成两个文件需要再除以二得到每个文件的大小,即max_total_log_size / 2 / 2,也就是max_total_log_size / 4

旋转文件
void CallSessionFileRotatingStream::OnRotation() 
{
  ++num_rotations_;
    
  if (num_rotations_ == 1) 
  {
    /*更改文件的大小*/
    SetMaxFileSize(GetRotatingLogSize(max_total_log_size_));
  } 
  else if (num_rotations_ == (GetNumFiles() - 1)) 
  {
    SetRotationIndex(GetRotationIndex() - 1);
  }
}

最大的那个文件记录了最开始的信息,这个文件一旦旋转到最后,一方面不能被删除了,另一方面不会继续参与文件旋转了,这样这个文件就被保存下来了。为了实现这个要求,需要干预父类中文件旋转操作。正好父类FileRotatingStream留了接口函数OnRotation()

num_rotations_用于记录旋转的次数,当num_rotations_等于1时,表示第一次旋转文件,这时0号文件文件写满了,也就是最大的文件写满了,需要更改文件的大小,以后再创建的文件就是小文件了。

num_rotations_等于所有文件数减一时,说明最大的文件旋转到最后了,需要重新设置旋转索引,将最大的文件从旋转索引中去掉,避免以后参与旋转。

FileRotatingStreamReader源码分析

FileRotatingStreamReader类所在的文件位置:src\rtc_base\FileRotatingStream.h FileRotatingStream.cc

构造器
FileRotatingStreamReader::FileRotatingStreamReader(const std::string& dir_path,const std::string& file_prefix) 
{
  file_names_ = GetFilesWithPrefix(AddTrailingPathDelimiterIfNeeded(dir_path),file_prefix);

  absl::c_sort(file_names_, std::greater<std::string>());
}

在指定的目录下,获取文件名与前缀匹配的所有文件名。并将这些文件名按照序号大小由大到小排序。文件序号越大数据也就越早。

文件大小
size_t FileRotatingStreamReader::GetSize() const 
{
  size_t total_size = 0;

  for (const auto& file_name : file_names_) 
  {
    total_size += GetFileSize(file_name).value_or(0);
  }

  return total_size;
}

遍历文件,将文件大小加在一起,获得所有文件的大小。GetFileSize()的返回值是absl::optional<size_t>类型,这个函数可能不返回文件大小,这时将文件大小设置为0。

读函数
size_t FileRotatingStreamReader::ReadAll(void* buffer, size_t size) const 
{
  size_t done = 0;

  for (const auto& file_name : file_names_) 
  {
    if (done < size) 
    {
      webrtc::FileWrapper f = webrtc::FileWrapper::OpenReadOnly(file_name);
      if (!f.is_open()) 
      {
        break;
      }
      
      done += f.Read(static_cast<char*>(buffer) + done, size - done);
    } 
    else 
    {
      break;
    }
  }

  return done;
}

这个函数会根据文件名,依次打开文件,将文件内容依次读到buffer中。

内存流-MemoryStream

MemoryStream定义的类对象把数据存储到内存,用读写文件的方式读写缓冲,方便了内存缓冲区的使用。

MemoryStream使用示例

工程

如何创建工程,参考《WebRTC源码分析之工程-project》,在src\examples\BUILD.gn中添加如下内容:

rtc_executable("webrtc_learn"){
    testonly = true
  sources = [
    "webrtclearn/main.cc"         
  ]
  deps = [
    "../rtc_base:rtc_base_tests_utils"      
  ]
}

代码

#include <string.h>
#include <iostream>
#include "rtc_base/memory_stream.h"

using namespace std;
using namespace rtc;

int main() 
{
    MemoryStream ms;
    
    char buf[1024] = "hello world";

    ms.Write(buf, strlen(buf), nullptr, nullptr);

    ms.Rewind();

    memset(buf, 0, 1024);

    size_t count = 0;
    StreamResult sr = ms.Read(buf, 1024, &count, nullptr);
    if (sr == SR_EOS)
    {
        cout << "end..." << endl;
    }
    else if (sr == SR_ERROR)
    {
        cout << "read failed" << endl;
    }

    buf[count] = '\0';

    cout << "buf = " << buf << endl;
            
    return 0;
}

在这里插入图片描述
定义了ms对象,就代表着内存的一段缓冲区,可以通过其内置的成员函数,操作这个缓冲区。

MemoryStream源码分析

MemoryStream类所在的文件位置:src\rtc_base\memory_stream.h memory_stream.cc

这个类的源码没有什么难点,直接看注释就可以了。

/*返回流的状态,内存是一直可用的,所以总是SS_OPEN状态。*/
StreamState MemoryStream::GetState() const 
{
  return SS_OPEN;
}

StreamResult MemoryStream::Read(void* buffer,size_t bytes,size_t* bytes_read,int* error) 
{
  /*读写位置超过了数据位置,则认为到了文件末尾了。*/
  if (seek_position_ >= data_length_) 
  {
    return SR_EOS;
  }

  /*可读数据的长度*/
  size_t available = data_length_ - seek_position_;

  /*设置读取数据的大小*/
  if (bytes > available) 
  {
    // Read partial buffer
    bytes = available;
  }

  /*拷贝数据*/
  memcpy(buffer, &buffer_[seek_position_], bytes);

  /*更新读写位置*/
  seek_position_ += bytes;

  if (bytes_read) 
  {
    *bytes_read = bytes;    /*记录读取数据的大小*/
  }

  return SR_SUCCESS;
}

StreamResult MemoryStream::Write(const void* buffer,size_t bytes,size_t* bytes_written,int* error) 
{
  /*可用的空间*/
  size_t available = buffer_length_ - seek_position_;

  if (0 == available) 
  {
    size_t new_buffer_length = std::max(((seek_position_ + bytes) | 0xFF) + 1, buffer_length_ * 2);

    /*申请空间*/
    StreamResult result = DoReserve(new_buffer_length, error);

    if (SR_SUCCESS != result) 
    {
      return result;
    }

    RTC_DCHECK(buffer_length_ >= new_buffer_length);

    /*现在可以的空间*/
    available = buffer_length_ - seek_position_;
  }

  /*写入数据的长度不能超过可用数据长度*/
  if (bytes > available) 
  {
    bytes = available;
  }

  /*写入数据*/
  memcpy(&buffer_[seek_position_], buffer, bytes);

  /*更新位置*/
  seek_position_ += bytes;

  /*更新数据长度*/
  if (data_length_ < seek_position_) 
  {
    data_length_ = seek_position_;
  }

  if (bytes_written) 
  {
    *bytes_written = bytes;
  }

  return SR_SUCCESS;
}

void MemoryStream::Close() 
{
  // nothing to do
}

/*设置读写位置*/
bool MemoryStream::SetPosition(size_t position) 
{
  if (position > data_length_)
    return false;

  seek_position_ = position;

  return true;
}

/*获取文件读取位置*/
bool MemoryStream::GetPosition(size_t* position) const 
{
  if (position)
    *position = seek_position_;

  return true;
}

/*将读写位置置到开头*/
void MemoryStream::Rewind() 
{
  seek_position_ = 0;
}

/*获取数据长度*/
bool MemoryStream::GetSize(size_t* size) const 
{
  if (size)
    *size = data_length_;

  return true;
}

/*预先设置空间大小*/
bool MemoryStream::ReserveSize(size_t size) 
{
  return (SR_SUCCESS == DoReserve(size, nullptr));
}

MemoryStream::MemoryStream() {}

MemoryStream::MemoryStream(const char* data) 
{
  /*不提供长度,默认\0作为结束。*/
  SetData(data, strlen(data));
}

MemoryStream::MemoryStream(const void* data, size_t length) 
{
  SetData(data, length);
}

MemoryStream::~MemoryStream() 
{
  delete[] buffer_;   /*释放内存*/
}

void MemoryStream::SetData(const void* data, size_t length) 
{
  data_length_ = buffer_length_ = length;

  /*释放之前的空间*/
  delete[] buffer_;    

  /*根据大小申请新的空间*/
  buffer_ = new char[buffer_length_];

  /*拷贝数据*/
  memcpy(buffer_, data, data_length_);

  /*将读写位置指向开始*/
  seek_position_ = 0;
}

/*预申请空间*/
StreamResult MemoryStream::DoReserve(size_t size, int* error) 
{
  /*若预申请的空间小于实际的空间,直接返回。*/
  if (buffer_length_ >= size)
    return SR_SUCCESS;

  /*重新申请空间*/
  if (char* new_buffer = new char[size]) 
  {
    /*将之前的内容拷贝到新空间中*/
    memcpy(new_buffer, buffer_, data_length_);

    /*释放之前的空间*/
    delete[] buffer_;  

    buffer_ = new_buffer;

    buffer_length_ = size;

    return SR_SUCCESS;
  }

  if (error)
  {
    *error = ENOMEM;   /*内存溢出*/
  }

  return SR_ERROR;
}

小结

本文介绍了WebRTC中stream的实现,特别是了解了FileRotatingStream类的使用方式,为RTC_LOG的使用做了铺垫。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值