分类 计算机网络 下的文章

一、HTTP1.0和HTTP1.1

HTTP1.0和1.1的主要区别为:

  1. 长连接:HTTP1.0默认是短连接,HTTP1.1默认使用长连接。
  2. 断点续传:HTTP1.1支持断点续传,可以通过 Range头部指定需的资源数据部分。
  3. 添加Host头部:HTTP1.1中为了解决虚拟主机的使用场景,通过Host字段来指定访问某个特定web服务。
  4. 状态码:HTTP1.1添加了更多的状态码,如100等。

二、HTTP1.1和HTTP2.0

HTTP2.0相对于1.1来说跨了一个大版本,相应的改动也是非常大的。它的主要目标是提高HTTP协议的传输效率,不过主要都是基于数据传输上的改动,HTTP协议本身并没有修改太多。关于HTTP2.0的相关信息可以参考HTTP/2简介

HTTP2.0和HTTP1.1的主要区别为:

  1. HTTP1.1通过ascii码传输数据,而HTTP2.0通过二进制帧来传输。
  2. HTTP2.0默认使用长连接,并且每个来源只有一个连接。
  3. HTTP2.0压缩了HTTP头部,优化头部传输机制,大幅减少http传输空间。
  4. HTTP2.0原生支持服务端推送。

一、HTTP/2概述

HTTP/2是HTTP协议的第二个大版本,相较于HTTP/1而言,HTTP/2的核心观念是“构建一个更快、更简单以及更强大”的web应用。

HTTP/2 will make our applications faster, simpler, and more robust—a rare combination—by allowing us to undo many of the HTTP/1.1 workarounds previously done within our applications and address these concerns within the transport layer itself.

HTTP/2的前身是SPDY协议,这个协议是由google开发出来的一个实验性协议,在2009年中旬发布,主要目的是为了减少网页加载延迟。这个协议的目标是:

  1. 减少50%的页面加载时间。
  2. 无需网站作者修改任何内容。
  3. 最小化部署的复杂性,避免对网络架构的更改。
  4. 与开源社区合作开发这个新协议。
  5. 收集真实的性能数据,验证此协议是否真的有效。

在2012年,SPDY协议得到了chrome、firefox和opera的支持,越来越多的大型网站(如google、twitter和facebook)以及小型网站都在其基础设施内部署SPDY。在被越来越多的行业采用后,SPDY已经具备了称为一个标准的条件。也正因为这样,HTTP-WG在SPDY的基础上制定了官方的HTTP/2协议。在2015年初,IESG批准了这个基于SPDY协议的HTTP/2协议。

其实很多应用目前(2020)就已经使用了SPDY协议,例如我在一个部署了阿里云CDN的网站上就看到了SPDY相关的身影:

虽然是个报错信息,但可以看出目前SPDY(HTTP/2)确实已经被广泛使用。而在实验数据上,通过模拟网络连接下载了25个最流行的网站之后,页面加载速度最高加快了55%。

So far we have only tested SPDY in lab conditions. The initial results are very encouraging: when we download the top 25 websites over simulated home network connections, we see a significant improvement in performance - pages loaded up to 55% faster.

——来源:Chromium Blog

HTTP/2没有改变HTTP的基本语义和操作,各种操作方法(如GET/POST/HEAD等)、请求头和请求体机制都没有改变,不同的只是传输的方式改变了。HTTP/1.x 协议以换行符作为纯文本的分隔符,而 HTTP/2 将所有传输的信息分割为更小的消息和帧,并采用二进制格式对它们编码。

HTTP/2的主要功能特性:

  1. 将HTTP协议中的纯文本传输修改成了二进制帧传输,分为头部帧和数据帧。
  2. 使用HPCAK对头部数据压缩,添加头部索引减少重复头部数据传输。
  3. 添加数据优先级,针对不同的数据源建立不同的优先级树,确保优先级高的数据不会被阻塞。
  4. 请求响应的复用,同一个数据流中可以同时以不同的顺序发送多个不同的帧,每个数据源只产生一个连接。
  5. 流控制,阻止发送方向接收方发送的大量数据,通过请求窗口限制请求速率。
  6. 服务端推送,打破严格的请求-响应机制,服务端也可以主动推送数据到客户端。

二、数据帧的传递方法

2.1 HTTP/2数据传输的基本元素

新的HTTP/2协议通过二进制帧来传输数据,改变了客户端与服务器之间交换数据的方式,主要涉及了三个概念:

  • 帧:HTTP/2数据传输的基本单位,通过把原始的文本数据处理成二进制帧来发送。
  • 消息:逻辑请求和响应构成的统一整体就是消息。
  • 数据流:建立的TCP连接,承载发送一条和多条消息。

帧和消息

和HTTP/1一样,HTTP/2依旧还是保留了请求头、请求体以及响应体的部分。只不过在传输的时候被处理成了二进制的内容格式,同时把请求帧和响应帧区分开来了,两者可以单独发送。

例如以下是一个GET请求的帧格式(实际传输是二进制的):

因为GET请求不包含请求体,因此只会有一个标头帧发送出去。而服务端响应的帧格式为:

上面的请求帧和响应帧构成的帧集合即为消息。

一个TCP连接上包含的多个消息即为流,它体现形式为:

2.2 每个来源一个连接

在浏览器中,一个页面存在很多请求,虽然HTTP/1.1中由连接保持机制,但是大量的连接还会使得浏览器不得不建立多个连接来连接复用。大部分的浏览器还会限制单个源的连接数量,并且由于HTTP/1.1中的keep-alive是基于pipeline的,所以一旦有些请求阻塞后会导致其他请求也阻塞。

当使用二进制帧传输后,HTTP/2不再依赖创建多个TCP连接进行连接复用。每个数据流都拆分成很多帧,而这些帧可以交错,还可以分别设定优先级。 因此,所有 HTTP/2 连接都是永久的,而且仅需要每个来源一个连接,随之带来诸多性能优势。

大多数 HTTP 传输都是短暂且急促的,而 TCP 则针对长时间的批量数据传输进行了优化。 通过重用相同的连接,HTTP/2 既可以更有效地利用每个 TCP 连接,也可以显著降低整体协议开销。 不仅如此,使用更少的连接还可以减少占用的内存和处理空间,也可以缩短完整连接路径(即,客户端、可信中介和源服务器之间的路径) 这降低了整体运行成本并提高了网络利用率和容量。 因此,迁移到 HTTP/2 不仅可以减少网络延迟,还有助于提高通量和降低运行成本。

连接数量减少对提升 HTTPS 部署的性能来说是一项特别重要的功能:可以减少开销较大的 TLS 连接数、提升会话重用率,以及从整体上减少所需的客户端和服务器资源。

三、标头压缩

每个 HTTP 传输都承载一组标头,这些标头说明了传输的资源及其属性。 在 HTTP/1.x 中,此元数据始终以纯文本形式,通常会给每个传输增加 500–800 字节的开销。如果使用 HTTP Cookie,增加的开销有时会达到上千字节。 (请参阅测量和控制协议开销。) 为了减少此开销和提升性能,HTTP/2 使用 HPACK 压缩格式压缩请求和响应标头元数据,这种格式采用两种简单但是强大的技术:

  1. 这种格式支持通过静态霍夫曼代码对传输的标头字段进行编码,从而减小了各个传输的大小。
  2. 这种格式要求客户端和服务器同时维护和更新一个包含之前见过的标头字段的索引列表(换句话说,它可以建立一个共享的压缩上下文),此列表随后会用作参考,对之前传输的值进行有效编码。

利用霍夫曼编码,可以在传输时对各个值进行压缩,而利用之前传输值的索引列表,我们可以通过传输索引值的方式对重复值进行编码,索引值可用于有效查询和重构完整的标头键值对。

图片来源:Introduction to HTTP/2

作为一种进一步优化方式,HPACK 压缩上下文包含一个静态表和一个动态表:静态表在规范中定义,并提供了一个包含所有连接都可能使用的常用 HTTP 标头字段(例如,有效标头名称)的列表;动态表最初为空,将根据在特定连接内交换的值进行更新。 因此,为之前未见过的值采用静态 Huffman 编码,并替换每一侧静态表或动态表中已存在值的索引,可以减小每个请求的大小。

注:在 HTTP/2 中,请求和响应标头字段的定义保持不变,仅有一些微小的差异:所有标头字段名称均为小写,请求行现在拆分成各个 :method:scheme:authority:path 伪标头字段。

四、服务端推送

HTTP/2 新增的另一个强大的新功能是,服务器可以对一个客户端请求发送多个响应。 换句话说,除了对最初请求的响应外,服务器还可以向客户端推送额外资源,而无需客户端明确地请求。

注:HTTP/2 打破了严格的请求-响应语义,支持一对多和服务器发起的推送工作流,在浏览器内外开启了全新的互动可能性。 这是一项使能功能,对我们思考协议、协议用途和使用方式具有重要的长期影响。

为什么在浏览器中需要一种此类机制呢?一个典型的网络应用包含多种资源,客户端需要检查服务器提供的文档才能逐个找到它们。 那为什么不让服务器提前推送这些资源,从而减少额外的延迟时间呢? 服务器已经知道客户端下一步要请求什么资源,这时候服务器推送即可派上用场。

事实上,如果您在网页中内联过 CSS、JavaScript,或者通过数据 URI 内联过其他资产(请参阅资源内联),那么您就已经亲身体验过服务器推送了。 对于将资源手动内联到文档中的过程,我们实际上是在将资源推送给客户端,而不是等待客户端请求。 使用 HTTP/2,我们不仅可以实现相同结果,还会获得其他性能优势。

五、参考

High Performance Browser Networking

HTTP/2

Introduction to HTTP/2

一、长连接和短连接

长连接和短链接的概念:

  • 短连接:传输完数据后连接立刻关闭。
  • 长连接:传输完数据后不会立刻关闭连接,下次传输数据继续复用这个连接。

很容易看出,长连接和短连接的主要区别就是连接完成后是否会关闭连接,长连接不会在完成后立马关闭。

众所周知,HTTP是基于TCP的,TCP连接的建立和释放需要经过三次握手和四次挥手,这个过程相较于数据传输是极度费时的。而一个web页面往往包含许多个页面请求,因此,使用长连接的优势在于不会频繁创建和释放连接。连接的保持在高并发或者大量请求的情况下能大大减少连接建立的时间,这点对于客户端和服务端来说都能提升性能。

长连接除了可以减少连接建立的时间以外,还有一个重要的用处就是可以检测网络中端到端的故障。因为TCP本身是没有故障检测机制的,默认的keep-alive机制检测时间太长,如果线路的一端异常导致连接没有释放,会导致另一端一直占用连接资源,浪费资源。

二、HTTP的keep-alive

HTTP的长连接保持通过指定Connection头部完成,如果值为keep-alive表明希望建立长连接,而如果值为close则表示短连接。当然,长连接还需要服务端软件也支持,目前广泛使用的apache/nginx等HTTP服务程序都能支持长连接。

在HTTP1.0中,Keep-Alive默认是关闭的,需要手动指定才能开启。而HTTP1.1开始,Keep-Alive默认是开启的。

当开启keep-alive后,同一个TCP连接会发送多个HTTP请求到对方。但是由于HTTP协议的无状态特性,只有一个HTTP连接完成之后才能继续在这个连接上发送下一个HTTP请求,即必须是pipeline模式。

以下是一个keep-alive抓包的示例,在nginx上开启keep-alive,使用浏览器访问web页面,同时使用wireshark抓包。抓到的数据包如下:

以上都是由客户端端口50591发送到服务端80的数据,数据包已经被分为了5段,分别是:

  1. TCP三次握手
  2. HTTP请求+响应
  3. HTTP请求+响应
  4. TCP的keep-alive
  5. TCP四次挥手

其中第二段和第三段都是单独的HTTP请求,说明一个TCP连接中确实可以完成多个HTTP请求。HTTP数据交互过程wireshark已经帮我们解析出来了,很明显就能看出。

同时,通过追踪HTTP流也可以看到HTTP的交互过程:

红色框出来的是第二次HTTP请求,它是在第一个HTTP请求的响应发送完成之后才执行的。

一、RTT和RTO的概念

TCP作为一个面向连接的、可靠的传输协议,内部实现了一个重传计时器来保证数据能传输到对方。每发送一个数据包,就给这个数据设置一个重传计时器。如果在计时器超时之前收到了针对这个数据包的ack,就取消这个计时器。如果没有收到,则开始发起重传。计时器超时的时间被称为RTO,这个时间的确定取决于RTT。

关于两者详细的解释:

  • RTT(Round Trip Time):一个连接的往返时间,即数据发送时刻到接收到确认的时刻的差值;
  • RTO(Retransmission Time Out):重传超时时间,即从数据发送时刻算起,超过这个时间便执行重传。

关于RTT和RTO值的确定一直以来都是值得讨论的地方,如何让RTO能适应网络变化。

二、RTT的测量

每发送一个分组,TCP都会进行RTT采样,这个采样并不会每一个数据包都采样,同一时刻发送的数据包中,只会针对一个数据包采样,这个采样数据被记为sampleRTT,用它来代表所有的RTT。

采样的方法一般有两种:

  1. TCP Timestamp选项:在TCP选项中添加时间戳选项,发送数据包的时候记录下时间,收到数据包的时候计算当前时间和时间戳的差值就能得到RTT。这个方法简单并且准确,但是需要发送段和接收端都支持这个选项。
  2. 重传队列中数据包的TCP控制块:每个数据包第一次发送出去后都会放到重传队列中,数据包中的TCP控制块包含着一个变量,tcp_skb_cb->when,记录了该数据包的第一次发送时间。如果没有时间戳选项,那么RTT就等于当前时间和when的差值。

linux内核中,更新rtt的函数为tcp_ack_update_rtt

三、RTO的计算

3.1 经典方法

为了避免单次RTT波动,计算RTO时新引入了变量SRTT,表示更加平滑的RTT数值,它的计算方法:

SRTT = x(SRTT) + (1 - x)RTT;

x被称为平滑因子,一般建议设置在[0.8, 0.9],意思是SRTT值百分之八十来自于之前的值,百分之二十来自于当前值。然后计算RTO的方法为:

RTO = min(ubound, max(lbound, y(SRTT)));

y是时延离散因子,推荐值为[1.3, 2.0],ubound是RTO的上边界,lbound是RTO的下边界。

算法的缺点

在RTT波动较大时,RTO不能明显适应网络变化。

3.2 标准方法

标准方法引入了平均偏差的概念,它类似于统计学里面的方差,但是因为方差的计算过程代价较大,对于快速TCP来说不太适合。假设rtt的值为M,RTO的计算方式为:

srtt = (1 - g)srtt + g(M);
rttval = (1 - h)rttval + h(|M - rttval|);
RTO = srtt + 4(rttval);

其中g设置为1/8,h设置为1/4,对srtt而言,它有1/8取决于当前值,7/8取决于现有值。当RTT变化时,偏差增量越大,RTO的增量也越大。

关于计算RTT和RTO的算法,还有很多种,历史上针对这个的探讨从未停止过。

比较出名的拥塞算法还有谷歌的bbr算法,高版本的linux内核已经合入了bbr算法作为拥塞控制算法。

四、其他

4.1 TCP Timestamps选项

时间戳选项的作用是为了方便计算RTT,每发出一个数据包,就记录下发送时间,收到数据包时就能准确的获知到数据包往返时间了。

通过TCPDUMP抓包很容易就能看到Timestamps选项:

一、关于Basic Authentication

HTTP本身提供了一种基础的认证方式Basic Authentication,使得访问者在访问时需要输入账号密码认证之后才能访问到页面:

如果没有输入密码访问,服务器将会返回401

当服务端开启认证后,通过认证的方式有两种:

  1. 在访问URL的时候主动代码账号和密码信息,如http://user:password@www.baidu.com,其中user是账户名,password是密码。
  2. 在请求头部加上Authorization头部,并将值设置为Basic xxxx,xxxx是账号和密码的base64编码结果。

一般我们使用的是第二种方式:如果网站需要认证,浏览器会自动会弹出登录框,手动输入账号密码后浏览器在头部带上认证信息访问。以下是一个抓包示例:

最下面一行的Authorization是授权信息,最后的bWFxaWFuOnF3ZTEyMw==即是经过base64加密后的账号密码。使用base64命令解码即可得到认证信息:

> printf "bWFxaWFuOnF3ZTEyMw==" | base64 -d
maqian:qwe123

关于认证状态保持

浏览器如何做到保持状态的呢?根据抓包分析发现,认证成功后,浏览器会自动记住当前页面的账号信息。后续的每一个请求,浏览器都自动加上认证头部,无需每次都再输入账号密码,这样就达到了认证状态保持的效果。

二、使用nginx开启basic authentication

nginx默认提供了ngx_http_auth_basic_module模块以支持basic authentication,该指令为:

location / {
    auth_basic "auth test";
    auth_basic_user_file conf/htpasswd;
}

其中auth_basic认证信息的提示语句,他的值可以是一个字符串或者off,如果值是off表示访问无需认证,如果值是一个字符串,表示需要提供认证信息才能访问。并且大部分浏览器会把这个字符串返回到前端,测试发现chrome不会,edge会:

auth_basic_user_file的值是密钥文件的路径,里面保存了所有的账号密码,内容格式为:

# comment
name1:password1
name2:password2:comment
name3:password3

nginx支持以下密码类型:

  1. encrypted with the crypt() function; can be generated using the “htpasswd” utility from the Apache HTTP Server distribution or the “openssl passwd” command;
  2. hashed with the Apache variant of the MD5-based password algorithm (apr1); can be generated with the same tools;
  3. specified by the “{scheme} data” syntax (1.0.3+) as described in RFC 2307; currently implemented schemes include PLAIN (an example one, should not be used), SHA (1.3.13) (plain SHA-1 hashing, should not be used) and SSHA (salted SHA-1 hashing, used by some software packages, notably OpenLDAP and Dovecot).

通常,我们可以使用htpasswdopenssl命令来生成密码,如:

> openssl passwd -crypt qwe123 # qwe123是生成的密码信息
wMEiqshd7n3YQ

指令放置上下文

这个指令可以放置在:

http, server, location, limit_except

三、参考

Module ngx_http_auth_basic_module

一、关于滑动窗口协议

在TCP协议中,所有的SEQ包发送出去都必须要受到对方的ACK才认为是发送成功,如果长时间没有收到ACK回复确认,发送方需要重新发送该包。

而如果发送方每次都是发送一个包,然后等到接收方回复ACK了再发送下一个包,那么数据包的传输效率就相当低了。滑动窗口协议的作用就是为了解决这个问题。

在滑动窗口协议中,发送方可以同时发送多个数据包并可以不用等待接收方确认,这样就大大增加了网络的推图两。唯一的限制是:接收方未确认的数据包不得超过双方约定的窗口个数。

TCP包头中有一个两字节的字段表示滑动窗口的大小,因此最大的滑动窗口大小为65535,这个大小是由接收方提出的,以接收方的大小为准。

最大窗口65536并不是绝对的,TCP选项中有提供一个窗口放大的选项可以对这个数值缩放。

例如以下数据,将发送的字节从1至11进行标号,接收方提出的窗口大小为6,此时已发送的1/2/3字节数据被接收方确认,当前窗口覆盖了从第4字节到第9字节的区域。4/5/6字节是已经发送但是还未收到确认,此时发送方还可以发送的数据域只剩下三个,即7/8/9三个数据。当7/8/9发送完成后,发送方无法再继续发送数据。只有接收方再确认序号4的数据后,发送方才能继续发送数据10。此时可用的窗口将会向右延伸到10,同时左边已发送并确认的数据也会向右延伸到4。

这个过程看起来就像是一个窗口不停向右滑动,因此该协议被称为滑动窗口协议。

822287-20180227212818806-1798395637.png

滑动窗口涉及到以下几个概念:

  1. 窗口合拢:当窗口左边界向右靠近时,这种现象发生在数据被发送方确认时。
  2. 窗口张开:窗口的右边界向右移动的时候,这种现象发生在接收端处理的数据的时候。
  3. 窗口收缩:窗口右边界向左移动时,这种现象不常发生。
注意:窗口大小是由接收方通告的,通过采取慢启动和拥塞避免算法等机制来使带宽和性能取得最佳。

二、坚持定时器

2.1 坚持定时器的引入

思考以下场景:接收方的接收缓冲区已满,因此接收方发送了一个窗口大小为0的数据到发送方,此时发送方停止发送数据。经过一段时间后,接收方有空闲的缓冲区可以接收数据,此时给发送方发了一个窗口大小不为0的包,但是恰巧这个包在路上出现了意外没有到达发送方。此时发送方还在一直等待接收方发送不为0的窗口过来,但是接收方又在等待发送方继续发送数据。两者相当于发生了死锁。

2.2 工作原理

为了解决上面这个问题,TCP引入了坚持定时器,该定时器功能为:TCP连接的一方收到对方窗口为0的通知时,启动该定时器,若定时器持续时间到达后还没有收到对方窗口大小不为0的通知,主动发送一个零窗口探测包(仅携带1字节数据),对方则需要在这个包中回复当前窗口的大小。

三、关于window scale选项

window scale选项是提供给滑动窗口协议用于对窗口大小放大使用,在SYN包或者SYN,ACK包中作为选项字段,表示滑动窗口的实际大小要根据这个值来进行缩放(SYN包本身的窗口不会缩放)。

发送方和接收方的滑动窗口缩放因子大小可以不一样,两者互不干扰,但实际上窗口大小还是以接收方为准。使用wireshark抓包很容易就能看到这个缩放选项,以下是一个数据包示例。

这个包是客户端发起的三次握手数据包:

image.png

客户端设置了窗口缩放大小为256,因此后面实际计算的时候窗口大小要乘以这个缩放大小1025(使用wireshark很容日就能看到),乘以256后得到实际的窗口大小为262400

image0855a2b1457c0f0d.png

Calculated window size: wireshark根据缩放因子计算出来的实际窗口大小。

Window size scaling factor: 窗口缩放因子。

这两个参数都是wireshark自动生成的,并不是携带在TCP包中的字段。

下午客户打电话过来说QQ总是掉线,怀疑(一口咬定)是我们设备(我们的是路由设备,放在出口)故障导致的,态度十分强硬。从客户提出问题的第一秒起就已经可以断定是运营商网络问题了,为什么呢:

  1. 对路由器来说,路由逻辑都是在系统内核实现的,linux系统跑了这么多年,不可能还出现这种问题。
  2. 客户那里断网不是每次都是所有PC集体断,只有少部分PC时断时续,不稳定。如果路由器有问题肯定是集体短线,而运营商有问题就不一定了,不是所有的QQ都在同一个服务器上,线路不同,网络状态也不同。

但是没有真凭实据可以来证明这一点,并且现象不是一直存在的,所以最后只能用挫招了:在设备的lan和wan口抓包,每十分钟重抓一次,直到客户网络出现问题。

最后抓了一个小时,终于抓到了数据包,那么如何判断是路由器的问题还是运营商网络的问题呢:

  1. 对比LAN和WAN的数据包,如果LAN的数据没有经过WAN发出去,或者从LAN收到数据包到WAN发出数据包的时间太长,那说明路由设备有问题。
  2. 如果来自LAN的数据包在WAN口都发出去了,但是没有收到对端服务器的回复,或者回复时间太长,那么就是运营商网络有问题了。

要用到的一个重要的功能就是Wireshark的追踪流功能:

使用追踪流可以把当前连接的所有交互数据都显示出来:

这个是lan口的抓包数据,PC的IP是192.168.10.62,已经能看到有很多重传的数据包了,但是还不能确定是路由器丢包了还是服务端丢包了。此时就要对比两个口的数据包,看看lan口的数据包是否有被转发出去,lan和wan的数据包可以通过目的IP地址来一一对应起来,因为不管数据怎么转发,目的IP都是不变的。可以先在lan口追踪每一个TCP流,然后在wan口过滤目的地址。

而在随机抓取的前面几个数据包中,发现,每个连接都是wan端发起的连接,然后服务端没有响应,一直在产生重传操作,这就可以证明是用户网络的问题而不是我们设备的问题了。

TCP流1

imagec46b15a9d92d3a02.png

TCP流2

image659c17255647c185.png

TCP流3

image68afd74093fafb22.png

TCP流4

imagede37053196e95251.png

一、HTTPS工作流程

HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

它的工作流程为:

  1. 客户端访问网站,先建立SSL连接,发送一个随机数A以及自己的加密套件等信息。这一步通常被成为Client Hello
  2. 服务端收到客户端的请求之后,生成一个随机数B,携带自己的证书(证书包含公钥、有效期等信息)一起发送给客户端,同时返回的还有加密协议信息。这一步通常称为Server Hello
  3. 客户端拿到证书后,通过公钥验证证书是否有效。如果有效的话,通过随机数A、随机数B以及公钥加密得到一个预主密钥,发送到服务端,这个密钥被称为Premaster Secret
  4. 服务端拿到客户端的数据后用私钥解开,通过加密校验,确认数据来自客户端。然后用随机数和预主密钥加密得到会话密钥,通过会话密钥加密一段握手消息,发送到客户端。
  5. 客户端收到之后解密数据,如果校验通过也发送一段握手消息到服务端。从这里开始所有后续的数据就会通过会话密钥来加密数据通信了。

图解流程为:

二、为什么需要多个随机数

避免客户端的随机数生成不是绝对随机的,多加一层随机数校验

一、TCP协议

TCP协议的首部:

字段解析:

  • 源目端口号:各2字节,short类型的端口号
  • 序号:标识当前TCP数据包在数据流中的序号,即平常的SYN和SEQ信息。
  • 确认号:收到数据报后,返回的ACK序号
  • 首部长度:TCP协议的首部长度,以四字节为单位,最多60字节。默认不带选项的情况下TCP首部是4个字节。
  • TCP选项:

    • URG: 紧急指针有效,很少使用
    • ACK: 确认选项
    • PSH: 推送选项,接收方收到后应该尽快被发送到应用层,选项没有被可靠的实现或用到
    • RST: 重置连接,经常是因为错误取消
    • SYN`: 开始连接,初始化一个连接的同步序号
    • FIN`: 结束连接
  • 窗口大小:2字节,滑动窗口协议中用来控制流量大小的字段
  • 校验和:计算方式和UDP一致,也会添加一个伪首部,计算整个头部和数据部分的内容
  • 紧急指针:只有在URG被设置时才有效,很少用到。
  • 其他选项

    • MSS: 最大段大小,表示连接希望收到的报文段的最大值

二、三次握手和四次挥手

参考TCP协议中的三次握手和四次挥手

三、TCP超时和重传

在连接无法收到响应后,TCP将会尝试重传数据,重传的时间间隔称为二进制指数退避。不过和CSMA/CD协议中的不同,这里的二进制指数退避是在之前的重传间隔上加倍。

0.2 - 0.4 - 0.8 - 1.6 - 3.2 - 6.4 - 12.8 - 25 - 50 - 100 - 200 - ...

linux中可以通过配置来设置重传的次数,重传涉及两个次数:第一个是TCP重新连接时尝试重传的次数,第二个是TCP放弃当前连接的时机。

net.ipv4.tcp_retries1
net.ipv4.tcp_retries2

3.1 RTT

RTT表示一个连接的往返时间,即数据发送时刻到接收到确认的时刻的差值。

RTT的取值方式:为一个已发送但尚未确认的报文段做一次RTT采样,得到一个SampleRTT(不是为每一个发送的报文段都测量RTT),从而用这个SampleRTT来接近(代表)所有RTT。

  • 一个连接中,有且仅有一个测量定时器被使用。也就是说,如果TCP连续发出3组数据,只有一组数据会被测量。
  • TCP决不会为已被重传的报文段测量SampleRTT,仅仅为传输一次的报文段测量SampleRTT。
  • ACK数据报不会被测量,原因很简单,没有ACK的ACK回应可以供结束定时器测量。

由于链路负载的变化,很多时候RTT并不能完全代表网络中真正的状态,为了得到一个更合理更接近事实的RTT,TCP规范中通过一个低通过滤器来设置出平滑的RTT估计器:

SRTT = α(SRTT) + (1 - α)RTT

α一般设置为0.8-0.9,意思是SRTT的值的80%-90%来自前一个估计值,10%-20%来自当前的估计值。

3.2 RTO

RTO指重传超时时间,从数据发送时刻算起,超过这个时间便执行重传。

RTO的值依赖RTT,[RFC0793]中推荐的RTO计算公式为:

RTO = min(ubound, max(lbound, (RTT)β))

β是离散因子,推荐值为1.3-2.0,ubound是RTO的上边界(建议为1分钟)lbound是RTO的下边界(如1秒)。这种方法被称为经典方法,它使得RTO值为1秒或者2倍的SRTT。对于相对稳定的RTT来说,这种方法能取得不错的性能。运行在RTT波动较大的网络环境时,无法获得期望的效果。

3.3 TCP中的四个定时器

  • 重传定时器:为了控制丢失的报文段或丢弃的报文段,也就是对报文段确认的等待时间。即上面说的RTT和RTO。
  • 坚持定时器:为对付零窗口通知而设立的,具体见下面
  • 保活计时器:为保持TCP的keepalive设立的,一般为2小时
  • 时间等待计时器:关闭连接时需要等待的时间,即2MSL等待期

3.4 坚持定时器

当窗口大小为0时,如果一个确认丢失了,双方就有可能因为等待对方而使连接终止:接收方等待接收数据(因为它已经向发送方通告了一个非0的窗口),而发送方在等待允许它继续发送数据的窗口更新。

为防止这种死锁情况的发生,发送方使用一个坚持定时器(persist timer)来周期性地向接收方查询,以便发现窗口是否已增大。这些从发送方发出的报文段称为窗口探查(window probe)。

3.4 快重传

TCP发送端在观测到多个重复的ACK后,此时重传可能丢失的数据包分组。不要等到计时器超时,此时依旧可以发送新的数据。

四、拥塞避免

4.1 慢启动

一个TCP刚启动时,不知道外部网络环境状态,为了避免发送过多数据出现拥塞,TCP会维护一个从1开始的cwnd值,每次发送cwnd个数据包。当收到一个数据报的确认后,该字段值加1。这个过程被称为慢启动。

具体的过程为:

  1. 连接建好的开始先初始化cwnd = 1,表明可以传一个SMSS大小的数据
  2. 收到一个ACK,cwnd++,呈线性上升
  3. 过了一个RTT,cwnd = cwnd*2,呈指数上升
  4. 每当产生一个RTO超时重传,cwnd = 1, ssthresh减半

慢启动有一个门限值ssthresh,超过这个值之后会启动拥塞避免算法。

4.2 拥塞避免

cwnd超过ssthresh时,开始采用拥塞避免算法。因为cwnd不可能无限增加,拥塞避免算法的目的是使得cwnd处于更缓慢增长的模式。

假设cwnd=k*SMSS,当发送端收到一个包的ACK反馈的时候,按照cwnd=cwnd+(1/k)*SMSS。这样每经过一个RTT发送k个数据包的时候,cwnd增长了大约一个SMSS,通常上我们即描述为每经过一个往返时间 RTT 就把发送方的拥塞窗口 cwnd 加 1,使拥塞窗口 cwnd 按接近线性规律缓慢增长。这种增长方式叫做“加法增大”(Additive Increase)。总结如下:

  1. 收到一个ACK时,cwnd = cwnd + 1/cwnd
  2. 每过一个RTT时,cwnd = cwnd + 1

使用慢启动和拥塞避免算法产生的流量增长趋势图: