1. HTTP协议
1.1 HTTP协议概述
HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。基于TCP的应用层协议,它不关心数据传输的细节,HTTP(超文本传输协议)是一个基于请求与响应模式的、无状态的、应用层的协议,只有遵循统一的 HTTP 请求格式,服务器才能正确解析不同客户端发的请求,同样地,服务器遵循统一的响应格式,客户端才得以正确解析不同网站发过来的响应。
1.2 HTTP协议发展历史
1.2.1 HTTP/0.9
这是最早定稿的HTTP版本,这个版本中它的内容非常地简单。
- 只有一个命令GET。对应到现在的GET请求和POST请求,这些叫做HTTP的命令或者方法。
- 没有HEADER等描述数据的信息。因为这个时候的请求非常简单,它需要达到的目的也非常简单,没有那么多数据格式。
- 服务器发送完内容之后,就关闭TCP连接。
1.2.2 HTTP/1.0
1.0的HTTP版本,是一种无状态,无连接的应用层协议。 HTTP1.0规定浏览器和服务器保持短暂的链接。
浏览器每次请求都需要与服务器建立一个TCP连接,服务器处理完成以后立即断开TCP连接(无连接),服务器不跟踪也每个客户单,也不记录过去的请求(无状态)。
这种无状态性可以借助cookie/session机制来做身份认证和状态记录。
HTTP1.0存在的问题:
- 无法复用连接
每次发送请求,都需要进行一次TCP连接,而TCP的连接释放过程又是比较费事的。这种无连接的特性会使得网络的利用率变低。 - 队头阻塞
由于HTTP1.0规定下一个请求必须在前一个请求响应到达之前才能发送,假设前一个请求响应一直不到达,那么下一个请求就不发送,后面的请求就阻塞了。
1.2.3 HTTP/1.1
这个版本是在HTTP/1.0的基础上增加了一些功能来优化网络连接的过程。
- 在这个版本支持了持久连接。在HTTP/1.0版本里面,一个http请求要发送就要先在客户端和服务器端之间创建一个TCP连接,创建完这个TCP连接之后,等服务器端返回完数据之后,这个TCP连接就关闭了。
这个成本是相对比较高的,因为在建立一个TCP连接的过程中要进行http的三次握手,这一部分是通过TCP来完成的,在创建这个连接的过程中消耗是比较高的,延迟也会比较高。所以如果在建立完一个连接之后,它可以不关闭,之后新的http请求就可以一直在这个连接里面进行数据发送的话,它的性能和效率肯定会提升很多,HTTP/1.1已经实现了这个功能。 - 增加了pipeline。可以在同一个TCP连接里面发送多个http请求,就是上面说的那样。但是在HTTP/1.1里面,虽然是可以在同一个TCP连接里面发送多个http请求,但是服务器端对于进来的请求,是要按照顺序进行数据返回的。因此,如果前一个请求等待时间非常长,而后一个请求处理得比较快。这个时候后一个请求不能先发送,而是要等第一个请求数据全部发送完成之后,才能进行发送,即是串行的。等待的这部分时间就体现出了与并行传输性能之间的差距。而这个在HTTP/2里面得到了优化。
- 增加了HTTP的头host和其他一些命令。其中比较重要的就是host,有了host之后就可以在同一台服务器(物理服务器)上同时跑多个web服务。
1.2.4 HTTP/2.0
与 HTTP/1.1 相比,它在以下几方面作了改进:
- 新的二进制格式(Binary Format),HTTP1.x的解析是基于文本。基于文本协议的格式解析存在天然缺陷,文本的表现形式有多样性,要做到健壮性考虑的场景必然很多,二进制则不同,只认0和1的组合。基于这种考虑HTTP2.0的协议解析决定采用二进制格式,实现方便且健壮。
- 多路复用(MultiPlexing),即连接共享,即每一个request都是是用作连接共享机制的。一个request对应一个id,这样一个连接上可以有多个request,每个连接的request可以随机的混杂在一起,接收方可以根据request的 id将request再归属到各自不同的服务端请求里面。
- header压缩,HTTP1.x的header带有大量信息,而且每次都要重复发送,HTTP2.0使用encoder来减少需要传输的header大小,通讯双方各自cache一份header fields表,既避免了重复header的传输,又减小了需要传输的大小。
- 服务端推送(server push),同SPDY一样,HTTP2.0也具有server push功能。
1.2.5 HTTP/3.0
尽管HTTP/2.0解决了很多1.1的问题,但HTTP/2.0仍然存在一些缺陷,这些缺陷并不是来自于HTTP/2协议本身,而是来源于底层的TCP协议。HTTP/3.0选择了使用UDP协议,在UDP的基础上实现多路复用、0-RTT、TLS加密、流量控制、丢包重传等功能,弥补了HTTP/2.0仍然存在一些缺陷,具体介绍见下文QUIC。
2. QUIC概述
2.1 QUIC是什么
QUIC(Quick UDP Internet Connection快速UDP互联网连接)是谷歌制定的一种基于UDP的低时延的互联网传输层协议。它提供了像TCP一样的传输可靠性保证,可以实现数据传输的0-RTT延迟,灵活的设计使我们可以对它的拥塞控制及流量控制做更多的定制,它还提供了传输的安全性保障,以及像HTTP/2一样的应用数据二进制分帧传输。QUIC协议其实就是所谓的HTTP/3.0。
2.2 QUIC的优势
2.2.1 极短的建连时间
在QUIC的建连时间中大部分为0-ORTT,极少部分是1-RTT。TCP的一个建连包含三次握手,而如果基于HTTPS加密则还需包含TLS一层的一个握手,同时增加1 RTT的时间;综合来看,已完成建连的TCP连接进行握手大概需要2 RTT,而首次建连的TCP则需3 RTT,其中便包括TLS的证书交换。但对于QUIC协议,若客户端之前未建连,其第一次建连时需在客户端直接生成证书与和协议栈相关的配置且附带一个ID,这些数据会与请求数据一起直接发送并保存在服务端,假若服务端已存在以上数据那么系统会直接进行校验操作并直接回复数据;而已建连的客户端则仅需0-RTTs时间,初次建连的QUIC服务端为便于进行建连与校验会把ID保存在服务端本地,待服务端完成校验即可发送证书至客户端并进行客户端校验,校验成功即可直接启动数字交换;且对于第一次建连的QUIC而言只需1 RTT就可完成建连并允许后续的数据交互,以上就是QUIC的建连时间特性——大部分0-RTT、极少部分1-RTT。
2.2.2 多路复用
基于TCP的HTTP/2.0深受TCP的队首阻塞问题困扰。由于HTTP/2.0在TCP的单个字节流抽象之上多路复用许多流,一个TCP片段的丢失将导致所有后续片段的阻塞直到重传到达,而封装在后续片段中的HTTP/2.0流可能和丢失的片段毫无关系。
由于QUIC是为多路复用操作从头设计的,携带个别流的的数据的包丢失时,通常只影响该流。每个流的帧可以在到达时立即发送给该流,因此,没有丢失数据的流可以继续重新汇集,并在应用程序中继续进行。
2.2.3 灵活的拥塞控制
QUIC 具有可插入的拥塞控制,且有着比TCP更丰富的信令,这使得QUIC相对于TCP可以为拥塞控制算法提供更丰富的信息。每个包,包括原始的和重传的,都携带一个新的包序列号。这使得QUIC发送者可以将重传包的ACKs与原始传输包的ACKs区分开来,这样可以避免TCP的重传模糊问题。
QUIC ACKs也显式地携带数据包的接收与其确认被发送之间的延迟,与单调递增的包序列号一起,这样可以精确地计算往返时间(RTT)。
最后,QUIC的ACK帧最多支持 256个ack块,因此在重排序时,QUIC相对于 TCP(使用SACK)更有弹性,这也使得在重排序或丢失出现时,QUIC可以在线上保留更多在途字节。客户端和服务器都可以更精确地了解到哪些包对端已经接收。
2.2.4 连接转移
对于TCP协议来说,标识一个TCP连接需要4个参数,既来源IP、来源端口、目的IP和目的端口。其中的任一参数改变,TCP连接就需要重新创建。这对于传统网络来说影响不大,因为来源和目的IP相对固定。但是在无线网络中,情况就大不相同了。设备在移动过程中,可能会因为网络切换(如从WIFI网络切换到4G网络环境),导致TCP连接需要重新创建。
QUIC协议使用了UDP协议,不再需要这四元组参数。同时QUIC协议实现了自己的会话标记方式,称为连接UUID。当设备网络环境切换时,连接UUID不会发生变化,因此无需重新进行握手。
2.2.5 前向纠错
数据包丢失不仅导致重传耗时,还会使拥塞窗口变小,从而降低吞吐量,影响了数据传输速度。谷歌在QUIC早期采用了前向纠错码(Forward-Error Correction)来减少包丢失现象。在一个Group中带上一个额外的fec包(N+1个包),分组中任意一个包丢失,都可以用其余N个包来恢复。这种做法同时也带来了冗余,可能没10个包中就要额外传输1个冗余包,在网络状况好的时候是一种浪费,目前谷歌已经废弃了QUIC的FEC功能。
同时,对于一些非常重要的包,QUIC在发送后的短时间内如果没收到回包,便会重发请求,以确保重要的节点不被Delay。
2.3 QUIC连接的生命周期
2.3.1 连接建立
QUIC首次连接需要1RTT,具体过程如下:
- 客户端发送inchoate Client Hello消息(CHLO)请求建立连接,服务器生成一组质数p以及其原根g。然后根据pg和随机生成数a算出A,将Apg放在serverConfig里面,发到Rejection消息(REJ)到客户端;
- 客户端随机生成一个数b,并根据serverConfig里面的pA和b就可以算出初始密钥K,并将B和用初始密钥加密的Data数据发到(full client hello消息)服务器。
- 服务器收到客户端的数据,用客户端发来的公开数B+server config生成同样的秘钥,来解密客户端发来的数据。然后发送Server hello消息(SHLO),这时会带上自己生成的服务器公开数(服务器这是在更新自己的私钥,实际上这就是为了保证前向安全性);
- 客户端收到这个服务器公开数后,二者便更新了秘钥,用SHA-256等算法推导出会话密钥。以后就用这个会话秘钥通讯了。
后续的连接,如果客户端本地的serverConfig没过期(包含了Apg和其他前次协商信息),直接可以计算出初始秘钥K并加密传输数据,实现0RTT握手。
2.3.2 数据传输
建立完连接后,就可以传输数据。QUIC连接中传输的所有数据,包括加密握手,被作为流(Stream)内的数据发送,流可以由客户端创建,也可以由服务器创建,可以与其它流并行交错地发送数据,且可以取消。如果端点收到一个STREAM帧,但它不想接受流,它可以立即以一个RST_STREAM帧(稍后描述)响应。注意,初始化流的端点可能也已经在那个流上发送了数据,这些数据必须被忽略。
一旦流创建好,它可被用于发送和接收数据。这意味着一系列的流帧可被QUIC端点在那个流上发送,直到流在那个方向上被终止。QUIC连接的任何一端都可以正常地终止一个流。有以下三种方式可以终止流:
- 正常终止:由于流是双向的,流可以是 “half-closed(半关闭)”或”closed(关闭)”状态。当流的一边发送一个FIN位被设为ture的帧,流就会被认为在那个方向上是”half-closed(半关闭)”的。当QUIC的两个端点都发送并接收到了FIN,则端点认为流是”closed(关闭)”状态的。尽管FIN应该随着流的最后的用户数据一起发送,但FIN位可以被流的最后的数据帧后面的空流帧发送。
- 异常终止:客户端或服务器可以在任何时候为一个流发送RST_STREAM帧。RST_STREAM帧包含一个错误码用以指示失败原因。当流的发起者发送了一个RST_STREAM帧,它表示完成流失败了,而且不会有更多的数据在那个流上发送了。当RST_STREAM帧是由流的接收者发送的时,发送者一旦接收就应该停止在那个流上发送任何数据。
- 当连接终止时流也会被终止。
2.3.3 连接终止
QUIC连接,一旦建立,可由以下两种方式中的一种终止
- 显式关闭:一个端点发送一个CONNECTION_CLOSE帧给对端来初始化一个连接终止,在这之前可以发送一个GOAWAY帧给对端来表明连接将在不久后终止。当发送GOAWAY帧时,通知对端所有活跃的流将继续被处理,但GOAWAY的发送者将不再初始化任何额外的流,且不接受任何新进入的流。在所有活跃的流的终止后,可以发送CONNECTION_CLOSE。如果一个端点未终止活跃的流但发送了一个CONNECTION_CLOSE帧(一个或多个流还没有FIN位或RST_STREAM帧被发送或接收),则对端必须假设流是不完整的且被异常地终止。
- 隐式关闭:QUIC连接默认的空闲超时时间是30秒,且是连接协商中的一个必须参数(“ICSL”)。最大值是10分钟。如果在空闲超时期间没有网络活动,连接将关闭。默认情况下将发送一个CONNECTION_CLSOE帧。
一个端点还可以在连接期间的任何时间发送一个PUBLIC_RESET包来突然地终止活跃的连接。QUIC中的PUBLIC_RESET等价于TCP的RST。
3. QUIC协议实践
3.1 下载proto-quic代码
去https://github.com/google/proto-quic
下载时,发现代码已经被清空了,但是在https://github.com/Louis14lan/proto-quic
找到了备份。
3.2 构建
添加环境变量和安装依赖cd proto-quic
export PROTO_QUIC_ROOT=‘pwd‘/src
export PATH=$PATH:‘pwd‘/depot_tools
./proto_quic_tools/sync.sh
3.3 编译
cd src
gn gen out/Default && ninja -C out/Default quic_client quic_server net_unittests
完毕后得到quic_server
和quic_client
3.4 准备测试数据
mkdir /tmp/quic-data
cd /tmp/quic-data
wget -p --save-headers https://www.example.org
下载一份www.example.org
的拷贝,它主要是给quic_server
二进制可执行文件用来提供本地服务的
修改测试数据cd www.example.org/
vim index.html
获取到的index.html在header里加上X-Original-Url: https://www.example.org/
3.5 生成和导入证书
我们需要生成一个加密证书,这是因为这个DEMO包含了SSL加密的逻辑。cd net/tools/quic/certs
./generate-certs.sh
将证书部署到系统certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n quic -i / /net/tools/quic/certs/out/2048-sha256-root.pem
导入失败,换一种方法,将证书添加到浏览器中。在地址栏中输入chrome://settings/certificates
之后点击导入,将~/yangpei/proto-quic/src/net/tools/quic/certs/out/2048-sha256-root.pem
导入,
然后勾选第一项确定即可。
3.6 运行QUIC服务器和客户端
运行服务端程序
./out/Default/quic_server --quic_response_cache_dir=/tmp/quic-data/www.example.org --certificate_file=net/tools/quic/certs/out/leaf_cert.pem --key_file=net/tools/quic/certs/out/leaf_cert.pkcs8 --host=127.0.0.1 --port=6121
新开终端运行客户端程序
./out/Default/quic_client --host=127.0.0.1 --port=6121 https://www.example.org/ --allow_unknown_root_cert
测试成功,获取到了之前下载的www.example.org的index.html
4. QUIC实现代码分析
4.1 文件介绍
- quic_connection类文件
主要编写QuicConnection类,该类是quic服务端和客户端的处理框架,它提供SendStreamData方法用来发送流数据,被QuicSession调用。 它使用QuicPacketGenerator来创建Quic帧。 而QuicPacketGenerator会被QuicConnection的OnSerializedPacket方法调用。 最后帧则会被QuicPacketWriter写入连接中。 - quic_session类文件
主要编写QuicSession类,QuicSession类是一个基础类,当一个具体会话类被创建时将被继承。 它主要将传入的数据发送到正确的Quic流。 拥有QuicConnection类,用于在线上发送数据。 因此它代表一个quic连接,由多个流组成,并抽象出真实的网络连接。 quic流会被WritevData方法用来发送数据。 反过来QuicConnection类会调用QuicConnectionVisitorInterface方法来通知会话新的数据包和对连接的更改。 - quic_stream类文件
主要编写QuicStream类,它定义了quic流类需要满足的接口。 它还实现了流的基本逻辑,如流控制,帧排序,处理流连接重置或关闭和缓冲数据写入,用户根据stream类来进行数据的抛出和写入。stream流有QuicDataStream类,其实现了传输SPDY请求的quic流。 它要求在会话管理的专用报头流中发送不同的报头。 报头通过OnStreamHeaders, OnStreamHeadersPriority和 OnStreamHeadersComplete发送给它。 初始化时会阻塞QuicStreamSequencer直到所有报头被接收。QuicStreamSequencer类用来缓冲帧数据直到它们可以传递到下一层为止。 其中包括检查重复帧,排序帧,以便数据有序,并检查错误情况。QuicHeadersStream类用来处理SPDY报头 - quic_packet类文件
主要编写QuicPacket类,如QuicPacketCreator类是QuicConnection用来创建和发送包的类。 它使用QuicPacketCreator来构建帧和包。 当一个包被创建完毕,它将调用OnSerializedPacket给它的调用者。QuicReceivedPacket类用来处理接收包。QuicPacketWriter类接口定义了通过QuicConnection发送数据包的方法。
它还定义了一些方法来判断套接字是否被阻塞。QuicPacketGenerator::DelegateInterface类定义了当新数据包可用时QuicPacketGenerator调用的接口。 它通过QuicConnection实现。 - quic_framer类文件
主要编写QuicFramer类,用来解析和构建QUIC包。 通过ProcessPacket方法来接收数据,并调用QuicFrameVisitorInterface接口来通知QuicConnection接收到新包。QuicFrameVisitorInterface类定义了QuicFrame处理新QUIC数据包的方法,它通过QuicConnection实现。
跟据以下几种类型来判断为什么类型的包,来进行相应的处理:
enum QuicFrameType {
PADDING_FRAME = 0,
RST_STREAM_FRAME = 1,
CONNECTION_CLOSE_FRAME = 2,
GOAWAY_FRAME = 3,
WINDOW_UPDATE_FRAME = 4,
BLOCKED_FRAME = 5,
STOP_WAITING_FRAME = 6,
PING_FRAME = 7,
STREAM_FRAME,
ACK_FRAME,
MTU_DISCOVERY_FRAME,
NUM_FRAME_TYPES
};
PADDING_FRAME:为填充字节帧,接收到这个包时会将包剩余部分填充字节。
RST_STREAM_FRAME:当由流的创建者发送时,表示创建者希望关闭流,当由接收者发送时,表示发生错误或者不想接收流,因此流应该被关闭。
CONNECTION_CLOSE_FRAME:连接关闭。
GOAWAY_FRAME:表示流应该被停止使用,因为之后将会被关闭,在使用的流将被继续处理,但是发送者不会在接收流。
WINDOW_UPDATE_FRAME:用于通知对端流量控制端口接收窗口大小的更新。
BLOCKED_FRAME:表示已经准备好发送数据且有数据要发送,但是被阻塞了。
STOP_WAITING_FRAME:通知对端不需要等待包号小于特定值的包。
PING_FRAME:用来验证对端是否保持活跃,且连接是否正常。
STREAM_FRAME:用于发送数据。
ACK_FRAME:通知对端哪些包被接收到了。
4.2 QUIC服务端客户端发送数据流程解析
quic实现的C/S端代码位于proto-quic\src\net\tools\quic
根据quic C/S端代码,其服务端和客户端代码的main()文件为server_bin.cc和client_bin.cc等文件。
client.main()函数基本实现步骤:
创建QuicClient类client,调用client.Initialize()进行初始化。
Initialize()实现:
{
定义流窗口大小
定义session窗口大小
设置epoll_server超时时间
调用CreateUDPSocket()创建UDP套接字
注册epoll时间回调函数
}
之后调用client.Connect()进行对话的连接
Connect()实现
{
写数据类PacketWrite类创建
创建session类
初始化session,InitializeSession()
WaitForEvent
}
调用client.CreateClientStream()与session中创建一个stream类流,用于发送数据。
调用stream->WriteStringPiece()来进行数据的发送。
调用client.WaitForEvents()等待事件。
调用stream->CloseConnection(net::QUIC_NO_ERROR);来关闭连接。
调用client.Disconnect()关闭client。
服务端与client端基本类似。
发送和接收:
发送:
最外层的发送数据接口为调用stream流的WriteOrBufferData(body, fin, nullptr)方法其中body是要发的数据,fin是标识是否是改流的最后一个数据。之后会在流中进行相应的判断和处理,如流上是否有足够的空间来发送这个数据,发送窗口大小是否合适,是否阻塞等。如果判断可以进行发送之后便会调用session类的方法WritevData()。
在session类会调用connection类的SendStreamData方法发送数据,并根据实际发送的数据更新相应stream流的数据消费的数值。
在connection类会调用PacketGenerator类的ConsumeData方法来发送数据。其中会根据包来进行ack的绑定。
之后会返回connection类,根据消息队列情况调用WritePacket()进行socket上包的写入,该方法实现于PacketWriter类。
接收:
当Server端创建好之后循环调用StartReading(),进行接收包,根据synchronous_read_count_ 来判断是否是CHLO包。
void QuicSimpleServer::StartReading() {
if (synchronous_read_count_ == 0) {
// Only process buffered packets once per message loop.
dispatcher_->ProcessBufferedChlos(kNumSessionsToCreatePerSocketEvent);
}
...
int result = socket_->RecvFrom(
read_buffer_.get(), read_buffer_->size(), &client_address_,
base::Bind(&QuicSimpleServer::OnReadComplete, base::Unretained(this)));
...
OnReadComplete(result);
}
OnReadComplete()中会调用dispatcher的处理包方法
void QuicSimpleServer::OnReadComplete(int result) {
...
dispatcher_->ProcessPacket(
QuicSocketAddress(QuicSocketAddressImpl(server_address_)),
QuicSocketAddress(QuicSocketAddressImpl(client_address_)), packet);
StartReading();
}
void QuicDispatcher::ProcessPacket(const QuicSocketAddress& server_address,
const QuicSocketAddress& client_address,
const QuicReceivedPacket& packet) {
...
framer_.ProcessPacket(packet);
...
}
跳转到Framer类的处理方法
bool QuicFramer::ProcessPacket(const QuicEncryptedPacket& packet) {
...
if (!visitor_->OnUnauthenticatedPublicHeader(public_header)) {
// The visitor suppresses further processing of the packet.
return true;
}
...
}
visitor_指向dispatch类,跳转到
QuicDispatcher::OnUnauthenticatedPublicHeader(){
...
QuicConnectionId connection_id = header.connection_id;
SessionMap::iterator it = session_map_.find(connection_id);
if (it != session_map_.end()) {
DCHECK(!buffered_packets_.HasBufferedPackets(connection_id));
it->second->ProcessUdpPacket(current_server_address_,
current_client_address_, *current_packet_);
return false;
}
...
}
当包头的connection_id 能在session_map里找到时,直接调用connection的ProcessUdpPacket处理,server端的session_map维护在dispatch类里,创建session类都会记录下来。
之后经过处理跳转到Framer类的ProcessFrameData()方法里,其中对stream Framer和ACK Framer分别进行了处理,
如果是stream包,则对其进行解析后会调用OnStreamFrame()抛到上层。
if (!ProcessStreamFrame(reader, frame_type, &frame)) {
return RaiseError(QUIC_INVALID_STREAM_DATA);
}
if (!visitor_->OnStreamFrame(frame)) {
QUIC_DVLOG(1) << ENDPOINT
<< "Visitor asked to stop further processing.";
// Returning true since there was no parsing error.
return true;
}
}
visitor_在Framer类里,由创建connection类时初始化,指向connection类,在到connection类里调用visitor_->OnStreamFrame(),visitor_指向session类,在由session类抛到stream类的OnDataAvailable()将数据进行处理,注意基础stream类里没有实现OnDataAvailable()的方法,需要编写,下面是官方tools文件里的处理。
OnDataAvailable() {
while (HasBytesToRead()) {
struct iovec iov;
if (GetReadableRegions(&iov, 1) == 0) {
// No more data to read.
break;
}
QUIC_DVLOG(1) << "Stream " << id() << " processed " << iov.iov_len
<< " bytes.";
body_.append(static_cast<char*>(iov.iov_base), iov.iov_len);
if (content_length_ >= 0 &&
body_.size() > static_cast<uint64_t>(content_length_)) {
QUIC_DVLOG(1) << "Body size (" << body_.size() << ") > content length ("
<< content_length_ << ").";
SendErrorResponse();
return;
}
MarkConsumed(iov.iov_len);
}
if (!sequencer()->IsClosed()) {
sequencer()->SetUnblocked();
return;
}
// If the sequencer is closed, then all the body, including the fin, has been
// consumed.
OnFinRead();
if (write_side_closed() || fin_buffered()) {
return;
}
}
如果是ACK包,则会对ack进行处理,并进行拥塞算法的运算。
if (!ProcessAckFrame(reader, frame_type, &frame)) {
return RaiseError(QUIC_INVALID_ACK_DATA);
}
if (!visitor_->OnAckFrame(frame)) {
QUIC_DVLOG(1) << ENDPOINT
<< "Visitor asked to stop further processing.";
// Returning true since there was no parsing error.
return true;
}
OnAckFrame()跳转到connection类的OnAckFrame,其中调用QuicSentPacketManager类OnIncomingAck()方法,其中进行了rtt和带宽的更新,并对丢包进行判断。
5. 总结
虽然目前QUIC协议已经运行在一些较大的网站上,但离大范围普及还有较长的一段距离,期待QUIC协议规范能够成为终稿,并在除了谷歌浏览器之外的其他浏览器和应用服务器中也能够实现。
参考资料:
[1] https://tools.ietf.org/html/draft-ietf-quic-transport-31
[2] http://www.chromium.org/quic
[3] https://www.jianshu.com/p/65daa1578808
[4] https://www.jianshu.com/p/f0aa0ae21809
[5] https://www.cnblogs.com/xidongyu/p/6838236.html
[6] https://blog.csdn.net/dxpqxb/article/details/76819992
[7] https://yu.mantoufan.com/202003051019409975