QUIC协议原理及实现分析

weixin_41911222 2022-01-18 16:28:37

QUIC的基本概念

QUIC全称 Quick UDP Internet Connection(快速UDP互联网连接),是由Google提出的使用UDP协议进行多路并发传输的协议。

QUIC相比现在广泛应用的HTTP/2.x + TCP + TLS协议主要有这些优势:减少了TCP三次握手及TLS握手时间、改进的拥塞控制、避免HOL(队头阻塞)的多路复用、连接迁移、前向冗余纠错。

为了实现传输的可靠性,它基本上实现并且改进了整个 TCP 协议的功能,包括序列号,重传,拥塞控制,流量控制等。
为了实现传输的安全性,它又彻底重构了 TLS 协议,包括证书压缩,握手消息,0RTT 等。虽然后续可能会采用 TLS1.3 协议,但是事实上是 QUIC 推动了 TLS1.3 的发展。
为了实现传输的并发性,它又实现了 HTTP2 的大部分特性,包括多路复用,流量控制等。

QUIC的数据包格式

QUIC的数据包分为Header和Data部分,其中Header是明文传输,包括Flags是标志位,Connection ID是连接ID,可用于连接迁移,QUIC Version是QUIC的版本号,Packet Number是包序号,用于保证可靠传输;Data部分是密文传输,是一些数据帧,有很多数据帧类型:Stream、ACK、Padding、Blocked等,其Stream帧传输应用数据

Stream

Frame Type: Bit7~Bit0

  1. Bit7:必须设置为1,表示Stream帧
  2. Bit6:如果设置为1,表示发送端在这个stream上已经结束发送数据,流将处于半关闭状态
  3. Bit5:如果设置为1,表示Stream头中包含Data Length字段
  4. Bit432:表示offset的长度。000表示0字节,001表示1字节,002表示2字节,以此类推
  5. Bit10:表示Stream ID的长度。00表示1字节,01表示2字节,10表示3字节,11表示4字节

QUIC的实现原理

1. 建立连接

建立https连接

先分析一样https的握手过程,包括TCP握手和TLS(Transport Layer Security)握

 

https连接耗时3个RTT

QUIC基于TLS建立连接

右边展示的是ECDH算法,一个RTT就可以协商好通信秘钥。

2. 可靠传输

QUIC是基于UDP的协议,而UDP是不可靠传输,QUIC如何实现可靠传输呢?
可靠传输必须满足两个条件

  • 数据完整性:发送端发出的数据包,接收端都能收到
  • 数据有序性:接收端能按序组装数据包,解码得到原始数据

可靠传输:数据完整性

实现方案:基于包号PKN和确认应答SACK的丢包重传机制

PKN是单调递增的,即使是重传,PKN也和之前的不一样。那么接收端怎么保证数据的有序性呢?
通过添加数据包在原始数据中的偏移量offset,接收端根据offset字段对异步到达的数据包进行排序

3. 流量控制

就是说发送端发出的包,接收端要有足够的缓冲空间来接收。
和TCP一样,QUIC也是利用滑动窗口机制实现流量控制,也就是连续ARQ协议

 

如果发送端收到了接收端的ACK帧,窗口就会向右移动,可用窗口就会变大,然后发送新的数据包。

 

虽然都是采用滑动窗口机制,和TCP不同的是,QUIC的滑动窗口分为Connect和Stream两种级别。

  • Connnect流量控制:规定了所有数据流的总窗口大小
  • Stream流量控制:规定了每个流的窗口大小

 

因为QUIC中每个连接上可以发送多个请求,每个请求对应一条流,每个流有自己的滑动窗口,整个连接也有一个滑动窗口,其大小是所有流的可用窗口之和。

QUIC源码分析

2.1 QuicClientBase类的关键信息

2.1.1 关键成员


  // Address of the server.
  QuicSocketAddress server_address_; //quic的socket address

  // If initialized, the address to bind to.
  QuicIpAddress bind_to_address_; //quic绑定的quic address

  // Local port to bind to. Initialize to 0.
  int local_port_; //quic的udp port

  // config_ and crypto_config_ contain configuration and cached state about
  // servers.
  QuicConfig config_; //quic连接配置
  QuicCryptoClientConfig crypto_config_; //quic加密配置

  // Writer used to actually send packets to the wire. Must outlive |session_|.
  std::unique_ptr< QuicPacketWriter> writer_; //用于发送报文的接口

  // Session which manages streams.
  std::unique_ptr< QuicSession> session_; //QuicSession是管理quic stream的会话类

  // The network helper used to create sockets and manage the event loop.
  // Not owned by this class.
  std::unique_ptr< NetworkHelper> network_helper_; //网络事件监听的event loop机制,也就是epoll的收发事件

2.1.2 关键成员函数

  • bool QuicClientBase::Initialize()
    //初始化,接收窗口session 15MB, stream 6MB; 并且初始化network_helper_,网络事件机制;在StartConnect or Connect之前调用
    // Initializes the client to create a connection. Should be called exactly
    // once before calling StartConnect or Connect. Returns true if the
    // initialization succeeds, false otherwise.

bool QuicClientBase::Initialize() {
  //......
  // If an initial flow control window has not explicitly been set, then use the
  // same values that Chrome uses.
  const uint32_t kSessionMaxRecvWindowSize = 15 * 1024 * 1024;  // 15 MB
  const uint32_t kStreamMaxRecvWindowSize = 6 * 1024 * 1024;    //  6 MB

  //.....

  if (!network_helper_->CreateUDPSocketAndBind(server_address_,
                                               bind_to_address_, local_port_)) {
    return false;
  }

  initialized_ = true;
  return true;
}

  • bool Connect();
    //连接quic server,包括 同步加密密钥,和handshake
    会调用StartConnect()

  • void QuicClientBase::StartConnect();
    //连接quic server,包括 同步加密密钥,和handshake;
    创建QuicPacketWriter;
    创建QuicSession;
    创建QuicConnection(其最为QuecSession的成员对象);


void QuicClientBase::StartConnect() {
  .......
  QuicPacketWriter* writer = network_helper_->CreateQuicPacketWriter();//创建writer接口
  ......
  session_ = CreateQuicClientSession(
      supported_versions(),
      new QuicConnection(GetNextConnectionId(), server_address(), helper(),
                         alarm_factory(), writer,
                         /* owns_writer= */ false, Perspective::IS_CLIENT,
                         can_reconnect_with_different_version
                             ? ParsedQuicVersionVector{mutual_version}
                             : supported_versions()));
  // Reset |writer()| after |session()| so that the old writer outlives the old
  // session.
  set_writer(writer);
  InitializeSession();
  set_connected_or_attempting_connect(true);
}

 

  • void QuicClientBase::InitializeSession()
    // Calls session()->Initialize(). Subclasses may override this if any extra
    // initialization needs to be done. Subclasses should expect that session()
    // is non-null and valid.

void QuicClientBase::InitializeSession() {
  session()->Initialize();
}


quic session初始化,可能被继承覆盖,如果需要做更多工作的话。

2.2 QuicSpdyClientBase的关键信息

QuicSpdyClientBase继承QuicClientBase,和QuicClientPushPromiseIndex::Delegate, QuicSpdyStream::Visitor

2.2.1 关键成员


  // Keeps track of any data that must be resent upon a subsequent successful
  // connection, in case the client receives a stateless reject.
  std::vector< std::unique_ptr< QuicDataToResend>> data_to_resend_on_connect_;

  std::unique_ptr< ClientQuicDataToResend> push_promise_data_to_resend_;

 

2.2.2 关键成员函数

  • std::unique_ptr QuicSpdyClientBase::CreateQuicClientSession;
    CreateQuicClientSession是基类QuicClientBase中的纯虚函数.
    创建QuicSpdyClientSession类对象,需要输入connection对象等。

std::unique_ptr< QuicSession> QuicSpdyClientBase::CreateQuicClientSession(
    const quic::ParsedQuicVersionVector& supported_versions,
    QuicConnection* connection) {
  return QuicMakeUnique< QuicSpdyClientSession>(
      *config(), supported_versions, connection, server_id(), crypto_config(),
      &push_promise_index_);
}


* void SendRequest(const spdy::SpdyHeaderBlock& headers, QuicStringPiece body, bool fin);
发送http request的报文,headers是http的头部额外信息,body是http的body信息;
创建Quic client stream类对象,通过其发送.


void QuicSpdyClientBase::SendRequest(const SpdyHeaderBlock& headers,
                                     QuicStringPiece body,
                                     bool fin) {
  //设置重传的部分代码
  ......
  QuicSpdyClientStream* stream = CreateClientStream(); //创建QuicSpdyClientStream类对象
  if (stream == nullptr) {
    QUIC_BUG << "stream creation failed!";
    return;
  }
  stream->SendRequest(headers.Clone(), body, fin);//通过QuicSpdyClientStream的SendRequest发送信息
  //记录重传信息
  .....
}

 

  • void SendRequestAndWaitForResponse(const spdy::SpdyHeaderBlock& headers,QuicStringPiece body,bool fin);
    // Sends an HTTP request and waits for response before returning. 发送http request后,通过WaitForEvents做异步等待返回!
    WaitForEvents依靠基类QuicClientBase中的实现完成:network_helper_->RunEventLoop();

void QuicSpdyClientBase::SendRequestAndWaitForResponse(
    const SpdyHeaderBlock& headers,
    QuicStringPiece body,
    bool fin) {
  SendRequest(headers, body, fin);
  while (WaitForEvents()) {
  }
}

 

  • QuicSpdyClientStream* CreateClientStream();
    创建一个QuicSpdyClientStream类对象

QuicSpdyClientStream* QuicSpdyClientBase::CreateClientStream() {
  if (!connected()) {
    return nullptr;
  }

  auto* stream = static_cast< QuicSpdyClientStream*>(
      client_session()->CreateOutgoingBidirectionalStream());
  if (stream) {
    stream->SetPriority(QuicStream::kDefaultPriority);
    stream->set_visitor(this);
  }
  return stream;
}

2.3 QuicClient

QuicClient继承QuicSpdyClientBase

2.3.1 关键成员

基本都是继承基类的成员变量.

2.3.1 关键成员函数

  • std::unique_ptr CreateQuicClientSession(const ParsedQuicVersionVector& supported_versions, QuicConnection* connection) override;
    继承和覆盖基类的CreateQuicClientSession函数。
    通过输入,QuicConnection和ParsedQuicVersionVector来进行创建quic session。
    在基类QuicSpdyClientBase中的CreateQuicClientSession返回的对象是QuicSpdyClientSession。
    在基类QuicClientBase中CreateQuicClientSession是一个纯虚函数

3. Quic Session相关代码

  • QuicSpdyClientSession
    代码路径: net/third_party/quic/core/http
    class QuicSpdyClientSession : public QuicSpdyClientSessionBase
  • QuicSpdyClientSessionBase
    代码路径: net/third_party/quic/core/http

  class QUIC_EXPORT_PRIVATE QuicSpdyClientSessionBase
    : public QuicSpdySession,
      public QuicCryptoClientStream::ProofHandler

  • QuicSpdySession
    代码路径: net/third_party/quic/core/http

class QUIC_EXPORT_PRIVATE QuicSpdySession
    : public QuicSession,
      public QpackEncoder::DecoderStreamErrorDelegate,
      public QpackEncoderStreamSender::Delegate,
      public QpackDecoder::EncoderStreamErrorDelegate,
      public QpackDecoderStreamSender::Delegate

  • QuicSession
    代码路径: net/third_party/quic/core

class QUIC_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface,
                                        public SessionNotifierInterface,
                                        public QuicStreamFrameDataProducer

NP515

...全文
824 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复

571

社区成员

发帖
与我相关
我的任务
社区描述
软件工程教学新范式,强化专项技能训练+基于项目的学习PBL。Git仓库:https://gitee.com/mengning997/se
软件工程 高校
社区管理员
  • 码农孟宁
加入社区
  • 近7日
  • 近30日
  • 至今

试试用AI创作助手写篇文章吧