[sylar]C++高性能服务器框架——序列化模块

445 阅读7分钟

序列化模块概述

序列化模块提供序列化与反序列化的功能,支持大端序和小端序,底层使用链表的形式存储数据,这样可以节省内存,管理内存碎片。该模块支持所有基本类型数据的写入和读取,可以选择固定字节长度或可变字节长度写入,使用可变字节长度时,使用Zigzag将有符号整型压缩后再进行Varint编码,这样能够节省大量内存空间。再写入数据时,将数据写入链表的最后一个节点中,若写不下时,创建新的节点继续写入数据。

Varint & Zigzag

Varint是一种使用一个或多个字节序列化整数的方法,会把整数编码为变长字节。对于32位整型数据经过Varint编码后需要15个字节,小的数字使用1个byte,大的数字使用5个bytes。64位整型数据编码后占用110个字节。在实际场景中小数字的使用率远远多于大数字,因此通过Varint编码对于大部分场景都可以起到很好的压缩效果。

Zigzag算法将有符号负整数转为正数,这样能够节省字节,因为负数的二进制位几乎全为1。

编码

原理:使用小端序低位字节存入数组低地址高位字节存入数组高地址

   1. 判断`value`是否大于`127`(1277位的最大值),若值大于`127`则还需要新的字节来保存`value`
   2. `(value & 0x7f) | 0x80`; 低七位保持不变,最高位置为`1`,表示这个字节之后还有数据要读
   3. `value >>= 7`; 右移七位,判断是否需要新的字节保存数据;
void EncodeVarint(uint32_t value) {
    uint8_t temp[5];
    uint8_t i = 0;
    while(value > 127) {
        temp[i++] = (value & 0x7f) | 0x80;
        value >>= 7;
    }
    temp[i] = value;
    write(tmp, i);
}

压缩

原理:将-1转为11转为2-2转为32转为4

//将-1变为1,1变为2
void EncodeZigzag32(const int32_t value) {
    if(v < 0) {
        return ((uint32_t)(-v)) * 2 - 1;
    } else {
        return value * 2;
    }
}

解码

原理:数组低地址保存到数据的低地址,数组高地址保存到数据的高地址

  1. b每次读8个字节,判断b是否小于128,若小于则说明最高位为1,该字节是最后一个字节,读完就返回。
  2. 若大于则说明还有数据没有取完,将数据的最高位置为0然后左移i位,和result做与操作将该字节放到数据的高位。
void DecoderVarint() {
    uint32_t result;
    for(uint i = 0; i < 32; i += 7) {
        uint8_t b = readFuint8();
        if(b < 0x80) {
            result |= (uint32_t)b << i;
            break;
        } else {
            result |= ((uint32_t)(b & 0x7f) << i);
        }
    }
}

解压缩

原理:

  1. 对输入整数v进行右移一位操作(即v >> 1),将其向右移动一位,相当于除以2。
  2. (v & 1),获取其二进制形式的最低位,若为奇数则最低位为1,取反为-1,若为偶数则最低位为0
  3. 步骤1和步骤2结果做异或运算。
void DecodeZigzag(const uint32_t& v) {
    return (v >> 1) ^ -(v & 1);
}

详解

读写操作覆盖了所有的类型,这里就不一一举例了,只举几个例子。

class ByteArray

Node(链表结构)

所有的数据都存在这个链表结构中,在堆区开辟内存存储在ptr中;size为一个节点大小,一般设置为一个页面大小4KB;next指向下一个节点。

struct Node {
    Node(size_t s);
    Node();
    ~Node();
​
    char* ptr;
    Node* next;
    size_t size;
};

mumber(成员变量)

// 内存块的大小
size_t m_baseSize;
// 当前操作位置
size_t m_position;
// 当前的总容量
size_t m_capacity;
// 当前数据的大小
size_t m_size;
// 字节序,默认大端
int8_t m_endian;
// 第一个内存块指针
Node* m_root;
// 当前操作的内存块指针
Node* m_cur;

ByteArray(构造函数)

ByteArray::ByteArray(size_t base_size)
    :m_baseSize(base_size)
    ,m_position(0)
    ,m_capacity(base_size)
    ,m_size(0)
    ,m_endian(SYLAR_BIG_ENDIAN) 
    ,m_root(new Node(base_size))
    ,m_cur(m_root) {
}

~ByteArray(析构函数)

ByteArray::~ByteArray() {
    Node* temp = m_root;
    while (temp) {
        m_cur = temp;
        temp = temp->next;
        delete m_cur;
    }
}

writeFint8(写int8:固定长度)

void ByteArray::writeFint8(int8_t value) {
    write(&value, sizeof(value));
}

writeFint16(写int16:固定长度)

void ByteArray::writeFint16(int16_t value) {
    // 两个字节需要判断字节序
    if (m_endian != SYLAR_BYTE_ORDER) {
        value = byteswap(value);
    }
    write(&value, sizeof(value));
}

EncodeZigzag(压缩)

static uint32_t EncodeZigzag(const int32_t& v) {
    if (v < 0) {
        return ((uint32_t)(-v)) * 2 - 1;
    } else {
        return v * 2;
    }
}

DecodeZigzag(解压缩)

static int32_t DecodeZigzag(const uint32_t& v) {
    return (v >> 1) ^ -(v & 1);
}

writeUint32(写Uint32:不固定长度)

void ByteArray::writeUint32(uint32_t value) {
    uint8_t tmp[5];
    uint8_t i = 0;
    while (value >= 0x80) {
        tmp[i++] = (value & 0x7F) | 0x80;
        value >>= 7;
    }
    tmp[i++] = value;
    write(tmp, i);
}

writeInt32(写Int32:不固定长度)

void ByteArray::writeInt32(int32_t value) {
    writeUint32(EncodeZigzag(value));
}

writeFloat(写Float:固定长度)

void ByteArray::writeFloat(float value) {
    uint32_t v;
    memcpy(&v, &value, sizeof(value));
    writeFuint32(v);
}

writeStringF16(写StringF16:固定长度)

void ByteArray::writeStringF16(const std::string& value) {
    writeFuint16(value.size());
    write(value.c_str(), value.size());
}

readFint8(读int8:固定长度)

int8_t ByteArray::readFint8() {
    int8_t v;
    read(&v, sizeof(v));
    return v;
}

readFint16(读int16:固定长度)

int16_t ByteArray::readFint16() {
    int16_t v;
    read(&v, sizeof(v));
    if(m_endian == SYLAR_BYTE_ORDER) {
        return v;
    } else {
        return byteswap(v);
    }
}  

readUint32(读Uint32:不固定长度)

uint32_t ByteArray::readUint32() {
    uint32_t result = 0;
    for (uint i = 0; i < 32; i += 7) {
        uint8_t b = readFuint8();
        if (b < 0x80) {
            result |= ((uint32_t)b) << i;
            break;
        } else {
            result |= ((uint32_t)(b & 0x7f) << i);
        }
    }
    return result;
}

readInt32(读int32:不固定长度)

int32_t ByteArray::readInt32() {
    return DecodeZigzag(readUint32());
}

readFloat(读Float:固定长度)

float ByteArray::readFloat() {
    uint32_t v = readFuint32();
    float value;
    memcpy(&value, &v, sizeof(v));
    return value;
}

readStringF16(读StringF16:固定长度)

std::string ByteArray::readStringF16() {
    uint16_t len = readFuint16();
    std::string buff(len, 0);
    read(&buff[0], len);
    return buff;
}

clear(清空ByteArray)

void ByteArray::clear() {
    m_position = m_size = 0;
    m_capacity = m_baseSize;
    Node* tmp = m_root->next;
    while (tmp) {
        m_cur = tmp;
        tmp = tmp->next;
        delete m_cur;
    }
    m_cur = m_root;
    m_root->next = nullptr;
}

write(写操作,改变position)

void ByteArray::write(const void* buf, size_t size) {
    if (size == 0) {
        return;
    }
    // 增加容量
    addCapacity(size);

    // 在当前Node的位置
    size_t npos = m_position % m_baseSize;
    // 当前Node剩余容量
    size_t ncap = m_cur->size - npos;
    // buf写到哪了
    size_t bpos = 0;

    while (size > 0) {
        // 该节点剩余的位置比写入的数据多
        if (ncap >= size) {
            // 在当前位置将所有数据都写入
            memcpy(m_cur->ptr + npos, static_cast<const char*>(buf) + bpos, size);
            // 若正好写满了这个节点,则跳到下一个节点
            if (m_cur->size == (npos + size)) {
                m_cur = m_cur->next;
            }
            // 更新当前操作的位置
            m_position += size; 
            // 更新数据写到哪个位置
            bpos += size;
            // 数据写完了
            size = 0;
        // 该节点不够写的
        } else {
            // 将数据写入这个节点的剩余位置
            memcpy(m_cur->ptr + npos, static_cast<const char*>(buf) + bpos, ncap);
            // 更新当前操作位置
            m_position += ncap;
            // 更新buf写到哪了
            bpos += ncap;
            // 更新剩余数据
            size -= ncap;
            // 指向下一个节点
            m_cur = m_cur->next;
            // 剩余大小为base大小
            ncap = m_cur->size;
           	// 更新在当前节点的位置
            npos = 0;
        }
    }
    // 更新size
    if (m_position > m_size) {
        m_size = m_position; 
    }
}

read(读操作,改变position)

void ByteArray::read(void* buf, size_t size) {
    // size比当前操作位置后的数据还多
    if (size > getReadSize()) {
        throw std::out_of_range("not enougth len");
    }
	
    // 当前节点的位置
    size_t npos = m_position % m_baseSize;
    // 当前节点剩余容量
    size_t ncap = m_cur->size - npos;
    // buf读到哪了
    size_t bpos = 0;

    while (size > 0) {
        // 该节点剩余的位置比要读的数据多
        if (ncap >= size) {
            // 将剩余的数据都读到buf中
            memcpy(static_cast<char*>(buf) + bpos, m_cur->ptr + npos, size);
            // 若正好读完这个节点,则跳到下一个节点
            if (m_cur->size == (npos + size)) {
                m_cur = m_cur->next;
            }
            // 更新当前操作位置
            m_position += size;
            // 更新当前读到哪了
            bpos += size;
            size = 0;
          // 该节点不够读的
        } else {
            // 将该节点剩余的容量都读到buf中
            memcpy(static_cast<char*>(buf) + bpos, m_cur->ptr + npos, ncap);
            // 更新当前操作位置
            m_position += ncap;
            // 更新读到哪了
            bpos += ncap;
            // 更新剩余多少数据没读
            size -= ncap;
            // 指向下一个节点
            m_cur = m_cur->next;
            // 剩余大小为base大小
            ncap = m_cur->size;
            // 更新在当前节点的位置
            npos = 0;
        }
    }
}

read(读操作,不改变position)

void ByteArray::read(void* buf, size_t size, size_t position) const {
    if(size > (m_size - position)) {
        throw std::out_of_range("not enough len");
    }

    size_t npos = position % m_baseSize;
    size_t ncap = m_cur->size - npos;
    size_t bpos = 0;
    Node* cur = m_cur;
    while(size > 0) {
        if(ncap >= size) {
            memcpy((char*)buf + bpos, cur->ptr + npos, size);
            if(cur->size == (npos + size)) {
                cur = cur->next;
            }
            position += size;
            bpos += size;
            size = 0;
        } else {
            memcpy((char*)buf + bpos, cur->ptr + npos, ncap);
            position += ncap;
            bpos += ncap;
            size -= ncap;
            cur = cur->next;
            ncap = cur->size;
            npos = 0;
        }
    }
}

setPosition(设置当前位置)

void ByteArray::setPosition(size_t v) {
    // 比容量还大,抛出异常
    if (v > m_capacity) {
        throw std::out_of_range("setPosition out of range");
    }
    // 设置当前位置
    m_position = v;
    // 若当前位置比数据大小还大
    if (m_position > m_size) {
		// 更新大小
        m_size = m_position;
    }
    // 链表遍历到当前位置
    m_cur = m_root;
    while (v > m_cur->size) {
        v -= m_cur->size;
        m_cur = m_cur->next;
    }
    if (v == m_cur->size) {
        m_cur = m_cur->next;
    }
}

addCapacity(增加容量)

void ByteArray::addCapacity(size_t size) {
    if (size == 0) {
        return;
    }
    // 剩余容量
    size_t old_cap = getCapacity();
    if (old_cap >= size) {
        return;
    }
	
    // 需要扩充多少字节数据
    size = size - old_cap;
    // 需要扩充多少节点
    size_t count = ceil(1.0 * size / m_baseSize);
    // 遍历到链表的末尾
    Node* tmp = m_root;
    while (tmp->next) {
        tmp = tmp->next;
    }
	
    Node* first = nullptr;
    // 创建新的节点
    for (size_t i = 0; i < count; ++i) {
        tmp->next = new Node(m_baseSize);
        if (first == nullptr) {
            first = tmp->next;
        }
        tmp = tmp->next;
        m_capacity += m_baseSize;
    }
	
    // 若剩余容量为0,则跳到下一个节点
    if (old_cap == 0) {
        m_cur = first;
    }
}

toString(转为string)

[m_position, m_size)

std::string ByteArray::toString() const {
    std::string str;
    str.resize(getReadSize());
    if (str.empty()) {
        return str;
    }
    read(&str[0], str.size(), m_position);
    return str;
}

toHexString(转为十六进制输出)

std::string ByteArray::toHexString() const {
    std::string str = toString();
    std::stringstream ss;
	
    for (size_t i = 0; i < str.size(); ++i) {
        // 32字节换行
        if (i > 0 && i % 32 == 0) {
            ss << std::endl;
        }
        ss << std::setw(2) << std::setfill('0') << std::hex
            << (int)(uint8_t)str[i] << " ";
    }
    return ss.str();
}

getReadBuffers(获取可读缓存,iovec数组,不改变position)

uint64_t ByteArray::getReadBuffers(std::vector<iovec>& buffers, uint64_t len) const {
    len = len > getReadSize() ? getReadSize() : len;
    if (len == 0) {
        return 0;
    }
	
    uint64_t size = len;

    size_t npos = m_position % m_baseSize;
    size_t ncap = m_cur->size - npos;
    struct iovec iov;
    Node* cur = m_cur;

    while (len > 0) {
        if (ncap >= len) {
            iov.iov_base = cur->ptr + npos;
            iov.iov_len = len;
            len = 0;
        } else {
            iov.iov_base = cur->ptr + npos;
            iov.iov_len = ncap;
            
            len -= ncap;
            cur = cur->next;
            ncap = cur->size;
            npos = 0;
        }
        buffers.push_back(iov);
    }
    return size;
}

getWriteBuffers(获取可写缓存,iovec数组,不改变position)

uint64_t ByteArray::getWriteBuffers(std::vector<iovec>& buffers, uint64_t len) {
    if (len == 0) {
        return 0;
    }

    addCapacity(len);
    uint64_t size = len;

    size_t npos = m_position % m_baseSize;
    size_t ncap = m_cur->size - npos;
    struct iovec iov;
    Node* cur = m_cur;
    
    while (len > 0) {
        if (ncap >= len) {
            iov.iov_base = cur->ptr + npos;
            iov.iov_len = len;
            len = 0;
        } else {
            iov.iov_base = cur->ptr + npos;
            iov.iov_len = ncap;
            
            len -= ncap;
            cur = cur->next;
            ncap = cur->size;
            npos = 0;
        }
        buffers.push_back(iov);
    }
    return size;
}

总结

使用序列化模块时要注意使用的方法是否会导致position发生变化,避免写入和读取数据时出core。