reSIProcate学习笔记(一)


Data

在reSIProcate中,Data是最基本的数据,代码中随处可见Data类型的形参和对象,Data.hxx和Data.cxx位于rutil目录下。

类注释说明中有如下一行概述

/* @brief An alternative to std::string, encapsulates an arbitrary buffer of bytes. */

可以认为Data和标准库中的string类似,并且其包含的字节数可以是任意的。

/* It has a variety of memory management styles that can be established at contruction time and changed later via setBuf().

Three modes of allocation are currently available:

    @li ‘Borrow’ – The Data instance is borrowing the memory from the passed in buffer. It will modify its contents as necessary, but will not deallocate it.     
    @li ‘Share’  – The Data instance will use the buffer in a read-only mode. If any attempt is made to modify the contents of the Data, it will copy the buffer and modify it. 
    @li ‘Take’   – The Data instance takes complete ownership of the buffer. The buffer must have been allocate using “new char[]” so that it can be freed with “delete char[]”. */

枚举类型ShareEnum位于类Data内,是Data的成员

enum ShareEnum
{
    Borrow = 0,
    Share  = 1,
    Take   = 2
};

/* Additionally, Data has a small locally-allocated buffer (member buffer) that
   it will use to hold small amounts of data. By default, this buffer can
   contain 16 bytes, meaning that Data will not use the heap unless it
   needs more than 16 bytes of space. The tradeoff here, of course, is that
   instances of Data will be larger than instances of std::string. Generally
   speaking, if you expect to need more than 16 bytes of room, and you cannot
   make good use of the flexible memory management offered by Data, you may want
   to use a std::string instead. */

当使用空间大于16字节时,Data才会在堆中开辟内存,而且Data的实例对象比string的实例对象占用空间大。这可以从宏定义中看出:

#ifndef RESIP_DATA_LOCAL_SIZE
#define RESIP_DATA_LOCAL_SIZE 16
#endif

Data的私有数据成员有:

typedef UInt32 size_type;
/* Trade off between in-object and heap allocation Larger LocalAlloc makes for larger objects that have Data members but bulk allocation/deallocation of Data  members. */
enum { LocalAlloc = RESIP_DATA_LOCAL_SIZE };
char* mBuf;
size_type mSize;
size_type mCapacity;
char mPreBuffer[LocalAlloc];
// Null terminator for mPreBuffer when mSize == LocalAlloc lands here; this is ok, because Borrow == 0.
// Note: we could use a char here, and expand mPreBuffer by 3 bytes, but this imposes a performance penalty
// since it requires operating on a memory location smaller than a word (requires masking and such).
size_type mShareEnum;

内联的构造函数如下:

inline Data() : mBuf(mPreBuffer), mSize(0), mCapacity(LocalAlloc), mShareEnum(Borrow)
{
    mBuf[mSize] = 0;
}

Data的构造函数共有17个重载:

class PreallocateType
{
    friend class Data;
    explicit PreallocateType(int);
};

/* Creates a data with a specified initial capacity.
   @param capacity  The initial capacity of the buffer
   @param foo       This parameter is ignored; it is merely used to disambuguate this constructor from the constructor that takes a single int. Always pass Data::Preallocate. */
Data(size_type capacity, const PreallocateType&);

/* Creates a data with a copy of the contents of the null-terminated string.
   @warning Passing a non-null-terminated string to this method would be a Really Bad Thing. */
Data(const char* str);

/* Creates a data with the contents of the buffer. 
   @param length Number of bytes in the buffer */
Data(const char* buffer, size_type length);
Data(const unsigned char* buffer, size_type length);
Data(const Data& data);

/* Creates a data with the contents of the string. */
explicit Data(const std::string& str);

/* Converts the passed in value into ascii-decimal representation, and then creates a "Data" containing that value. (E.g. "Data(75)" will create a Data with length=2, and contents of 0x37 0x35). */
explicit Data(Int32 value);
explicit Data(UInt32 value);
explicit Data(UInt64 value);

enum DoubleDigitPrecision 
{
    ZeroDigitPrecision = 0, OneDigitPrecision, 
    TwoDigitPrecision,      ThreeDigitPrecision, 
    FourDigitPrecision,     FiveDigitPrecision,
    SixDigitPrecision,      SevenDigitPrecision,
    EightDigitPrecision,    NineDigitPrecision,
    TenDigitPrecision,      MaxDigitPrecision
};

/* Converts the passed in value into ascii-decimal representation, and then creates a "Data" containing that value. (E.g. "Data(75.4,2)" will create a Data with length=4, and contents of 0x37 0x35 0x2E 0x34).
   @param precision  Number of digits after the decimal point. Trailing zeros will be removed. */
explicit Data(double value, Data::DoubleDigitPrecision precision = FourDigitPrecision);

/* Creates a buffer containing "true" or "false", depending on the value of "value". */
explicit Data(bool value);

/* Creates a buffer containing a single character. Is this silly? Maybe. Perhaps it can be removed. */
explicit Data(char c);

/* Creates a Data from the passed-in buffer. */
Data(ShareEnum, const char* buffer, size_type length);
Data(ShareEnum, const char* buffer, size_type length, size_type capacity);

/* Takes a null-terminated string and creates a buffer.
   @warning Passing a non-null-terminated string to this method would be a Really Bad Thing. */
Data(ShareEnum, const char* buffer);

/* Lazily creates a Data from the passed-in Data. 
   @warning Calling this with "Take" or "Borrow" is invalid and will cause an assertion or crash.
   @todo This implementation has some confusing and conflicting comments. (e.g. is Borrow actually okay? Can there be some way to use it with Take as long as you play with mShareEnum correctly?) */
Data(ShareEnum, const Data& staticData); // Cannot call with 'Take'

inline ~Data()
{
    if (mShareEnum == Take)
    {
        delete[] mBuf;
    }
}

Data API: setBuf

/* Set the Data to hold {buf} using share type {se}, which may be any of Share (read-only, no-free), Borrow (read-write, no-free) or Take (read-write, yes-free). Both the capacity and current length are set to {length}; you can call truncate2() afterwords to shorten.  The provided buffer (and its current contents) will be used going forward; any currently owned buffer will be released.
NOTE: The {buf} param is declared const to support Share type; for Borrow and Take the buffer may be written (e.g., treated non-const). */
Data& Data::setBuf(ShareEnum se, const char* buffer, size_type length)
{
   resip_assert(buffer);
   if (mShareEnum == Take)
   {
      delete[] mBuf;
   }
   mBuf = const_cast<char*>(buffer); // 去除const属性
   mCapacity = mSize = length;
   mShareEnum = se;
   return *this;
}

Data API: takeBuf

/* Take the data from {other}. Any current buffer is released. {this} will have the same storage mode as {other} and steal its buffer. All storage modes of {other} (Share,Borrow,Take) are legal. When done, {other} will be empty (it will ref its internal buffer). */
Data& Data::takeBuf(Data& other)
{
   if ( &other == this )
      return *this;
   if (mShareEnum == Data::Take)
      delete[] mBuf;
   if ( other.mBuf == other.mPreBuffer )
   {
      // plus one picks up the terminating safety NULL
      memcpy( mPreBuffer, other.mPreBuffer, other.mSize+1);
      mBuf = mPreBuffer;
   }
   else
   {
      mBuf = other.mBuf;
      other.mBuf = other.mPreBuffer;
   }
   mSize = other.mSize;
   mCapacity = other.mCapacity;
   mShareEnum = other.mShareEnum;

   // reset {other} to same state as the default Data() constructor
   // note that other.mBuf is set above
   other.mSize = 0;
   other.mCapacity = LocalAlloc;
   other.mShareEnum = Data::Borrow;
   other.mPreBuffer[0] = 0;

   return *this;
}

Data API: getBuf

/* Set size to be exactly {length}, extending buffer if needed. Also, reallocate buffer if needed so that it is writable. Buffer contents is NOT initialized, and existing contents may or may not be preserved.
   @note Purpose of this function is to provide a working buffer of fixed size that the application fills in after this call.
   @note If you want just the buffer without changing the size, use data() and cast-away the const-ness.
   @note The result may or may not be NULL terminated. The buffer is NULL terminated only when safe to do so without extra reallocation. */
char* Data::getBuf(size_type length)
{
   if (mShareEnum == Data::Share || mCapacity < length)
   {
      // will alloc length+1, so the term NULL below is safe
      resize(length, false);
      mBuf[length] = 0;
   }
   else if ( mCapacity != length )
   {
      mBuf[length] = 0;
   }
   // even if we don't NULL-term it, it may have NULL term from before.
   // But if external buffer (taken or borrow'd) then don't know if it
   // has a NULL or not.
   mSize = length;
   return mBuf;
}

Data API: duplicate, copy

/* Functional equivalent of: *this = Data(buf, length) and Data& copy(const char *buf, size_type length) but avoids an actual copy of the data if {other} is Shared or Borrowed.  Will have the same storage mode as {other}. */
Data& Data::duplicate(const Data& other)
{
   if (&other == this)
      return *this;

   if (mShareEnum == Data::Take)
      delete[] mBuf;

   if (other.mBuf == other.mPreBuffer)
   {
      // plus one picks up the terminating safety NULL
      memcpy(mPreBuffer, other.mPreBuffer, other.mSize + 1);
      mBuf = mPreBuffer;
   }
   else
   {
      mBuf = other.mBuf;
   }
   mSize = other.mSize;
   mCapacity = other.mCapacity;
   mShareEnum = other.mShareEnum;

   return *this;
}

/* Functional equivalent of: *this = Data(buf, length) but avoids the intermediate allocation and free. Also, will never decrease capacity. Safe to call even if {buf} is part of {this}.
   @note The result is always NULL terminated. Unfortunately, this requires a buffer allocation even if capacity exactly equals length. */
Data& Data::copy(const char *buf, size_type length)
{
   if (mShareEnum == Data::Share || mCapacity < length+1)
   {
      // will alloc length+1, so the term NULL below is safe
      resize(length, false);
   }
   // {buf} might be part of ourselves already, in which case {length}
   // is smaller than our capacity, so resize above won't happen, so
   // just memmove (not memcpy) and everything good
   mSize = length;
   if (mSize>0)
   {
      memmove(mBuf, buf, mSize);
   }
   // DONT do term NULL until after copy, because may be shifting contents
   // down and don't want to put NULL in middle of it
   mBuf[mSize] = 0;
   return *this;
}

存储编码方式及相关API:

typedef enum
{
    BINARY,
    BASE64,
    HEX
} EncodingType;

/* Computes the MD5 hash of the current data.
   @param type The encoding of the return (default is HEX)
   @return The MD5 hash, in the encoding specified by type. */      
Data Data::md5(EncodingType type = HEX) const
{
   MD5Context context;
   MD5Init(&context);
   MD5Update(&context, reinterpret_cast &lt; unsigned const char* &gt; (mBuf), (unsigned int)mSize);

   unsigned char digestBuf[16];
   MD5Final(digestBuf, &context);
   Data digest(digestBuf,16);

   switch(type)
   {
      case BINARY:
         return digest;
      case BASE64:
         return digest.base64encode(true);
      case HEX:
      default:
         return digest.hex();
   }
   resip_assert(0);
   return digest.hex();
}

/* Returns a hexadecimal representation of the contents of this Data. */
Data hex() const;

/* Returns the binary form of the hexadecimal string in this Data */
Data fromHex() const;

/* Performs RFC 3548 Base 64 decoding of the contents of this data.
   @returns A new buffer containing the unencoded representation */
Data base64decode() const;

/* Performs RFC 3548 Base 64 encoding of the contents of this data.
   @returns A new buffer containing the base64 representation */
Data base64encode(bool useUrlSafe = false) const;

查找和置换:

/*Finds a specified sequence of bytes in this Data.
  @param match The bytes to be found
  @param start Offset into this Data to start the search
  @returns An index to the start of the found bytes. */
Data::size_type Data::find(const Data&amp; match, size_type start = 0) const
{
   if (start < mSize)
   {
      ParseBuffer pb(mBuf + start, mSize - start);
      pb.skipToChars(match);
      if (!pb.eof())
      {
         return pb.position() - pb.start() + start;
      }
   }
   return Data::npos;
}

/* Replaces up to max occurrences of the bytes match with target. Returns the number of matches. */
int Data::replace(const Data&amp; match, const Data&amp; replaceWith, int max = INT_MAX)
{
   resip_assert(!match.empty());

   int count = 0;

   const int incr = int(replaceWith.size() - match.size());
   for (size_type offset = find(match, 0); 
        count < max && offset != Data::npos; 
        offset = find(match, offset+replaceWith.size()))
   {
      if (mSize + incr >= mCapacity)
      {
         resize((mCapacity + incr) * 3 / 2, true);
      }
      else
      {
         own();
      }

      // move the memory forward (or backward)
      memmove(mBuf + offset + replaceWith.size(), mBuf + offset + match.size(), mSize - offset - match.size());
      memcpy(mBuf + offset, replaceWith.data(), replaceWith.size());
      mSize += incr;
      ++count;
   }

   return count;
}

静态成员变量及静态成员函数:

/* Constant that represents a zero-length data. */
static const Data Empty;

/* Represents an impossible position; returned to indicate failure to find. */
static const size_type npos;

/* @internal
   This template is here to help diagnose API/ABI mismatches. Say you build
   librutil. A single Data::init(DataLocalSize<RESIP_DATA_LOCAL_SIZE>)
   function is implemented in Data.cxx, with the default
   value. (Data::init(DataLocalSize<16>) ends up being defined,
   Data::init(DataLocalSize<15>) does not) If, later, another build using
   that librutil tries to tweak the local alloc size to 24, it will end
   up attempting to call Data::init(DataLocalSize<24>); this will result
   in a link-time error, that while opaque, is less opaque than the stack
   corruption that would result otherwise. */
template <int S>;
struct DataLocalSize
{
    explicit DataLocalSize(size_t) {}
};

/* Initializes Data class.
   @note This method is a link time constraint. Don't remove it. */
static bool init(DataLocalSize<RESIP_DATA_LOCAL_SIZE> arg);

 

Leave a comment

邮箱地址不会被公开。 必填项已用*标注