秋招保驾护航——计算机网络

660 阅读22分钟

秋招保驾护航系列文章正在持续更新,往期文章如下,需者自取:

传输层

传输层位于应用层和网络层中间,在因特网协议中,我们关注TCP和UDP协议。先来了解一下传输层提供的服务:

  • 传输层协议为运行在不同进程之间提供了逻辑通信

  • 应用进程通过使用传输层提供的逻辑通信功能彼此发送报文,无需考虑承载这些报文的物理基础设施的细节

  • 传输层协议是在端系统实现的:

    • 发送端,运输层将从应用进程接收到的报文转换为报文段
    • 为每个报文段加上一个运输层首部,生成一个运输层报文段
    • 传递这些报文段给网络层
    • 网络层将其封装成数据包向目的地发送
    • 在接收端,网络层从数据包中提取运输层报文段,并将其提交给上层运输层
    • 运输层处理接收到的报文段
    • 将报文段中的数据为接受应用进程使用

UPD和TCP提供的服务模型

  • 运输层的多路复用多路分解
  • 提供完整性检查

TCP额外提供的服务

  • 可靠数据传输服务
  • 拥塞控制

1. TCP和UDP的区别

TCP是一个面向连接的、可靠的、基于字节流的传输层协议。而UDP是一个面向无连接的传输层协议。

(1)面向连接服务

  • 面向连接服务:在进行报文流动前,TCP让客户和服务器互相进行交换运输层控制信息。
  • 这个所谓的握手过程提醒客户和服务器,让他们为大量的分组的到来做好准备
  • 一个TCP连接在两个进程的套接字之间建立后,双方可以在此连接上进行报文收发
  • 当报文放松结束时,必须拆除该连接。

(2)可靠数据传送服务

  • 通信进程之间依靠TCP能够无差错、按适当顺序交付所有的数据

(3)面向字节流

  • UDP 的数据传输是基于数据报的,这是因为仅仅只是继承了 IP 层的特性,而 TCP 为了维护状态,将一个个 IP 包变成了字节流。

2. TCP连接的三次握手

  • 客户端发送SYN,表明要向服务器建立连接。同时带上序列号ISN

  • 服务器返回ACK(序号为客户端序列号+1)作为确认。同时发送SYN作为应答(SYN的序列号为服务端唯一的序号)

  • 客户端发送ACK确认收到回复(序列号为服务端序列号+1)

    在这里插入图片描述

1、为什么是是三次握手不是两次或四次?

因为,tcp连接是全双工的,数据在两个方向上能同时传递。所以要确保双方,同时能发数据和收数据

  • 第一次握手:证明了发送方能发数据
  • 第二次握手:ack确保了接收方能收数据,syn确保了接收方能发数据
  • 第三次握手:确保了发送方能收数据
  • 四次握手浪费,两次握手不能保证“双方同时具备收发功能”

2、为什么客户端最后还要发送一次确认? 主要是为了防止已失效的连接请求报文段突然又传到了 服务器,因而产生错误。

3. TCP四次挥手

  • 主动关闭的一方发送FIN,表示要单方面关闭数据的传输

  • 服务端收到FIN后,发送一个ACK作为确认(序列号为收到的序列号+1)

  • 等服务器数据传输完毕,也发送一个FIN标识,表示关闭这个方向的数据传输

  • 客户端回复ACK以确认回复

    在这里插入图片描述

1、为什么挥手是四次而握手是三次?
TCP释放连接时之所以需要“四次挥手”,是因为FIN释放连接报文与ACK确认接收报文是分别由第二次和第三次"握手"传输的。为何建立连接时一起传输,释放连接时却要分开传输?

  • 建立连接时,被动方服务器端结束CLOSED阶段进入“握手”阶段并不需要任何准备,可以直接返回SYN和ACK报文,开始建立连接。
  • 释放连接时,被动方服务器,突然收到主动方客户端释放连接的请求时并不能立即释放连接,因为还有必要的数据需要处理,所以服务器先返回ACK确认收到报文,经过CLOSE-WAIT阶段准备好释放连接之后,才能返回FIN释放连接报文。

4. TCP可靠数据传输原理

(1) rdt1.0 协议

首先考虑最简单的情况,底层通信道是完全可靠的
在这里插入图片描述

  • 发送方

    • rdt_send(data):接收来自较高层的数据
    • make_pkt(data):产生一个包含该数据的分组
    • 将分组(packet)发送到信道中
  • 接收端

    • rdt_rcv:从底层信道接受一个分组
    • extract(packet, data):从分组中取出数据
    • deliver_data(data):将数据传给较高层

(2) rdt2.0 协议

第一种协议是在认为数据完全可靠下的情况,但是在实际模型中是分组中的比特可能受损的模型。
在这里插入图片描述

  • 发送方

    • 状态1:

      • 发送端协议等待来自上层传输下来的数据
      • rdt_send(data):接收来自较高层的数据,带有检验和
      • make_pkt(data):产生一个包含该数据的分组
      • udt_send(sndpkt):发送该分组
    • 状态2:

      • 等待来自接收方的ACK或NAK分组
      • rdt_rcv(rcvpkt) && isACK(rcvpkt):表示接收方已经正确接收分组,状态转换成等待上层调用
      • rdt_rcv(rcvpkt) &&isNAK(rcvpkt):表示上一个分组接收方的响应是重传,重新上传一遍分组并且等待和接收方回送的ACK和NAK
  • 接收方

    • 分组没有受损,返回ACK
    • 分组受损,返回NAK

(3) rdt2.1 协议

rdt2.0看似可以运行,但是有一个致命的缺陷,没有考虑到ACK或NAK受损的可能性。因此在rdt2.0基础上引入了序号。

发送方
在这里插入图片描述
接收方
在这里插入图片描述

(4)rdt 3.0

除了比特受损外,我们再考虑计算机网络中出现的底层信道丢包的情况。

设置一个倒技术定时器

  • 每次发送一个分组时便启动一个定时器
  • 响应定时器中断
  • 终止定时器

(5)流水线

rdt3.0是一个功能正确的传输协议,但是他的停等协议(等待接收方返回的ACK后才能进入等待上层调用的状态)的特殊性能也造成了效率较低的问题。

解决办法:不以停等的方式运行,允许发送方发送多个分组,无需等待确认。这种技术称为流水线
在这里插入图片描述

流水线带来的影响:

  • 必须增加序号范围,因为每个分组必须有唯一的标识符
  • 协议的发送发和接收方必须缓存多个分组
  • 所需序号范围对缓冲的要求取决于数据传输协议如何处理丢失、损坏以及延时过大的分组。解决流水线差错恢复的两种基本方法是:回退N步(GBN)和选择重传(SR)

(6)回退N步协议

回退N步协议(GBN协议,滑动窗口协议):允许发送发发送多个分组不需要等待确认,但是未确认的分组数不能超过某个最大值N。

设置N的原因:流量控制、拥塞控制

GBK协议响应的三种事件:

  • 发送方:

    • 当上层调用时

      • 窗口已满,告诉发送方等待一会
      • 窗口未满,产生一个分组并传送
    • 收到一个ACK

      • 窗口向右滑动
    • 超时事件

      • 如果收到一个ACK,但是前面的分组未被确认,重启定时器
  • 接收方:

    • 序号为n的分组被正确接收到,并且按序,为n发一个ACK
    • 其它所有情况,接收方丢弃该分组,并选择最近序列的分组重新发ACK

(8)选择重传协议(SR)

滑动窗口协议潜为了保证分组的正确顺序,对数据进行重传,但是考虑到窗口长度和带宽较大的情况,就会造成重复传递带来的效率问题。

选择重传:让发送发仅重传那些让它怀疑在接收方出错的分组,避免了不必要的重传。

(8)TCP中的可靠数据传输

因特网的网络层服务(IP服务)是不可靠的,即不保证数据交付、不保证数据包按序交付、不保证数据包的完整。TCP在IP不可靠尽力而为的服务至上创建了可靠数据传输服务,确保了数据传输到另一端的是无损坏、无间隙、非冗余且按序交付的。

我们将根据前面的原理来解释TCP如何实现可靠数据传输的:

  1. 如果来自下层的数据完全可靠,根据rdt1.0那么TCP协议只需要进行数据的传输即可。
  2. 但是很可惜,网络传输过程中往往有比特的损失,于是根据rdt2.0,加上了校验和确保了数据的正确性
  3. 看似上述协议已经完美,但是网络运输中还存在丢包的问题,根据rdt3.0,引入了计时器,当一个分组隔一段时间没有发过来,便重发一遍报文并重启计时器
  4. 但是计时器还存在一个问题,如果响应报文只是延迟传过来,怎么与其他报文进行区分呢?于是引入了序号。这样接收方就可以根据数据的字节编号,得出这些数据是接下来的数据,还是重传的数据。
  5. 根据rdt一些列的协议解决了可靠传输的问题,但是这是一种停等协议,就是说在传输的过程中,若未接收到报文的响应,上层应用就要一直等待,这样的工作效率太低。于是引入流水线的工作方式,运行多个报文发送,不用去等待响应报文后再继续发送。
  6. 网络中充斥着和发送数据包一样数据量的确认回复报文,因为每一个发送数据包,必须得有一个确认回复。提高网络效率的方法是:累积确认 。接收方不需要逐个进行回复,而是累积到一定量的数据包之后,告诉发送方,在此数据包之前的数据全都收到。例如,收到 1234,接收方只需要告诉发送方我收到4了,那么发送方就知道1234都收到了。
  7. 累计确认提高了网络效率,但是出现丢包的话采用的是GBN方法,即将从丢包的那个报文开始全部重传,这样做虽然保证了报文的有序性,但是一旦带框和流量大的话就会造成严重的资源浪费。所以在TCP报文的选项字段,可以设置已经收到的报文段,每一个报文段需要两个边界来进行确定。这样发送方,就可以根据这个选项字段只重传丢失的数据了。这种方法看起来很像SR协议,所以我们说TCP协议的可靠数据传输的差错恢复机制是GBN协议和SR协议的混合体。
  8. 发送是否可以无限发送直到把缓冲区所有数据发送完?不可以。因为需要考虑接收方缓冲区以及读取数据的能力。如果发送太快导致接收方无法接受,那么只是会频繁进行重传,浪费了网络资源。所以发送方发送数据的范围,需要考虑到接收方缓冲区的情况。这就是TCP的流量控制 。解决方法是:滑动窗口 。

5. TCP流量控制

对于发送端和接收端而言,TCP 需要把发送的数据放到发送缓存区, 将接收的数据放到接收缓存区。而流量控制要做的事情,就是在通过接收缓存区的大小,控制发送端的发送。如果对方的接收缓存区满了,就不能再继续发送了。

要具体理解流量控制,首先需要了解滑动窗口的概念。

TCP 滑动窗口分为两种: 发送窗口接收窗口

发送窗口

发送端的滑动窗口结构如下:

其中包含四大部分:

  • 已发送且已确认
  • 已发送但未确认
  • 未发送但可以发送
  • 未发送也不可以发送

其中有一些重要的概念,我标注在图中:

发送窗口就是图中被框住的范围。SND 即send, WND 即window, UNA 即unacknowledged, 表示未被确认,NXT 即next, 表示下一个发送的位置。

接收窗口

接收端的窗口结构如下:

REV 即 receive,NXT 表示下一个接收的位置,WND 表示接收窗口大小。

流量控制过程

6. TCP拥塞控制

上一节所说的流量控制发生在发送端跟接收端之间,并没有考虑到整个网络环境的影响,如果说当前网络特别差,特别容易丢包,那么发送端就应该注意一些了。而这,也正是拥塞控制需要处理的问题。

对于拥塞控制来说,TCP 每条连接都需要维护两个核心状态:

  • 拥塞窗口(Congestion Window,cwnd)
  • 慢启动阈值(Slow Start Threshold,ssthresh)

涉及到的算法有这几个:

  • 慢启动
  • 拥塞避免
  • 快速重传和快速恢复

接下来,我们就来一一拆解这些状态和算法。首先,从拥塞窗口说起。

拥塞窗口

拥塞窗口(Congestion Window,cwnd)是指目前自己还能传输的数据量大小。

那么之前介绍了接收窗口的概念,两者有什么区别呢?

  • 接收窗口(rwnd)是接收端给的限制
  • 拥塞窗口(cwnd)是发送端的限制

限制谁呢?

限制的是发送窗口的大小。

有了这两个窗口,如何来计算发送窗口

发送窗口大小 = min(rwnd, cwnd)

取两者的较小值。而拥塞控制,就是来控制cwnd的变化。

慢启动

刚开始进入传输数据的时候,你是不知道现在的网路到底是稳定还是拥堵的,如果做的太激进,发包太急,那么疯狂丢包,造成雪崩式的网络灾难。

因此,拥塞控制首先就是要采用一种保守的算法来慢慢地适应整个网路,这种算法叫慢启动。运作过程如下:

  • 首先,三次握手,双方宣告自己的接收窗口大小
  • 双方初始化自己的拥塞窗口(cwnd)大小
  • 在开始传输的一段时间,发送端每收到一个 ACK,拥塞窗口大小加 1,也就是说,每经过一个 RTT,cwnd 翻倍。如果说初始窗口为 10,那么第一轮 10 个报文传完且发送端收到 ACK 后,cwnd 变为 20,第二轮变为 40,第三轮变为 80,依次类推。

难道就这么无止境地翻倍下去?当然不可能。它的阈值叫做慢启动阈值,当 cwnd 到达这个阈值之后,好比踩了下刹车,别涨了那么快了,老铁,先 hold 住!

在到达阈值后,如何来控制 cwnd 的大小呢?

这就是拥塞避免做的事情了。

拥塞避免

原来每收到一个 ACK,cwnd 加1,现在到达阈值了,cwnd 只能加这么一点: 1 / cwnd。那你仔细算算,一轮 RTT 下来,收到 cwnd 个 ACK, 那最后拥塞窗口的大小 cwnd 总共才增加 1。

也就是说,以前一个 RTT 下来,cwnd翻倍,现在cwnd只是增加 1 而已。

当然,慢启动拥塞避免是一起作用的,是一体的。

快速重传

在 TCP 传输的过程中,如果发生了丢包,即接收端发现数据段不是按序到达的时候,接收端的处理是重复发送之前的 ACK。

比如第 5 个包丢了,即使第 6、7 个包到达的接收端,接收端也一律返回第 4 个包的 ACK。当发送端收到 3 个重复的 ACK 时,意识到丢包了,于是马上进行重传,不用等到一个 RTO 的时间到了才重传。

这就是快速重传,它解决的是是否需要重传的问题。

选择性重传

那你可能会问了,既然要重传,那么只重传第 5 个包还是第5、6、7 个包都重传呢?

当然第 6、7 个都已经到达了,TCP 的设计者也不傻,已经传过去干嘛还要传?干脆记录一下哪些包到了,哪些没到,针对性地重传。

在收到发送端的报文后,接收端回复一个 ACK 报文,那么在这个报文首部的可选项中,就可以加上SACK这个属性,通过left edgeright edge告知发送端已经收到了哪些区间的数据报。因此,即使第 5 个包丢包了,当收到第 6、7 个包之后,接收端依然会告诉发送端,这两个包到了。剩下第 5 个包没到,就重传这个包。这个过程也叫做选择性重传(SACK,Selective Acknowledgment) ,它解决的是如何重传的问题。

快速恢复

当然,发送端收到三次重复 ACK 之后,发现丢包,觉得现在的网络已经有些拥塞了,自己会进入快速恢复阶段。

在这个阶段,发送端如下改变:

  • 拥塞阈值降低为 cwnd 的一半
  • cwnd 的大小变为拥塞阈值
  • cwnd 线性增加

以上就是 TCP 拥塞控制的经典算法: 慢启动拥塞避免快速重传和快速恢复

应用层

应用层我们主要关注HTTP协议,HTTP使用TCP协议作为他的支撑运输协议

  • 客户发起一个与服务器的连接
  • 一旦连接建立,浏览器和服务器间的进程可以通过套接字接口来访问TCP
  • 客户通过套接字发送HTTP请求报文,从套接字接收HTTP响应报文
  • TCP提供可靠的数据传输服务
  • 服务器的响应报文完整的回到客户端

注意:HTTP是一个无状态协议,服务器向客户发送被请求的文件,不存储任何关于该客户的状态信息

1. HTTP报文格式

在这里插入图片描述

访问 hackr.jp 时,请求报文的首部信息

在这里插入图片描述

(1)HTTP请求报文

在这里插入图片描述

  • 请求行:请求方法 + URI + 协议版本

    • 请求方法:GET、POST、PUT、HEAD、OPTIONS、TRACT、CONNECT、LINK、UNLINK

      方法说明支持的HTTP版本
      Get获取资源1.0、1.1
      POST传输实体主体1.0、1.1
      PUT传输文件1.0、1.1
      HEAD获取报文首部1.0、1.1
      DELETE删除文件1.1
      OPTIONS询问支持的方法1.1
      TRACK追踪路径1.1
      CONNECT要求使用隧道协议连接代理1.1
      LINK建立和资源之间的联系1.0
      UNLINK断开连接关系1.0
    • URI
      在这里插入图片描述

      • 协议:http和https
      • 登录信息:可选,指定用户名和密码作为从服务器端获取资料的登录信息
      • 服务器地址:常见的URL,通过DNS解析成主机唯一的IP地址
      • 端口号:访问服务器的套接字,web服务器默认端口号是80
      • 带层次的文件路径:指定服务器上的特定文件路径获取资源
      • 查询字符:可选,对于已指定的文件路径内的资源可以使用查询字符串
      • 片段标识符:可选,标记出已获取资源中的子资源
    • 协议版本:http0.9、http1.0、http1.1

  • 首部字段:见下

  • 报文实体内容

2.HTTP响应报文

在这里插入图片描述

  • 状态行:协议版本 + 状态码 + 状态码的原因短语

    • 协议版本:http0.9、http1.0、http1.1

    • 状态码:
      在这里插入图片描述

      • 200 ok:正常处理了
      • 204 No Content:接受的请求已经成功处理,但是返回的响应报文中不含实体的主体部分
      • 206 Partial Content:该状态码表示客户端进行了范围请求,而服务器成功执行了这部分的GET请求
      • 301 Moved Permanently:永久性重定向,表示请求得资源已经被分配了新的URI
      • 302 Found:临时性重定向,表示请求得资源已经分配了新的URI
      • 303 See Other:请求的资源存在着另一个URI,应使用GET方法定向获取请求的资源
      • 304 Not Modified:服务器允许访问资源,但是未满足条件的情况
      • 307 Temporary Redirect:临时重定向
      • 400 Bad Request:请求报文中存在着语法错误
      • 401 Unauthorized:需要有通过HTTP认真的认证信息
      • 403 Forbidden:请求资源被服务器拒绝了
      • 404 Not Found:服务器上无法找到请求的资源
      • 500 Internal Server Error:服务器在执行请求时出现了错误
      • 503 Service Unavailable:服务器超负荷或正在进行停机维护
  • 首部字段:见下

  • 主体

3.首部字段

  • 通用首部字段
    在这里插入图片描述
  • 请求首部字段
    在这里插入图片描述
  • 响应首部字段
    在这里插入图片描述
  • 实体首部字段
    在这里插入图片描述

2. get和post的区别

你敢在post和get上刁难我,就别怪我装逼了

  • get用来获取数据,post用来提交数据
  • get参数有长度限制(受限于url长度,具体的数值取决于浏览器和服务器的限制,最长2048字节),而post无限制。
  • get请求的数据会附加在url之 ,以 " ? "分割url和传输数据,多个参数用 "&"连接,而post请求会把请求的数据放在http请求体中。
  • get是明文传输,post是放在请求体中,但是开发者可以通过抓包工具看到,也相当于是明文的。
  • get请求会被浏览器主动cache,而post不会,除非手动设置。

3. 持续连接和非持续连接

从 Http0.9 到 Http2 要发送多个请求,从多个 Tcp 连接=>keep-alive=>管道化=>多路复用不断的减少多次创建 TCP 等等带来的性能损耗。

(1)非持续连接的HTTP

客户和服务器间的每一个请求/响应都用一个单独的TCP连接发送
在这里插入图片描述

缺点:

  • 必须为每一个请求对象建立一个全新的连接,客户和服务器中都要分配TCP缓冲区和保持TCP变量,给服务器带来严重负担
  • 每一个对象都要经受两倍RTT的交付时延,一个RTT用于建立TCP,一个用于请求和接收一个对象

(2)持续连接的HTTP

持久连接(HTTP Persistent Connections,也称HTTP keep-alive)

在采用HTTP1.1持续连接的情况下,服务器在发送响应后保持TCP连接的打开,后续的请求报文和响应报文能够通过相同的TCP连接进行传送。
在这里插入图片描述

管线化

持久的连接使得管线化成为可能——不需要等待下一个请求得到响应就可以进行下一次请求

多路复用

HTTP 传输是基于请求-应答的模式进行的,报文必须是一发一收,但值得注意的是,里面的任务被放在一个任务队列中串行执行,一旦队首的请求处理太慢,就会阻塞后面请求的处理。这就是著名的HTTP队头阻塞问题。

多路复用代替原来的序列和阻塞机制。所有就是请求的都是通过一个 TCP 连接并发完成。因为在多路复用之前所有的传输是基于基础文本的,在多路复用中是基于二进制数据帧的传输、消息、流,所以可以做到乱序的传输。多路复用对同一域名下所有请求都是基于流,所以不存在同域并行的阻塞。多次请求如下图:

http2.0

4. http各版本的区别

HTTP各版本特性及区别

HTTP 1.0:

  • 任意数据类型都可以发送
  • 有GET、POST、HEAD三种方法
  • 无法复用TCP连接(长连接)
  • 有丰富的请求响应头信息。以header中的Last-Modified/If-Modified-SinceExpires作为缓存标识

HTTP 1.1:

  • 引入更多的请求方法类型PUTPATCHDELETEOPTIONSTRACECONNECT
  • 引入长连接,就是TCP连接默认不关闭,可以被多个请求复用,通过请求头connection:keep-alive设置
  • 引入管道连接机制,可以在同一TCP连接里,同时发送多个请求
  • 强化了缓存管理和控制Cache-ControlETag/If-None-Match
  • 支持分块响应,断点续传,利于大文件传输,能过请求头中的Range实现
  • 使用了虚拟网络,在一台物理服务器上可以存在多个虚拟主机,并且共享一个IP地址

HTTP 2:

  • 二进制分帧:不再是纯文本,避免文本歧义,缩小了请求体积
  • 服务器推送:服务器可以额外的向客户端推送资源,而无需客户端明确的请求
  • 多路复用: 在共享TCP链接的基础上同时发送请求和响应
  • 增加了安全性,使用HTTP 2.0,要求必须至少TLS 1.2
  • 使用HPACK算法将头部压缩,用哈夫曼编码建立索表,传送索引大大节约了带宽