571
社区成员
发帖
与我相关
我的任务
分享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的数据包分为Header和Data部分,其中Header是明文传输,包括Flags是标志位,Connection ID是连接ID,可用于连接迁移,QUIC Version是QUIC的版本号,Packet Number是包序号,用于保证可靠传输;Data部分是密文传输,是一些数据帧,有很多数据帧类型:Stream、ACK、Padding、Blocked等,其中Stream帧传输应用数据。
Stream

Frame Type: Bit7~Bit0
建立https连接
先分析一样https的握手过程,包括TCP握手和TLS(Transport Layer Security)握

https连接耗时3个RTT
QUIC基于TLS建立连接

右边展示的是ECDH算法,一个RTT就可以协商好通信秘钥。
QUIC是基于UDP的协议,而UDP是不可靠传输,QUIC如何实现可靠传输呢?
可靠传输必须满足两个条件:
可靠传输:数据完整性
实现方案:基于包号PKN和确认应答SACK的丢包重传机制

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

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

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

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

因为QUIC中每个连接上可以发送多个请求,每个请求对应一条流,每个流有自己的滑动窗口,整个连接也有一个滑动窗口,其大小是所有流的可用窗口之和。
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() {
//......
// 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() {
session()->Initialize();
}
quic session初始化,可能被继承覆盖,如果需要做更多工作的话。
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< 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 QuicSpdyClientBase::SendRequestAndWaitForResponse(
const SpdyHeaderBlock& headers,
QuicStringPiece body,
bool fin) {
SendRequest(headers, body, fin);
while (WaitForEvents()) {
}
}
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;
}
QuicClient继承QuicSpdyClientBase
2.3.1 关键成员
基本都是继承基类的成员变量.
2.3.1 关键成员函数
class QUIC_EXPORT_PRIVATE QuicSpdyClientSessionBase
: public QuicSpdySession,
public QuicCryptoClientStream::ProofHandler
class QUIC_EXPORT_PRIVATE QuicSpdySession
: public QuicSession,
public QpackEncoder::DecoderStreamErrorDelegate,
public QpackEncoderStreamSender::Delegate,
public QpackDecoder::EncoderStreamErrorDelegate,
public QpackDecoderStreamSender::Delegate
class QUIC_EXPORT_PRIVATE QuicSession : public QuicConnectionVisitorInterface,
public SessionNotifierInterface,
public QuicStreamFrameDataProducer
NP515