📕
余烬的小册
数据结构与算法GitHub
  • 总述
  • 经验记录
    • 经验总结
      • web component
      • 前端性能优化总结与分析
      • 我的长列表优化方案
      • 双向通讯解决方案
      • 🔧基于istanbul实现代码测试覆盖率工具
      • 表单系统(低代码表单)
      • 跨端小程序
      • 设计一个即时聊天功能
      • 跨页面通讯 3658699fe4cb4d0bbe22b0881390bacd
    • 踩坑记录
      • HTML踩坑记录
      • Flutter踩坑记录
      • CSS踩坑记录
  • 源码解析
    • Vue源码解析
      • Vue2源码解析系列-响应式原理
      • Vue2源码解析系列-模板编译
      • Vue2源码解析系列-渲染系统(待更新)
        • Patch
      • Vue2源码解析系列-调度系统(todo)
      • Vue2组件更新流程(todo)
      • 如何学习Vue源码
      • Vue3源码解析系列-响应系统
      • Vue3源码解析系列-渲染系统
      • Vue3源码解析系列-组件化和渲染优化(todo)
      • Vue router源码解析(todo)
    • React源码解析(todo)
    • 微前端
      • qiankun源码解析(todo)
    • Vite源码解析
      • Vite Client源码
      • Vite Server源码(todo)
  • 前端技术
    • javaScript
      • ES6
        • 变量声明
        • 模块化
        • 箭头函数
        • 你不知道的for...of
        • 新的数据结构Set和Map
        • JavaScript异步编程终极解决方案
        • ES6 Class 3a0c0a225a534984aabe9a943c5df975
      • JavaScript Error
      • JavaScript浅拷贝和深拷贝
      • JavaScript闭包
      • JavaScript最佳实践
      • JavaScript设计模式
      • async函数的polyfill
    • 深入理解JavaScript系列
      • JavaScript中的继承
      • JavaScript原始类型和引用类型
      • JavaScript浅拷贝和深拷贝
      • JavaScript手写系列
      • JavaScript之this
      • 词法环境和环境记录
      • JavaScript内存泄漏
      • 执行上下文
      • 从ECMAScript规范中学习this
    • TypeScript
      • TypeScript基础教程
      • Typescript高级操作
      • TypeScript工具类型
      • Typescript手写实现工具类型
      • Typescript总结(思维导图)
    • 浏览器原理
      • 页面渲染原理
      • 浏览器存储
      • JavaScript事件循环
      • 事件循环
      • 跨域
      • DOM事件流
      • 从输入url到页面渲染
      • 判断节点之间的关系及根据节点关系查找节点
      • history API
    • 跨端技术
      • Flutter
        • Flutter布局组件
    • 前端工程化
      • Babel插件开发指南
      • 循环依赖
      • pm2
    • React
      • React 状态管理
      • React组件通讯
      • Redux入门
      • Flux
      • React Hook(todo)
      • Effect
  • 服务器端
    • 计算机网络
      • 应用层
      • 运输层
      • 物理层
      • 数据链路层
      • HTTP缓存
      • HTTPS
      • 网络层
    • NodeJs
      • Node.js
      • nodejs最佳实践
      • 《深入浅出Nodejs》小结
      • mongoose填充(populate)
      • node事件循环
      • Node子进程
      • nestjs从零开始
      • nodejs流
      • Nodejs调试
      • Koa源码解析
    • 服务器
      • 操作系统
      • Linux
      • nginx常用指令
      • nginx常用配置
    • 数据库
      • Mysql常见语法
      • MongoDB Indexes索引
  • 前端安全与性能优化
    • 前端安全
      • 跨站脚本攻击(XSS)
      • 跨站点请求伪造(CSRF)
      • 点击劫持
      • 中间人攻击
      • 越权攻击与JWT
    • 前端性能优化
      • 前端监控系统
      • 前端性能优化总结与分析 7348bba0918645b1899006dc842a64c1
      • 衡量性能的核心指标 0dc15ef127cf4f4a9f1137c377420292
      • 图片懒加载
  • 杂项
    • 其他
      • Git
      • web component框架
      • 实现滚动框的懒加载
      • Stencil指南
    • CSS
      • 定位和层叠上下文
      • BFC
      • 盒模型
      • css选择器
      • css变量
由 GitBook 提供支持
在本页
  • 概述
  • 协议端口号
  • UDP 用户数据报协议
  • TCP 传输控制协议
  • tcp的可靠传输原理
  • TCP流量控制
  • 拥塞控制
  • tcp的连接与断开
在GitHub上编辑
  1. 服务器端
  2. 计算机网络

运输层

上一页应用层下一页物理层

最后更新于3年前

概述

运输层是面向通讯的最高层,同时也是用户功能中的最底层,它向上层应用层提供服务。

端对端的通讯本质上其实是一台主机的应用进程对另外一台主机的应用进程进行通讯,也就是说,端对端的通讯其实是应用进程之间的通讯。而运输层的作用就是为应用进程之间提供端到端的逻辑通信

运输层有着两种传输协议: 面向连接的TCP和面向无连接的UDP。

用UDP和TCP协议的各种应用和应用层协议

协议端口号

协议端口号简称端口号。

前面说过运输层是为应用进程提供端对端的服务,但是指明目的进程进行传输是不可能的,因为进程的创建和撤销是动态的,发送方无法预料发送数据后的情况。解决这个问题的方法是使用协议端口号,它的过程是:发送方将报文交付到指定的端口即可,后面的上交报文到应用进程由TCP完成。也就是说,两个计算机中的进程要互相通信,不仅必须知道对方的IP地址(为了找到对方的计算机),而且还要知道对方的端口号(为了找到对方计算机中的应用进程)。

UDP 用户数据报协议

  • UDP是一种面向无连接的协议,也就是说它不会事先建立连接,这减小了性能开销和时延

  • 它会尽最大努力传送数据,即不保证可靠传输。

  • 面向报文,即无论应用层交下来多大的报文,UDP即不会拆分也不会合并,而是加上UDP首部后就发送。

  • UDP没有拥塞控制,也就是说无论网络是否拥塞,发送方的发送速率不会改变

  • UDP支持一对一、一对多、多对一、多对多

  • UDP首部开销小,只有8个字节。

UDP的首部(每个字段都是两个字节)

  • 源端口

  • 目的端口

  • 长度,即UDP整体长度

  • 检验和检测UDP用户数据报在传输中是否有错。有错就丢弃。

TCP 传输控制协议

TCP是运输层的另一种传输协议。它具有以下特点

  • 面向连接,也就是说在传输数据前会事先建立连接

  • 只支持一对一

  • 提供可靠交付,能保证数据无差错、不丢失、不重复。

  • 提供全双工通信,通讯双方在任何时候都能发送数据

  • 面向字节流。“面向字节流”的含义是:虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序交下来的数据看成仅仅是一连串的无结构的字节流。

tcp的可靠传输原理

理想的传输条件有以下两个特点:

(1) 传输信道不产生差错。

(2) 不管发送方以多快的速度发送数据,接收方总是来得及处理收到的数据。

然而实际的网络都不具备以上两个理想条件。但我们可以使用一些可靠传输协议,当出现差错时让发送方重传出现差错的数据,同时在接收方来不及处理收到的数据时,及时告诉发送方适当降低发送数据的速度。这样一来,本来是不可靠的传输信道就能够实现可靠传输了。

停止等待协议+超时重传

发送方每次发送一个报文段,就停止等待一段时间,接收方在接收到报文段后发送一个确认,发送方接收到确认后再发送下一个报文段,这就是停止等待协议。

发送方只要超过了一段时间仍然没有收到确认,就认为刚才发送的分组丢失了,因而重传前面发送过的分组。这就叫做超时重传。要实现超时重传,就要在每发送完一个分组设置一个超时计时器。如果在超时计时器到期之前收到了对方的确认,就撤销已设置的超时计时器。

如果接收方接收到重复的报文段,这可能是报文段传输时间太长,导致发送方误以为报文段已经丢失而重新发送,或者是接收方发送的确认丢失了,这时接收方会做两件事:

  1. 丢弃这个重复的分组M1,不向上层交付。

  2. 向发送方发送确认。因为A之所以重传M1就表示A没有收到对M1的确认。

像上述的这种可靠传输协议常称为自动重传请求ARQ。意思是重传的请求是自动进行的。接收方不需要请求发送方重传某个出错的分组。

流水线传输

为了提高传输效率,发送方可以不使用低效率的停止等待协议,而是采用流水线传输。流水线传输就是发送方可连续发送多个分组,不必每发完一个分组就停顿下来等待对方的确认。

流水线传输需要使用到连续自动重传请求协议和滑动窗口协议

连续自动重传请求协议

连续ARQ协议规定,发送方每收到一个确认,就把发送窗口向前滑动一个分组的位置。

接收方一般都是采用累积确认的方式。这就是说,接收方不必对收到的分组逐个发送确认,而是在收到几个分组后,对按序到达的最后一个分组发送确认,这就表示:到这个分组为止的所有分组都已正确收到了。累积确认有优点也有缺点。优点是:容易实现,即使确认丢失也不必重传。但缺点是不能向发送方反映出接收方已经正确收到的所有分组的信息。

TCP 滑动窗口

窗口是缓存的一部分,用来暂时存放字节流。发送方和接收方各有一个窗口,接收方通过 TCP 报文段中的窗口字段告诉发送方自己的窗口大小,发送方根据这个值和其它信息设置自己的窗口大小。

发送窗口内的字节都允许被发送,接收窗口内的字节都允许被接收。如果发送窗口左部的字节已经发送并且收到了确认,那么就将发送窗口向右滑动一定距离,直到左部第一个字节不是已发送并且已确认的状态;接收窗口的滑动类似,接收窗口左部字节已经发送确认并交付主机,就向右滑动接收窗口。

接收窗口只会对窗口内最后一个按序到达的字节进行确认,例如接收窗口已经收到的字节为 {31, 34, 35},其中 {31} 按序到达,而 {34, 35} 就不是,因此只对字节 31 进行确认。发送方得到一个字节的确认之后,就知道这个字节之前的所有字节都已经被接收。

TCP流量控制

流量控制是为了控制发送方发送速率,保证接收方来得及接收。TCP的流量控制借助滑动窗口实现。

接收方发送的确认报文中的窗口字段可以用来控制发送方窗口大小,从而影响发送方的发送速率。将窗口字段设置为 0,则发送方不能发送数据。

拥塞控制

如果网络出现拥塞,分组将会丢失,此时发送方会继续重传,从而导致网络拥塞程度更高。因此当出现拥塞时,应当控制发送方的速率。这一点和流量控制很像,但是出发点不同。流量控制是为了让接收方能来得及接收,而拥塞控制是为了降低整个网络的拥塞程度。

TCP 主要通过四个算法来进行拥塞控制:慢开始、拥塞避免、快重传、快恢复。

为了集中精力讨论拥塞控制,我们假定:

(1) 数据是单方向传送,而另一个方向只传送确认。

(2) 接收方总是有足够大的缓存空间,因而发送窗口的大小由网络的拥塞程度来决定。

慢开始和拥塞避免

慢开始

发送方维持一个拥塞窗口的状态变量,发送放让自己的发送窗口等于或小于拥塞窗口。

慢开始的思路是当刚开始发送数据的时候,由于不清楚网络拥塞情况,因此应当由慢变快地发送数据,也就是有小到大地逐渐增加拥塞窗口。每经过一个传输轮次(即将发送窗口所有报文段都发送了且都收到了确认,即一个往返时间RTT),拥塞窗口cwnd就加倍。

拥塞避免

拥塞避免的思路是让拥塞窗口缓慢地增大,每经过一个往返时间RRT就把拥塞窗口加1,而不是加倍,这样可以让拥塞窗口按线性规律缓慢增长。

慢开始门限

为了防止拥塞窗口cwnd增长过大引起网络拥塞,还需要设置一个慢开始门限的状态变量。

  • 当cwnd < ssthresh时,使用上述的慢开始算法。

  • 当cwnd > ssthresh时,停止使用慢开始算法而改用拥塞避免算法。

  • 当cwnd = ssthresh时,既可使用慢开始算法,也可使用拥塞避免算法。

网络拥塞时

无论是慢开始阶段还是拥塞避免阶段,只要发送方发现网络拥塞(根据有没有按时收到确认),就会把慢开始门限设为原来的一半,然后把拥塞窗口重新设为1,执行慢开始算法。

快重传

快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认(为的是使发送方及早知道有报文段没有到达对方)而不要等待自己发送数据时才进行捎带确认,发送方只要一连收到三个重复确认就应当立即重传对方尚未收到的报文段,而不必继续等待为设置的重传计时器到期。由于发送方能尽早重传未被确认的报文段,因此采用快重传后可以提高网络的吞吐量。

快恢复

与快重传配合使用的还有快恢复算法,其过程有以下两个要点:

  1. 当发送方连续收到三个重复确认时,就执行“乘法减小”算法,把慢开始门限ssthresh减半。这是为了预防网络发生拥塞。请注意,接下去不执行慢开始算法。

  2. 由于发送方现在认为网络很可能没有发生拥塞(如果网络发生了严重的拥塞,就不会一连有好几个报文段连续到达接收方,也就不会导致接收方连续发送重复确认),因此与慢开始不同之处是现在不执行慢开始算法(即拥塞窗口cwnd现在不设置为1),而是把cwnd值设置为慢开始门限ssthresh减半后的数值,然后开始执行拥塞避免算法(“加法增大”),使拥塞窗口缓慢地线性增大。

在采用快恢复算法时,慢开始算法只是在TCP连接建立时和网络出现超时时才使用。

tcp的连接与断开

tcp连接的建立

端点

每条TCP连接都有两个端点,这两个端点即不是应用进程也不是端口,而是由ip地址+端口号组成的套接字(或插口)。例如192.168.0.1:80就是一个套接字。每条TCP连接唯一地被通讯两段的端点所确定。

三次握手

三次握手过程

  1. 客户端选择一个初始序列x,发送一个SYN同步报文段给服务器。不能携带数据,会消耗一个序号。

  2. 服务器接收后,如果同意建立连接,会返回一个SYN-ACK报文段,ack设为x+1,同时也选择一个初始序号y。不能携带数据,会消耗一个序号。

  3. 客户端接收后发送SYN-ACK消息,ack确认序号设为y+1,序号seq设为x+1。可以携带数据也可以不携带,携带数据才会消耗序号。

三次握手的作用

  1. 第一次是验证客户端的发送能力。

  2. 第二次是验证服务器的发送能力和接收能力

  3. 第三次是验证客户端的接收能力,同时也避免建立"已失效的请求连接"

为什么需要第三次握手

第三次握手一是可以验证客户端的接收能力,二是可以避免建立"已失效的请求连接"。"已失效的请求连接"是指发送方发送的tcp连接建立请求由于网络拥塞等情况,很长时间后才到达接收方,如果没有第三次握手,服务器则直接会建立一条无用连接。

为什么只有第三次握手才允许携带数据

第一二次握手时不能确定对方身份,如果能携带数据易被攻击,第三次握手时tcp连接已经建立,可以传送数据了。

TCP连接的释放

四次挥手

四次挥手过程:

  1. 客户端主动发送FIN连接释放报文段,其序号seq = u,它等于前面已传送过的数据的最后一个字节的序号加1。不携带数据,消耗一个序号

  2. 服务器返回ACK确认报文段,确认序号ack=u+1,序号是v,等于B前面已传送过的数据的最后一个字节的序号加1。此时整个连接处于半关闭状态,客户端已经没有数据要发送了,但是仍然要接收服务器发送的报文段。

  3. 服务器发送完所有报文段后,发送FIN-SYN,序号是最后发送的那一个字节的序号w,ack是u+1

  4. 客户端发送ACK确认报文段,序号是u+1,ack是w+1.

服务器接收ACK报文段后立即关闭连接,而客户端等待一段时间(2MSL)后再断开。

四次挥手的作用

  1. 第一次告知服务器要断开连接,此时客户端已经发送完所有的报文段

  2. 第二次服务器确认收到断开连接的请求,但是此时服务器可能还有报文段没有发送完

  3. 第三次服务器已经发送完所有的报文段,告知客户端可以释放连接了

  4. 客户端确认,tcp连接释放。

为什么第四次挥手要等待一段时间才关闭

第一,为了保证客户端发送的最后一个ACK报文段能到达服务器,如果发送丢失,还有留时间重传。

第二,防止之前提到的“已失效的连接请求报文段”出现在本连接中。A在发送完最后一个ACK报文段后,再经过时间2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。

image-20211024102431840
image-20211024121833800
image-20211024123736380
image-20211024161827087
image-20211024162802800
image-20211024163539813
image-20211024171756397
img