CS144 骨架代码分析

2026-01-22

序列化

序列化:将结构化信息转化为大端序的二进制序列

Sponge和Minnow在这里处理有差异,Sponge是解析后直接返回string,Minnow是通过序列化一个Serializer类处理

class Serializer
{
  std::vector<Ref<std::string>> output_ {};
  std::string buffer_ {};

  void flush();

public:
  // 关键函数,将不同大小的无符号整数转化位大端序字节流
  template<std::unsigned_integral T>
  void integer( const T val )
  {
    constexpr uint64_t len = sizeof( T );

    for ( uint64_t i = 0; i < len; ++i ) {
      const uint8_t byte_val = val >> ( ( len - i - 1 ) * 8 );
      buffer_.push_back( byte_val );
    }
  }

  void buffer( std::string buf );
  void buffer( Ref<std::string> buf );
  void buffer( const std::vector<Ref<std::string>>& bufs );
  std::vector<Ref<std::string>> finish();
};

output_是最终完整的系列化字节,buffer_是临时缓冲区

void Serializer::flush()
{
  if ( not buffer_.empty() ) {
    output_.emplace_back( move( buffer_ ) );
    buffer_.clear();
  }
}
void Serializer::buffer( string buf )
{
  if ( not buf.empty() ) {
    flush();
    output_.emplace_back( move( buf ) );
  }
}
void Serializer::buffer( Ref<string> buf )
{
  if ( not buf.get().empty() ) {
    flush();
    output_.emplace_back( move( buf ) );
  }
}
void Serializer::buffer( const vector<Ref<string>>& bufs )
{
  for ( const auto& b : bufs ) {
    buffer( b.borrow() );
  }
}
vector<Ref<string>> Serializer::finish()
{
  flush();
  return move( output_ );
}

观察这些实现发现,序列化也就是将这些数据统统写入缓冲区output_

那么它和其他协议配合就能实现序列化

struct IPv4Datagram {
  IPv4Header header {};
  std::vector<Ref<std::string>> payload {};
  .......
  void serialize( Serializer& serializer ) const
  {
    header.serialize( serializer );
    serializer.buffer( payload );
  }
};

在IPv4Datagram中有这个函数,分别序列化IPv4Header并写入payload

void IPv4Header::serialize( Serializer& serializer ) const
{
  // consistency checks
  if ( ver != 4 ) {
    throw runtime_error( "wrong IP version" );
  }

  const uint8_t first_byte = ( static_cast<uint32_t>( ver ) << 4 ) | ( hlen & 0xfU );
  serializer.integer( first_byte ); // version and header length
  serializer.integer( tos );
  serializer.integer( len );
  serializer.integer( id );

  const uint16_t fo_val = ( df ? 0x4000U : 0 ) | ( mf ? 0x2000U : 0 ) | ( offset & 0x1fffU );
  serializer.integer( fo_val );

  serializer.integer( ttl );
  serializer.integer( proto );

  serializer.integer( cksum );

  serializer.integer( src );
  serializer.integer( dst );
}

在header中的序列化函数实现是这样的,利用integer()模版函数进行端序转换

最后写入output_,用finish()函数输出序列化结果

优点

零拷贝,高性能

避免内存频繁分配,消耗性能

高兼容性,采用模版函数,能够序列化各种类型无符号整数,而且跨平台

天然支持分层解析,高灵活性

  • 为什么不使用struct来存储header?

字节端序冲突

结构体存在内存对齐,会插入空白字节,打乱header

如果出现接收到的数据包长度不足,使用指针访问容易出现越界或溢出