计算机网络相关知识体系详解
HTTP是上层应用层的网络传输协议,定义了网络传输中的通信格式,而实际的传输则由传输层的TCP/IP协议负责。HTTP 通信由 TCP/IP 承载的, TCP/IP 是全球计算机及网络设备都在使用的一种常用的分组交换网络分层协议集。 客户端应用程序可以打开一条 TCP/IP 连接,连接到可能运行在世界任何地方的服务器应用程序。 一旦连接建立, 在客户端和服务器的计算机之间交换的报文就永远不会丢失、 受损或失序。
TCP协议目的是为了保证数据能在两端准确连续的流动,可以想象两个建立起TCP通道的设备就如同接起了一根水管,数据就是水管中的水由一头流向另一头。然而TCP为了能让一个设备连接多根“水管”,让一个设备能同时与多个设备交互信息,它必须要保证不同水管之间不会产生串联或相互影响
为了确保数据能够正确分发,TCP用一种叫做TCB,也叫传输控制块的数据结构把发给不同设备的数据封装起来,我们可以把该结构看做是信封。一个TCB数据块包含了数据发送双方对应的socket信息以及拥有装载数据的缓冲区。
在两个设备要建立连接发送数据之前,双方都必须要做一些准备工作,分配内存建立起TCB数据块就是连接建立前必须要做的准备工作 。
最开始的时候客户端和服务器都是处于CLOSED状态。主动打开连接的为客户端,被动打开连接的是服务器。
TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态
TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号 seq=x
此时,TCP客户端进程进入了 SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
ACK为1表示确认号有效,为0表示报文中不包含确认信息
TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
当服务器收到客户端的确认后也进入established状态,此后双方就可以开始通信了。
注:tcp建立连接需要三次握手,SYN是发送标志位,ACK是确认标志位.
主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
换个易于理解的视角来看为什么要3次握手。
客户端和服务端通信前要进行连接,3次握手的作用就是双方都能明确自己和对方的收、发能力是正常的。
第一次握手:客户端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
第二次握手:服务端发包,客户端收到了。这样客户端就能得出结论:服务端的接收、发送能力,客户端的接收、发送能力是正常的。
第三次握手:客户端发包,服务端收到了。这样服务端就能得出结论:客户端的接收、发送能力,服务端的发送、接收能力是正常的。 第一、二次握手后,服务端并不知道客户端的接收能力以及自己的发送能力是否正常。而在第三次握手时,服务端收到了客户端对第二次握手作的回应。从服务端的角度,我在第二次握手时的响应数据发送出去了,客户端接收到了。所以,我的发送能力是正常的。而客户端的接收能力也是正常的
因此,三次握手的主要目的就是收发双方为了确认彼此是否可以正常通信而进行的。只有能够正常通信,才会后续发送请求报文。同时也是为了建立收发双发的确认号Ack和序号Seq的值,在后续的数据传输阶段,都是在彼此Ack和Seq值的基础上进行计算的,这样做保证了TCP报文传输的连贯性
三次握手也是客户端和服务端建立通信连接的过程。
经历了上面的三次握手过程,客户端和服务端都确认了自己的接收、发送能力是正常的。之后就可以正常通信了。
如果server端接到了clien发的SYN后回了SYN-ACK后client掉线了,server端没有收到client回来的ACK,那么,这个连接处于一个中间状态,即没成功,也没失败。于是,server端如果在一定时间内没有收到的TCP会重发SYN-ACK。在Linux下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻售,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP才会把断开这个连接。
DDOS又称为分布式拒绝服务,全称是Distributed Denial of Service。DDOS本是利用合理的请求造成服务器资源过载,导致服务不可用。常见的DDOS攻击有SYN flood(SYN flood)、UDP flood、ICMP、flood等,其中SYN flood是一种最为经典的DDOS攻击。SYN flood如此猖獗是因为它利用了TCP协议设计中的缺陷,而TCP/IP协议是整个互联网的基础,牵一发而动全身,如今想要修复这样的缺陷几乎成为不可能的事情。
SYN flood攻击原理:
数据传输完毕后,双方都可释放连接。最开始的时候,客户端和服务器都是处于established(表示连接已经建立)状态,然后客户端主动关闭,服务器被动关闭。
客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1(FIN表示关闭连接,SYN表示建立连接),其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1(确认序号为收到的序号加1),并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2*MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态
总结
MSL(Maximum Segment Lifetime),TCP允许不同的实现可以设置不同的MSL值。
去向ACK消息最大存活时间(MSL) + 来向FIN消息的最大存活时间(MSL)。这恰恰就是**2MSL( Maximum Segment Life)。
第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,等待2MSL时间,客户端就可以放心地释放TCP占用的资源、端口号。如果不等,释放的端口可能会重连刚断开的服务器端口,这样依然存活在网络里的老的TCP报文可能与新TCP连接报文冲突,造成数据冲突,为避免此种情况,需要耐心等待网络老的TCP连接的活跃报文全部死翘翘,2MSL时间可以满足这个需求(尽管非常保守)!
建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必已经将全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
TCP通过 “ 发送 — 应答 (ACK确认)”来确保传输的可靠性,它是端到端传输的。
TCP传输是分段的,一个HTTP响应报文会被操作系统切成多个MSS(Maximum Segment Size)大小的段,直到接收端接受到完整的报文为止。 在此过程中,报文分段按照顺序进行发送,每个报文段在发送时,会做顺序编号,以便能够完整正确地组装。
MSS:Maximum Segment Size 最大报文段长度,是TCP协议的一个选项,用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度(不包括文段头)。如果MSS选项数据为512,则表示该报文段的发送方可以处理的最大报文段长度为512字节(不包括TCP与IP协议头长度)。主机一般默认MSS为536字节
端口号
表示同一个计算机上的不同进程
源端口号和目标端口号都是占用了两个字节
TCP的源端口号和目标端口号预计IP报文中的源IP和目标IP确认一条唯一的TCP连接
序号
Seq,4个字节
确认序号
Ack,占四个字节
控制位
URG、ACK、PSH、RST、SYN、FIN
TCP 的数据是通过名为 IP 分组( 或 IP 数据报) 的小数据块来发送的。HTTP就是“HTTP over TCP over IP” 这个“协议栈” 中的最顶层了。 其安全版本 HTTPS就是在 HTTP 和 TCP 之间插入了一个( 称为 TLS 或 SSL的) 密码加密层。
HTTP 要传送一条报文时, 会以流的形式将报文数据的内容通过一条打开的TCP 连接按序传输。 TCP 收到数据流之后, 会将数据流砍成被称作段的小数据块, 并将段封装在 IP 分组中, 通过因特网进行传输。 所有这些工作都是由TCP/IP 软件来处理的, HTTP 程序员什么都看不到
每个 TCP 段都是由 IP 分组承载, 从一个 IP 地址发送到另一个 IP 地址的。 每个 IP分组中都包括:
IP 首部包含源和目的 IP 地址、 长度和其他一些标记。 TCP 段的首部包含了TCP端口号、 TCP 控制标记, 以及用于数据排序和完整性检查的一些数字值。
TCP 连接是通过 4 个值来识别的:
< 源 IP 地址、 源端口号、 目的 IP 地址、 目的端口号 >
这 4 个值一起唯一地定义了一条连接。 两条不同的 TCP 连接不能在同一时刻拥有 4 个完全相同的地址组件值
有些连接共享了相同的目的端口号(C 和 D 都使用目的端口号 80)。 有些连接使用了相同的源 IP 地址(B 和 C)。 有些使用了相同的目的 IP 地址(A 和 B,C和 D)。 但没有两个不同连接所有的 4 个值都一样。
将TCP与UDP这样的简单传输协议区分开来的两种协议不同的传输数据的质量。TCP对于发送数据进行跟踪,这种数据管理需要协议有以下两大关键功能:
要完成这些任务,整个协议操作是围绕滑动窗口确认机制来进行的。因此,理解了滑动窗口,也就是理解了TCP。
在我们滑动窗口协议之前,我们如何来保证发送方与接收方之间,每个包都能被收到,并且是按次序的呢?
问题
吞吐量非常的低。我们发完包1,一定要等确认包1,我们才能发送第二个包。
那么我们就不能先连发几个包等他一起确认吗?这样的话速度更快,吞吐量更高
问题
如果过多的源同时以很快的速度发送大量的数据包,而此时接收方并没有如此高的接收数据的能力,因此极易导致网络的拥塞。
滑动窗口协议(Sliding Window Protocol)
该协议是 TCP协议 的一种应用,用于网络数据传输时的流量控制,以避免拥塞的发生。**该协议允许发送方在停止并等待确认前发送多个数据分组。**由于发送方不必每发一个分组就停下来等待确认。因此该协议可以加速数据的传输,提高网络吞吐量。滑动窗口算法其实和这个是一样的,只是用的地方场景不一样。
如果我们在任一时间点对于这一过程做一个“快照”,那么我们可以将TCP buffer中的数据分为以下四类,并把它们看作一个时间轴:
说明
正常情况
可以看到4号包对方已经被接收到,所以被涂成了灰色。“窗口”就往右移一格。我们就把11号包读进了我们的缓存。进入了“待发送”的状态。8、9号包已经变成了黄色,表示已经发送出去了。接下来的操作就是一样的了,确认包后,窗口往后移继续将未发送的包读进缓存,把“待发送“状态的包变为”已发送“。
丢包情况
有可能我们包(5-11)发过去,对方的Ack丢了。也有可能我们的包并没有发送过去。从发送方角度看就是我们没有收到Ack。
发生的情况:一直在等Ack。如果一直等不到的话,我们也会把读进缓存的待发送的包也一起发过去。但是,这个时候我们的窗口已经发满了。所以并不能把12号包读进来,而是始终在等待5号包的Ack。
问题
如果我们这个Ack始终不来怎么办呢?
超时重发/重传
原理是在发送某一个数据以后就开启一个计时器,在一定时间内如果没有得到发送的数据报的ACK报文,那么就重新发送数据,直到发送成功为止。
影响超时重传机制协议效率的一个关键参数是重传超时时间(RTO,Retransmission TimeOut)。RTO的值被设置过大过小都会对协议造成不利影响。
在 Unix 以及 Windows 系统中,最初其重发超时的默认值一般设置为6秒(重发时间必须是0.5秒的倍数)左右。数据被重发之后若还是收不到确认应答,则进行再次发送。此时,等待确认应答的时间将会以2倍、4倍的指数函数延长。
此外,数据也不会被无限、反复地重发。达到一定重发次数之后,如果仍没有任何确认应答返回,就会判断为网络或对端主机发生了异常,强制关闭连接,并且通知应用通信异常强行终止。
HTTP 紧挨着 TCP, 位于其上层, 所以 HTTP 事务的性能在很大程度上取决于底层TCP 通道的性能。
HTTP 事务的时延有以下几种主要原因
这些网络时延的大小取决于硬件速度、 网络和服务器的负载, 请求和响应报文的尺寸, 以及客户端和服务器之间的距离。 TCP 协议的技术复杂性也会对时延产生巨大的影响
性能聚焦区域
TCP 连接握手需要经过以下几个步骤。
由于网络自身无法确保可靠的分组传输( 如果网络设备超负荷的话, 可以随意丢弃分组), 所以 TCP 实现了自己的确认机制来确保数据的成功传输。每个 TCP 段都有一个序列号和数据完整性校验和。 服务端收到完好的TCP段时,都会向发送者回送小的确认报文。 如果发送者没有在指定的窗口时间内收到确认信息, 发送者就认为分组已损毁或丢失, 并重发数据。
由于确认报文很小, 所以 TCP 允许在发往相同方向的输出数据分组中对其进行“捎带”。 TCP 将返回的确认信息与输出的数据分组结合在一起, 可以更有效地利用网络。 为了增加确认报文找到同向传输数据分组的可能性, 很多 TCP栈都实现了一种“延迟确认” 算法。 延迟确认算法会在一个特定的窗口时间( 通常是 100 ~ 200 毫秒) 内将输出确认存放在缓冲区中, 以寻找能够捎带它的输出数据分组。 如果在那个时间段内没有输出数据分组, 就将确认信息放在单独的分组中传送。
但是当希望有相反方向回传分组的时候, 偏偏没有那么多。 通常, 延迟确认算法会引入相当大的时延。
慢启动算法思路
主机开发发送数据报时,如果立即将大量的数据注入到网络中,可能会出现网络的拥塞。慢启动算法就是在主机刚开始发送数据报的时候先探测一下网络的状况,如果网络状况良好,发送方每发送一次文段都能正确的接受确认报文段。那么就从小到大的增加拥塞窗口的大小,即增加发送窗口的大小, 用于防止因特网的突然过载和拥塞。
TCP 慢启动限制了一个 TCP 端点在任意时刻可以传输的分组数。 简单来说, 每成功接收一个分组, 发送端就有了发送另外两个分组的权限。 如果某个HTTP 事务有大量数据要发送, 是不能一次将所有分组都发送出去的。 必须发送一个分组, 等待确认; 然后可以发送两个分组, 每个分组都必须被确认,这样就可以发送四个分组了,以此类推。 这种方式被称为慢启动。
由于上述慢启动方法中,拥塞窗口的扩大方式采用指数型扩大,因此在窗口大到一定程度后(达到慢启动门限ssthresh),减慢增加的速度,转成线性扩大窗口的方式,也就是每次收到新的 ACK 没有丢包的话只比上次窗口增大 1。整个过程看起来就像这样:
随着窗口进一步缓慢增加,终于有一天,网络还是遇到了丢包的情况,我们就会假定这是拥塞造成的。这个时候会进行超时重传或者快速重传。
如果只对连接进行简单的管理, TCP 的性能时延可能会叠加起来。 比如, 假设有一 个包含了 3 个嵌入图片的 Web 页面。 浏览器需要发起 4 个 HTTP 事务来显示此页面:1 个用于顶层的 HTML 页面, 3 个用于嵌入的图片。 如果每个事务都需要(串行地建立) 一条新的连接, 那么连接时延和慢启动时延就会叠加起来 。
除了串行加载引入的实际时延之外, 加载一幅图片时, 页面上其他地方都没有动静也会让人觉得速度很慢。 用户更希望能够同时加载多幅图片。
串行加载的另一个缺点是, 有些浏览器在对象加载完毕之前无法获知对象的尺寸,而且它们可能需要尺寸信息来决定将对象放在屏幕的什么位置上, 所以在加载了足够多的对象之前, 无法在屏幕上显示任何内容。 在这种情况下, 可能浏览器串行装载对象的进度很正常, 但用户面对的却是一个空白的屏幕, 对装载的进度一无所知。
还有几种现存和新兴的方法可以提高 HTTP 的连接性能:
并行连接
通过多条 TCP 连接发起并发的 HTTP 请求
持久连接
重用 TCP 连接, 以消除连接及关闭时延
管道化连接
通过共享的 TCP 连接发起并发的 HTTP 请求
如前所述, 浏览器可以先完整地请求原始的 HTML 页面, 然后请求第一个嵌入对象, 然后请求第二个嵌入对象等, 以这种简单的方式对每个嵌入式对象进行串行处理。 但这样实在是太慢了!
HTTP 允许客户端(浏览器)打开多条TCP连接, 并行地执行多个 HTTP 事务(每个TCP连接处理一个HTTP事务)。 在这个例子中, 并行加载了四幅嵌入式图片, 每个事务都有自己的 TCP 连接。 页面上的每个组件都包含一个独立的HTTP 事务
并行连接不一定更快
打开大量连接会消耗很多内存资源, 从而引发自身的性能问题。 复杂的 Web页面可能会有数十或数百个内嵌对象。 客户端可能可以打开数百个连接, 但 Web服务器通常要同时处理很多其他用户的请求, 所以很少有 Web 服务器希望出现这样的情况。 一百个用户同时发出申请, 每个用户打开 100 个连接, 服务器就要负责处理10 000 个连接。 这会造成服务器性能的严重下降。 对高负荷的代理来说也同样如此。
实际上, 浏览器确实使用了并行连接, 但它们会将向同一个域名请求的并行连接的总数限制为一个较小的值。
浏览器同域名请求的最大并发数限制:
Web 客户端经常会打开到同一个站点的连接。 比如, 一个 Web 页面上的大部分内嵌图片通常都来自同一个 Web 站点, 而且相当一部分指向其他对象的超链通常都指向同一个站点。 因此, 初始化了对某服务器 HTTP 请求的应用程序很可能会在不久的将来对那台服务器发起更多的请求(比如, 获取在线图片)。这种性质被称为站点局部性(site locality)。
HTTP/1.1(以及 HTTP/1.0 的各种增强版本) 允许 HTTP 设备在事务处理结束之后将 TCP 连接保持在打开状态, 以便为未来的 HTTP 请求重用现存的连接。在事务处理结束之后仍然保持在打开状态的 TCP 连接被称为持久连接。 非持久连接会在每个事务结束之后关闭。 持久连接会在不同事务之间保持打开状态,直到客户端或服务器决定将其关闭为止。
重用已对目标服务器打开的空闲持久连接, 就可以避开缓慢的连接建立阶段。而且,已经打开的连接还可以避免慢启动的拥塞适应阶段, 以便更快速地进行数据的传输。
Connection: Keep-Alive
在持久连接中,每一个HTTP请求都是串行的。每个请求的发送都必须等待上一个请求的响应。
HTTP/1.1 允许在持久连接上使用管道化(pipeline)技术。 这是相对于 keepalive 连接的又一性能优化。
在响应到达之前, 可以将多条请求放入队列。 当第一条请求通过网络流向另一端的服务器时, 第二条和第三条请求也可以开始发送了。 在高时延网络条件下, 这样做可以降低网络的环回时间, 提高性能。
对管道化连接有几条限制