传输层
在前面的文章中我们总结了TCP/IP五层模型中的链路层和网络层,这两层主要负责的是主机和主机之间的通信,那么我们传输的数据到达主机之后呢?每台电脑上有那么多的应用程序,这些数据该分配给哪个进程?这就是这一章要提到的传输层的工作了。它主要负责的是端口与端口之间的通信。
TCP和UDP
在传输层中最重量级的两个协议,就是UDP和TCP 1.UDP(User Data Protocol)用户数据报协议
- 使用UDP协议的主机可以在任意时刻发送数据,远程主机接收数据后也无需返回响应
- 虽然UDP协议提供无连接,不可靠的服务,但是在一些即时通信的场景经常使用(QQ,微信)
2.TCP(Transmission Control Protocol)传输控制协议
- TCP提供面向连接的服务,在发送数据前要建立连接,发送完成后要断开连接。
- TCP提供可靠的传输服务,所以TCP不支持广播和多播(仅一对一),这使得TCP报文的首部增大很多,并且还会占用处理机资源
- TCP一般用于传输文件,远程登录,发送接收邮件等场景
TCP报文段首部格式
TCP 报文段的具体格式大家可以不必都记住,但是其中的几个控制位与我们接下来要讲的三次握手和四次挥手息息相关,大家一定要牢记。
- 源端口和目的端口:各占两个字节,是源主机和目的主机对应进程的端口号
- 序号/序列号(Sequence Number,SN):在TCP传输的数据中每一个字节都是按顺序编号的,序号就是此次发送数据的第一个字节的序号。初始学号称为Init Sequence Number
- 确认号ack:期望收到对方发送过来的下一个报文段的第一个字节的序号,如果确认好为N,就表面序号N-1及之前的数据都成功收到了。
- 控制位(保留右边的六个字段):
1.URG-紧急位:当URG=1时,表明当前报文段中有紧急数据,是高优先级的数据,不必在缓存中排队,应该尽快发送。URG和紧急指针配合使用,紧急指针用来指出当前报文段紧急数据的字节数。(比如说有个病毒程序我想马上中断它,如果没有URG,这个指令还得在队列中排队等待处理,黄花菜都凉了) 2.ACK-确认:这个和确认号ack不是一个东西,ACK=1的时候ack确认号才是有效的,ACK=0的话ack是无效的,规定TCP连接建立之后ACK都要置一 3.PSH-推送:在两个进程交互通信时,有时某个进程想另一个进程立刻响应本次发送的指令,那么此时就可使用PUSH操作,发送方TCP把PUSH置1,然后把报文段发送出去,接收方收到后查看报文段的PUSH=1,就尽快交付接收应用进程,而不是等待缓存填满再向上交付 4.RST-复位:当TCP连接出现了严重错误的时候(主机崩溃或其他),需要释放连接再重新建立 5.SYN-同步:SYN=1时表示这是一个连接请求或者连接接受响应报文 当 SYN = 1 而 ACK = 0 时,表明这是一个连接请求报文段。对方若同意建立连接,则应在响应的报文段中使 SYN = 1 且 ACK = 1。 6.FIN-终止:用来释放一个连接,当FIN=1时表示我这一端的数据已经发送完成了,请求释放连接。
上述解析了TCP首部报文里的各个字段和6个控制位的含义,就是因为TCP提供可靠连接就是靠的上述的这些东西,接下来我们来解析一下TCP三次握手建立连接和四次挥手释放连接的过程
TCP三次握手建立连接
进行三次握手主要是为了确认发收双方的发送和接收数据能力是否正常,必须是三次吗?两次行不行,四次行不行?
我们先来看一下三次握手的过程: 回顾一下上述字符的含义:
- SYN:连接请求/响应报文
- seq:序列号,表示本报文段第一个字节的序列号
- ACK:确认,为1时表示连接已建立,ack有效
- ack:确认号,ack=x+1,表示前x个字节数据都正常接收,并且表示下一个想收到的数据开头序号为x+1
刚开始的时候客户端处于CLOSED状态,服务端处于LISTEN状态
CLOSED :没有任何连接状态 LISTEN :侦听来自远方 TCP 端口的连接请求
第一次握手 客户端发送一个SYN=1的连接请求报文,然后该报文还包含一个初始序列号 seq=x,表示这个报文第一个字节的序号,此时客户端处于SYN_Sent状态 (SYN_Sent:发送连接请求报文后等待匹配连接的状态) 第二次握手 服务端收到客户端的SYN报文之后,也发送一个SYN=1的响应报文,并且也把自己的初始序列号seq=y,以及确认号ack=x+1加进报文中,表示成功收到了客户端的连接请求报文,记得还要把ACK置1,此时服务端处于 SYN_RCVD状态(收到连接请求报文后,等待对方的连接响应报文) 第三次握手 客户端收到服务端的SYN报文后,此时已经确定服务端的发送和接收能力正常,这时客户端会发送一个ACK报文给服务端,ACK报文里包含ack=y+1的确认号,seq=x+1的序列号,表示将x+1开头的报文段发送过去,并且也将ACK置1,此时客户端从SYN_SENT->Established状态,表示连接建立
在服务端收到ACK报文后,也从SYN_RCVD->Established状态。 至此,TCP连接建立完成。
那么再回到上面的问题,为什么一定要三次握手,我们来分析一下: 第一次握手,客户端发送SYN报文,此时服务端收到后知道客户端的发送能力正常,自己的接收能力正常;客户端啥也不知道。 第二次握手,客户端收到服务端的SYN报文,知道自己的发送和接收能力正常,服务端的发送和接收能力也正常,但是服务端还是只知道客户端的发送能力正常,自己的接收能力正常 第三次握手,服务端再收到客户端的ACK报文之后,知道了自己的发送和接收能力正常,对方的发送和接收能力也正常。 所以两次握手不能保证是可靠、安全的连接,四次握手就有些多余,浪费网络流量。
三次握手过程中可以发送数据吗? 第一、二次不行,第三次握手可以,因为第一、第二次握手都处于连接建立的状态,如果在这时候有攻击者一直向服务端发送SYN报文并且携带大量数据(攻击者是不需要理会服务端的接收能力的),服务端会耗费大量的时间和内存去处理这些SYN恶意报文,得不偿失。
简单的记忆就是当SYN=1的时候,不可以携带数据。
而第三次的时候,此时客户端已经处于Established状态,知道服务端的发送接收正常,自然可以携带数据。
半连接队列 当服务端处于SYN_RCVD状态时,此时双方还没有完全建立连接,服务器会把这种状态下的SYN报文放在一个队列里,这个队列就是半连接队列,还有一个全连接队列,就是当第三次握手之后,所有报文都会放在全连接队列里,如果队列满了还可能会丢包。
SYN泛洪攻击 攻击者伪造大量不存在的IP地址,然后给服务器发送大量SYN报文,那么服务器就得不断的响应,并等待客户端的ACK报文,但是由于IP地址根本不存在,所以半连接队列会被这些虚假SYN报文塞满,正常的SYN报文会被阻塞,严重的会导致网络瘫痪
如果第三次握手丢包了,怎么处理? 重传,服务器发送完SYN报文后,会等待一定时间客户端的ACK报文,如果超时了,就会首次重传,如果再超时就继续重传,每次等待的时间指数增长,1,2,4,8…重传超过一定次数,就将该报文从半连接队列中删除。
TCP四次挥手释放连接
建立连接只需要三次握手,而断开一个连接却需要四次挥手,这是由于TCP的半关闭(half-close)特性造成的,TCP令提供连接的一端在结束它的发送后还能接收另一端数据的能力。
TCP四次挥手断开连接,客户端和服务端均可主动发起 我们也像建立连接一样解析一下上述各个字段的含义:
- FIN:和SYN相反,SYN请求建立连接,那么FIN报文就是请求断开连接
- seq:报文首个字节的序号
- ACK:确认报文
- ack:确认号,希望收到下一个报文的首个字节的序号
首先双方都处于Established状态,假设由客户端发起挥手动作 第一次挥手 客户端发送一个FIN报文,包含seq=u,并停止发送数据,主动关闭TCP连接,此时客户端处于FIN_WAIT1状态,等待服务端的ACK确认报文 第二次挥手 服务端收到FIN报文之后,会发送一个ACK报文,并把ack=u+1,表示收到了客户端的FIN报文,此时服务端处于CLOSE_WAIT状态 客户端收到ACK报文之后,此时TCP处于半关闭状态,客户端到服务端的连接释放,此时客户端由FIN_WAIT1->FIN_WAIT2状态,现在客户端只需等待服务端发送FIN报文请求释放连接即可 第三次挥手 当服务端也把自己该发送的数据发送完成,也会给客户端发送一个FIN报文,且指定一个序列号,此时服务端由CLOSE_WAIT->LAST_ACK状态,等待客户端的ACK报文 第四次挥手 客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答(ack = w+1),且把服务端的序列值 +1 作为自己 ACK 报文的序号值(seq=u+1),此时客户端处于 TIME_WAIT (时间等待)状态。 注意此时服务端到客户端的连接仍然没有断开,还需等待2MSL(一个报文来回时间)才会变成CLOSED状态,这是因为假设此时ACK报文发生了丢包状况,那么服务端在规定时间没有收到,就会以为客户端没有收到自己发送的FIN报文,会进行重传,客户端一收到FIN报文,就知道之前的ACK报文丢了,也会重新发送,服务端收到后才会断开连接,变成CLOSED状态。
TCP如何保证可靠传输
可靠传输,其实就是保证发送方发出的字节流和接收方的收到的字节流是完全一致的,那么我们上文已经知道TCP的面向连接是依靠三次握手和四次挥手,那可靠传输是如何保证的呢?
TCP保证可靠传输的方式有以下几种:
- 校验和以及CheckSum(知道即可)
- 序列号和确认应答机制(重要)
- 重传机制
- 流量控制(拥塞窗口协议)
- 拥塞控制
序列号和确认应答机制
TCP报文段中包含一个字段seq,表示这个报文的第一个字节的序号(一个字节一个序号) 那么确认应答机制就是接收方收到报文之后返回的一个确认应答消息 确认应答机制和重传机制是结合使用的,所以我们下面再来解释一下重传机制
重传机制
在复杂的网络传输过程中,报文的传输并不一定是安全的,比如说遇到恶意攻击,网络故障,主机崩溃等情况都有可能令报文丢失,比如说我需要序列号为101开头的数据,结果你发了一个102开头的报文,通常遇到这种情况,都是由TCP负责恢复丢失的报文
那么最基本的恢复功能就是重传机制了,常见的重传机制有: 超时重传和快速重传
1.超时重传 超时重传就是发送方发送报文时会设置一个计时器,如果在规定时间内发生方没有收到接收方的ACK报文,就会触发超时重传,重新发送这个报文(第四次挥手就是这个原理)
造成发送方没有收到ACK报文的情况有两种
1.发送方的报文丢失 2.接收方的ACK报文丢失 超时重传时间我们一般用 RTO(Retransmission Timeout) 来表示,那么,这个 RTO 设置为多少最合适呢,也就是说经过多长时间进行重传最好?
在这之前,我们先讲解一下 RTT(Round-Trip Time 往返时延) 的概念:RTT 就是数据从网络一端传送到另一端所需的时间,也就是报文段的往返时间
显然超时重传的世界RTO应略微大于RTT即可
- 如果RTO远大于RTT,就会造成网络空闲时间过长,效率下降
- 如果RTO小于RTT,那么就会造成不必要的重传,资源浪费
如果超时重传也失败了,每次的等待时间都会成倍增长,1-2-4-8,但是这会导致一个问题,就是周期太长了,效率比较低,那么有没有什么改进的方法呢?——快速重传
2.快速重传 快速重传不以时间为驱动,而是以数据为驱动 原理:当接收方收到的ACK报文的seq比确认号ack更大时,就会给发送方发送一个冗余ACK,指明期望收到的下一个报文段的序列号为多少。
举个例子:发送方给接收方发送1,2,3,4,5个报文段 1.接收方收到报文段 1,返回 1 的 ACK 确认报文(确认号为报文段 2 的第一个字节) 2.接收方收到报文段 3,仍然返回 1 的 ACK 确认报文(确认号为报文段 2 的第一个字节) 3.接收方收到报文段 4,仍然返回 1 的 ACK 确认报文(确认号为报文段 2 的第一个字节) 4,接收方收到报文段 5,仍然返回 1 的 ACK 确认报文(确认号为报文段 2 的第一个字节) 5.接收方收到 3 个对于报文段 1 的冗余 ACK,认为报文段 2 丢失,于是重传报文段 2 6.接收方收到了重传的报文2,此时因为3,4,5报文也已经收到了,此时会返回一个ACK报文,确认号ack为6 (下图有个小错误,触发的是快速重传,不是超时重传)
滑动窗口协议
1.累积确认 上述我们在总结快速重传的时候,提到过接收到报文2之后,此时因为报文段 3、4、5 都收到了,所以返回 6 的 ACK确认报文,这是为什么呢?
之前我们提到每收到一个报文段,就返回一个ACK确认报文,那么收到报文2,应该返回3的确认报文才对啊。
其实上面这种收到一个报文就返回一个确认报文的机制效率是特别低下的,每个报文的往返时间越长,网络的吞吐量就越低,效率自然就低了。
就比如:你发了一个消息给我,我在忙没回,所以你就一直等待我的回应,才和我说下一句话,这显然是不现实的。
为此TCP引入了窗口的概念,窗口的大小就是指无需等待确认应答,可以继续发送数据的最大值。
窗口的实质就是操作系统开辟的一块缓冲区,在发送方接收到确认应答报文之前,缓冲区必须保留这些已发送却无应答的报文段,直到对应的确认报文到达了,再将缓冲区中对应的报文挪走。
假设窗口大小为3个报文段,那么发送方可以连续发送三个报文段而无需等待确认应答报文,哪怕确认应答报文有一个丢失了,也可以通过后面的报文来确认。
举个例子,发送方发送了3个报文段,分别是(100-199,200-299,300-399),假设ack=300的ACK报文丢失了,接收方也无需重新发送,因为发送方可以根据ack=400的ACK报文确认——序号400之前的所有数据都已经成功收到了。 2.发送方的滑动窗口 发送方的滑动窗口主要分为四部分: 1.已发送并收到ACK确认报文的数据 2.已发送未收到ACK确认报文的数据 3.未发送但总大小仍在发送方处理范围内的数据 4.未发送但总大小超出发送方处理范围内的数据
当发送方处于3阶段的数据都发送出去后,此时滑动窗口的大小为0,表示在收到ACK确认报文之前,发送方不可以再发送数据了。,如下图所示
当收到之前发送的数据 32-36 字节的 ACK 确认应答后,如果发送窗口的大小没有变化,则滑动窗口往右边移动 5 个字节,因为有 5 个字节的数据被确认应答,接下来 52-56 字节又变成了可用窗口,那么后续也就可以发送 52~56 这 5 个字节的数据了: 3.接收方的滑动窗口 接收方滑动窗口可以分为3部分: 1.已成功接收并确认的数据 2.未收到数据但可以接收的数据 3.未收到数据但不可以接收的数据(超出滑动窗口大小)
同样的,接收方的滑动窗口在成功接收并确认的数据后,窗口右移。
流量控制
我们之前有提到过一个SYN泛洪攻击,就是说攻击者不理会服务器的接收能力,一次性发送大量的虚假SYN报文,从而导致大量ACK报文丢失,触发重传机制,造成网络阻塞甚至瘫痪。
所以为了防止上述的问题发生,TCP引入了流量控制机制,主机B给主机A发送自己的接收缓冲区(窗口)大小,来使主机A控制发送的报文数量,总结来说,就是控制发送方的发送速率,使得接收方来得及接收报文
TCP实现流量控制机制的体现就是滑动窗口协议
上文我们有提到过滑动窗口的大小,那么这个大小是如何确定的呢?就是在TCP报文段首部的窗口字段处设置的。 这个窗口字段的含义其实就是自己的接收窗口的剩余大小,这样发送方就会根据接收方的接收能力调节自己的发送速率,不会使得接收方瘫痪。
所以窗口大小通常是由接收方来决定的
所以大概的一个流程就是: 1.接收方在发送ACK确认报文时会将首部的窗口字段设置为缓冲区剩余大小(rwnd) 2.发送方接收到ACK报文后,查看窗口字段,知道了接收方窗口的剩余大小,调节自己的发送速率 3.如果窗口大小为0,此时发送方停止发送数据,并且定时向接收方发送窗口探测数据段,告知接收方把剩余窗口大小发送过来。
拥塞控制
这一部分可以说面试的时候经常会问到,一定要重点复习。 所谓拥塞就是说:在某段时间,对网络中某一资源的需求大于该资源可以提供的可用部分,即需大于供,网络性能变差。
如果网络出现拥塞,TCP报文可能会大量丢失,触发重传机制,加剧网络拥塞程度。
因此当出现拥塞的时候,就要调整发送方的发送速率,这和流量控制协议很像,但是出发点是不一样的。
流量控制考虑的是接收方的接收能力,而拥塞控制考虑的是网络的拥塞程度
流量控制是让接收方来得及接收,拥塞控制是为了降低网络的拥塞程度,防止过多数据注入网络
为了调节发送方的发送速率,引入了拥塞窗口(cwnd,上文提到的接收窗口时rwnd),拥塞窗口是发送方维护的一个状态变量,它会根据网络的拥塞程度动态变化。
- 只要网络中出现阻塞,cwnd减少
- 只要网络中未出现阻塞,cwnd增加
在引入拥塞窗口(cwnd)之前,发送方的发送窗口大小和接收窗口大小基本相等,但是后面发送窗口大小就变成了拥塞窗口和接收窗口的最小值
TCP的拥塞控制采用了四种算法: 1.慢开始 2.拥塞避免 3.快重传 4.快恢复
慢开始
慢开始的思路是:TCP在建立连接完成后,如果一下子把大量的数据注入网络中,很大概率会阻塞网络。所以最后是先探测一下,一点一点的提高发送数据包的数量。慢开始的cwnd初始化为1,每经过一个往返时间大小翻倍 当然如果一直采用慢开始算法,这样网络会很快阻塞,所以设置了一个 慢开始轮限(ssthresh)状态变量:
- 当cwnd<ssthresh时,继续使用慢开始
- 当cwnd>=ssthresh时,开始使用拥塞避免算法
拥塞避免算法
拥塞避免算法是缓慢的增加cwnd的大小,每经过一个往返时间cwnd+1
需要注意的是,无论是慢开始还是拥塞避免,如果一旦触发超时重传机制,慢开始轮限ssthresh和cwnd大小都会减小 ssthresh=cwnd//2 cwnd=1
网络阻塞触发重传机制后,cwnd重置为1,继续使用慢开始算法
快重传和快恢复
快重传算法和快恢复算法一般结合使用,我们知道快速重传是以数据为驱动的(所以比超时重传效率高) 当触发快速重传机制的时候,即接收方收到重复的3个ACK确认报文后,就会执行快重传算法(触发快速重传机制和超时重传机制的条件和对应方法都是不一样的:超时—拥塞避免+慢开始;快速—快重传+快恢复)
快重传的工作是:
- cwnd=cwnd//2(和慢开始的区别)
- ssthresh=cwnd
我们可以看到快重传的cwnd并没有重置为1,因为TCP认为触发快速重传机制时情况没有超时重传严重,快速重传只是丢失了一部分数据报而已。
快恢复算法是在快重传算法后面引入的,当收到3个冗余ACK后,TCP进入的并不是拥塞避免阶段,而是快恢复阶段
快恢复:当收到三个冗余ACK的同时,表示有三个数据包也已经成功接收了,根据数据包守恒原则,此时有三个“老”数据包离开了网络,那么就代表网络可以容许三个“新”数据包进入网络
快恢复的工作是: 1.cwnd=ssthresh+3 2.当再次收到1个冗余ACK时,cwnd+1 3.当收到新的ACK确认报文后,把cwnd设置为ssthresh的值,即cwnd=ssthresh。因为此时表示重复的ACK都已经确认了,恢复过程已经结束,可以进入拥塞避免阶段了。
应用层
我们在之前总结网络层的时候经常会谈到IP地址,那么IP地址到底是从何而来,如何得来的呢?答案就在应用层的DNS协议中。
DNS协议
什么是DNS协议
在谈及DNS协议前,我们需要区分域名和IP地址这两个概念
- 域名:又称网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时对计算机的定位标识(有时也指地理位置)比如 www.baidu.com
- IP地址:一长串的能够标识特定网络上的某台主机的数字
由于 IP 地址具有不方便记忆并且不能显示地址组织的名称和性质等缺点,人们设计出了域名,并通过域名解析协议(DNS,Domain Name System)来将域名和 IP 地址相互映射,使人更方便地访问互联网,而不用去记住能够被机器直接读取的 IP 地址数串。将域名映射成 IP 地址称为正向解析,将 IP 地址映射成域名称为反向解析。
域名
那么域名是怎么得来的呢?是哪个组织这么厉害可以创造出定位所有计算机的域名?
其实域名是由一个叫做 ICANN (Internet Corporation for Assigned Names and Numbers)的组织创造的,总部在美国加州。ICANN 负责管理全世界域名系统的运作。
域名其实是具有一定的层次结构的,从上到下依次为:根域名、顶级域名(top level domain,TLD)、二级域名、(三级域名)
1.顶级域名 简单的说,顶级域名就是网址最后的那一块,比如,网址www.baidu.com 的顶级域名就是 .com。ICANN 的一项主要工作,就是规定哪些字符串可以当作顶级域名。截至 2015 年 7 月,顶级域名共有 1058 个,它们大致可以分成两类:
- 通用顶级域名
- 国家顶级域名
域名这么多,ICANN就为每个顶级域名都找了一个托管商,该域名的所有事项都由托管商负责。ICANN 只与托管商联系,这样管理起来就容易多了。举例来说,.cn 国家顶级域名的托管商就是中国互联网络信息中心(CNNIC),它决定了 .cn 域名的各种政策。
2.二级域名 而二级域名(Second Level Domain,SLD) 在通用顶级域名或国家顶级域名之下具有不同的意义: 1)通用顶级域名下的二级域名:一般是指域名注册人选择使用的网上名称,如 yahoo.com(商业组织通常使用自己的商标、商号或其他商业标志作为自己的网上名称,如 baidu.com) 2)国家顶级域名下的二级域名:一般是指类似于通用顶级域名的表示注册人类别和功能的标志。例如,在 .com.cn 域名结构中,.com 此时是置于国家顶级域名 .cn 下的二级域名,表示中国的商业性组织,以此类推。
三级域名甚至不能称为域名,一般称之为域名下的 “二级目录”。
3.根域名 我们说了这么多,那么最顶级的根域名在哪里,为什么网址里找不到呢? 由于 ICANN 管理着所有的顶级域名,所以它是最高一级的域名节点,被称为根域名(root domain)。在有些场合,www.xxx.com 被写成 www.xxx.com.,即最后还会多出一个点’.’。这个点就是根域名。 理论上所有域名的查询都必须先查询根域名,因为只有根域名才能告诉你,某个顶级域名由哪台服务器管理。事实上也确实如此,ICANN 维护着一张列表(根域名列表),里面记载着顶级域名和对应的托管商。
4.域名服务器 域名服务器是指管理域名的主机和相应的软件,它可以管理所在分层的域的相关信息。一个域名服务器所负责管里的分层叫作 区 (ZONE)。域名的每层都设有一个域名服务器: 除了上述三种域名服务器,还有一个没有画出来的叫做本地域名服务器,也非常重要,我们来讲述一下这些服务器是如何协作工作的。
其实我们可以把上述域名服务器之间的关系看成是数据库里的B树,从根域名服务器开始,一层一层查询,直到找到对应的网址
DNS查询方式
DNS的查询方式有两种:
- 递归查询——必须返回正确的域名
- 迭代查询——告诉你这个域名归谁管,你去问它
👍 通俗点来说,在递归查询中,如果 A 请求 B,那么 B 作为请求的接收者一定要给 A 想要的答案;而迭代查询则是指,如果接收者 B 没有请求者 A 所需要的准确内容,接收者 B 将告诉请求者 A,如何去获得这个内容,但是自己并不去发出请求。
**域名服务器之间的查询使用迭代查询方式,以免根域名服务器的压力过大。**通过下面这两个图就能很好的理解了
输入一个URL,发生了什么事
我们上述已经知道IP地址其实就是域名通过DNS协议解析得来的,那么我们平时输入的网址是域名吗,输入网址后经过了什么操作,使得一个丰富的页面呈现在我们面前?
域名和URL(网址)
平时我们在页面上输入的其实是URL而不是域名,URL可以看做是域名的父集,URL里包含了域名,举个例子:www.baidu.com/veal98 是一个网址,而 www.baidu.com 就是服务器的域名。
一个完整的URL如下: 网址里的域名根据DNS解析得到IP地址后找到特定网络上的服务器,然后再根据路径名访问服务器里的文件。 那么客户端(浏览器)要做的第一步就是将域名和文件路径分开,从而知道需要在哪台服务器上请求哪种资源。
1.浏览器封装HTTP请求报文
对URL进行解析之后,浏览器确定了目标服务器和资源路径,接下来就需要根据这些消息封装成一个HTTP报文(下文会讲述)发送出去,举一个HTTP请求报文的例子: 其中封装这个概念是贯穿了整个计算机通信过程的,就是说发送端在层与层之间传输数据时,每经过一层就会打上一个该层所属的首部信息,这样接收方接收数据向上传递时就一层层的解封,将首部信息消去
应用层:HTTP报文 传输层:TCP/UDP报文 网络层:IP数据报 链路层:帧 物理层:比特流
2.DNS协议解析获取IP地址
HTTP报文封装好之后,我们要把这个报文发给谁?这就是DNS协议做的事情了,虽然解析得到了域名,理论浏览器已经知道目标服务器是谁了。但是实际上,域名并不是目标服务器真正意义上的地址,互联网上每一台计算机都被全世界唯一 IP 地址标识着,但是 IP 地址并不方便记忆,所以才设计出了域名。
那么我们上述提到的四个域名服务器在解析域名的过程中,就起到了重要作用了。
- 首先搜索浏览器的DNS缓存,缓存中维护着一张域名和IP地址的映射表
- 若没有命中,则继续搜索操作系统中的DNS缓存
- 若还是没有命中,就将域名发送至本地域名服务器,本地域名服务器查询自己的DNS缓存,如果命中就返回(到本地域名服务器为止,都是使用的递归查询)
- 如果本地域名服务器中也没有找到,则本地域名服务器向上级服务器进行查询,这时候的查询方式变成了迭代查询
- 首先本地域名服务器向根域名服务器查询,根域名服务器是最高层次的,它并不会直接指明这个域名对应的 IP 地址,而是返回顶级域名服务器的地址,也就是说给本地域名服务器指明一条道路,让他去这里寻找答案 - 本地域名服务器拿到顶级域名服务器地址后,再去顶级域名服务器查询,获取权限域名服务器的地址 - 本地域名服务器向权限域名服务器发起请求,获取到对应的IP地址 - 本地域名服务器将获取的IP地址返回给操作系统,并将IP地址缓存
- 操作系统将IP地址返回给浏览器,自己也把IP地址缓存
- 浏览器获得了域名对应的IP地址,并将IP地址缓存。
(这里需要提到DNS使用的是UDP协议,所以上述域名服务器的一系列请求的转发都是基于UDP这个无连接协议的)
3.建立TCP连接
获得IP地址后,浏览器就需要和特定的服务器建立TCP连接,然后发送封装好的HTTP请求报文了,TCP建立连接使用三次握手,就是为了保证发收双方的发送和接收能力正常,断开连接就使用四次挥手
4.浏览器发送请求
TCP连接建立完成之后,浏览器和服务器之间就有一条虚拟的传输通道可以用来传输数据了,由于HTTP请求报文比较大,所以TCP会将HTTP报文按序号分割成若干报文段并且加上TCP首部,分别进行传输,这样服务器收到后就可以根据TCP首部来组合报文段了。
5.负责传输的IP协议
TCP建立连接之后,其实传输TCP报文段的是IP协议,IP协议将TCP报文段加上IP首部封装成IP数据报再进行传输 IP数据报的首部包含源IP地址和目的IP地址,但是网络层的IP协议主要负责的是选择合适的网间路由和交换结点,保证数据及时送达,所以真正出力的并不是IP协议
6.链路层的MAC地址
上一篇文章中我们知道,其实真正负责传输数据的网络间复杂的一条条链路,IP协议的作用是在这些复杂的线路中找到最优解。那么如果要在链路上传输,不可缺少的就是MAC地址,这需要网络层的ARP协议解析IP地址,得到具体的MAC地址 当然在链路层传输的时候,链路层也会在IP数据报首部和尾部加上标识,封装成帧,在链路上传输的数据是帧而不是IP数据报
7.服务器响应请求
服务器收到数据后,一层一层的解封,组合数据,然后处理HTTP请求报文并 返回一个HTTP响应报文。
8.浏览器渲染页面
浏览器收到HTTP响应报文之后,根据浏览器的渲染机制对页面进行渲染
9.四次挥手断开连接
当浏览器和服务器都不需要发送接收数据后,用四次挥手断开TCP连接。
然后我们上面其实还有一些知识点,比如说HTTP协议,HTTP请求报文,以及使用UDP的DNS协议,我们一步一步来解析。
DNS协议只使用了UDP协议吗
严格的说,DNS协议并不是仅仅使用了UDP协议,它同时占用了 UDP 和 TCP 的 53 端口,作为单个应用层的协议,DNS 同时使用两种传输协议也属实是个另类了。
DNS为什么同时使用UDP和TCP
我们首先比较一下UDP和TCP之间的区别把
- TCP面向连接,需要三次握手和四次挥手,而UDP无连接
- TCP提供可靠的数据传输,而UDP是不可靠的
- TCP首部需要20个字节,而UDP首部仅8个字节
- TCP速度比较慢,但安全性高,通常用于远程登录,电子邮件收发;而UDP速度快,但不安全,通常用于即时通讯
- 这是最重要的一点,TCP是面向字节流的,而UDP是面向报文的(这也是DNS使用UDP的原因)
我们需要解释一下面向字节流和面向报文是什么意思:
- 我们都知道HTTP报文在传输过程中是会被TCP按序号分割,然后封装成TCP报文段,接收方在收到这些TCP报文段之后就可以按序号组合成一个完整的HTTP报文,所以说TCP是面向字节流的
- 而UDP报文在传输过程中是不经过任何拆分和合并的,因为UDP首部没有TCP首部中的序列号,所以默认只有一个UDP报文
那么这样就会带来一个问题。互联网上的链路的最大传输单元=576字节,为了可以顺利传输UDP报文,UDP报文大小被限制为512字节
那么DNS由于大面积使用UDP协议,所以当基于UDP协议的DNS报文大小超过512字节时,多出来的部分会被截取丢弃,这样用户收到的数据就是不完整的,这时候只能使用TCP协议,虽然速度慢,但是数据完整性有保障。
那么DNS何时使用UDP,何时使用TCP
- DNS在域名解析过程中,会根据DNS响应报文的大小选择使用TCP还是UDP,如果响应报文大小超出512字节就使用TCP,否则使用UDP,但是一般域名解析返回的UDP报文不会超过512字节,所以DNS默认使用UDP
- DNS在进行区域传输的时候一定会使用TCP保证安全和数据完整
所以HTTP协议为什么不使用速度更快的UDP也有了答案,因为HTTP协议是明文传输的,如果要保证安全性,传输数据时需要携带数字证书和数字签名,使得整个HTTP报文大小远大于512字节,自然要使用TCP协议
|