TCP协议及特性详解

2023-11-05

TCP

TCP 协议是一个有连接, 可靠传输, 面向字节流, 全双工的传输层通信协议

相比 UDP , TCP 最大的差异和优势无疑是可靠传输, 而为了保证可靠传输, TCP 协议中有两个核心的机制, 确认应答超时重传

确认应答

发送方在发送完数据之后, 为了确认接收方是否收到了数据, 接收方会返回一个应答报文, 表示自己已经收到了数据。那怎么判断这是个普通的数据报还是一个应答报文呢 ? 这就涉及到了 TCP 报头结构, 如下, TCP 报头中有 6 位比特位, 其中第二个比特位 ACK 就用来表示这是不是应答报文, 如果 ACK 为 1 说明这是个应答报文 (也称为 ACK 报文, 后面统称应答报文为ACK报文), 相反为 0 则不是。

在这里插入图片描述

如果发送方一次性发送了多组数据, 怎么判断 ACK 接收了哪组数据呢

在这里插入图片描述

如下, TCP 报头中有个32位序号, TCP 会给数据报中数据的每一个字节进行编号, 而由于一个 TCP 数据报只能记录一个序号, 所以这个序号就是字节流的起始序号, 而确认序号就是接收方期望从发送方接收到的下一个字节的序号, 例如, 发送方发送了一个序号为1, 长度为 1000 的数据报, 也就是发送了序号为 1 - 1000 的数据, 接收方期望下次接收到的字节序号为1001, 所以此时确认序号就是 1001, 确认序号就是表明当前序号之前的数据都收到了, 而发送方下次发送的数据也应该从1001开始。需要注意, 确认序号只有当 ACK 为1 的时候有效, 也就是应答报文。

在这里插入图片描述

网络中数据传输中又是充满不确定性的, 晚发的数据先到达的情况也经常出现, 那么如何保证数据的有序性呢 ? 实际上接收方还会根据序号对TCP数据报进行排序。

超时重传

由于网络环境复杂, 数据传输过程中难免遇到意外, 每一次数据传输都是由可能丢失的, 如果数据发送后丢失了(丢包)怎么办?

每次数据传输如果成功, 都应收到 ACK , 如果发送方发送完数据, 等待一段时间后还没有收到 ACK , 就会触发超时重传机制, 重新发送一次数据, 连续触发超时重传都会让等待时间增加。超过一定次数后还没收到 ACK 后, 就会断开连接, 尝试重新连接, 如果还连不上, 说明现在网络情况糟糕, 于是放弃传输数据。

具体来说会有两种情况: (1) 发送方发送的数据丢了 (2) 发送方发送的数据到了, 但是接收方返回的 ACK 丢了。
如果发送的数据丢了, 重新发一次即可, 但是如果发送的数据到了, ACK 丢了, 这种情况会超时重传一次, 不过这样会收到两份一样的数据, 接收方就会根据数据报序号去重, 保证没有两个一样的数据报

那么如何保证数据的正确性呢?
这和 UDP 协议中的校验和是一样的道理, TCP 数据报的每一次转发, 发送前都会计算校验和, 并将这个结果一起发送出去, 到达某台设备后都会重新计算校验和, 如果不相同, 那就直接丢包来保证数据不能是错误的。而丢包带来的结果就是发送方不会收到 ACK 而超时重传, 利用时间换取数据的准确。

连接建立与断开

为了保证可靠性传输, TCP 在真正传输数据之前应该建立连接, 数据传输完成后断开连接, 建立连接也就是"三次挥手", 断开连接即"四次挥手"

三次挥手

  1. 首先客户端(A)会发送给服务器(B)一个连接请求(SYN, 是一个同步报文段, 表示建立连接)
  2. B 接收到SYN, 返回ACK (复习一下, ACK 就是应答数据报)
  3. B 也给 A 发送建立连接的请求 (SYN)
  4. A 返回 ACK
    在这里插入图片描述

虽然有四个步骤, 但是 2 和 3 步骤是可以合并的, B 在收到 A 的 SYN 后, 就会在操作系统内核一起将 ACK 和 SYN 一并发送出去。具体点, 在代码中, new Socket(IP, port) , 就会触发三次握手建立连接。同时 SYN 也是这 6 个比特位中的一个。

在这里插入图片描述

这三次握手除了建立连接之外, 也在判断通信双方的发送能力和接受能力是否正常, 通信链路是否正常, 除此之外, 也在调整通信中重要的参数, 例如序号: 序号要从哪里开始

四次挥手

  1. A 向 B 发送 FIN (FIN 也是TCP报头中 6 个比特位中的一个, 表示关闭连接的请求)
  2. B 接收到了 FIN, 返回 ACK
  3. B 向 A 发送 FIN
  4. A 返回 ACK
    在这里插入图片描述

需要注意的是 : 接收方接收到 FIN 后, 操作系统内核就会立刻返回 ACK, 而 FIN 是代码中的行为, 具体点就是 Socket.close(); 当代码执行到上述语句后, 才会发送 FIN

四种常见状态

  1. LISTEN
    表示服务器的状态——服务器已经准备好了, 具体到代码中就是 new ServerSocket(port)

  2. ESTABLISHED
    表示客户端和服务器连接建立完成, 可以随时准备通信

  3. CLOSE_WAIT
    最先收到 FIN 的一方会进入这个状态, 收到 FIN , 并且返回 ACK后, 进入CLOSE_WAIT状态, 并且等待 Socket.close() 的调用, 执行到这个语句之后就会发送 FIN
    在这里插入图片描述

  4. TIME_WAIT
    如下, 在最后一次收到 FIN 并返回 ACK 之后, 进入TIME_WAIT状态, 然后会在这里等待一段时间后进入 CLOSED 状态, 彻底结束本次通信。而等待的时间是 2 MSL, MSL —— 数据从一个主机传输到另一个主机所要花费的最大时间。这样做的好处是如果最后一个 ACK 没有正常到达, 有时间超时重传, 确保 B 也能正常关闭
    在这里插入图片描述

效率提升机制

滑动窗口

TCP 为了保证可靠性, 速度肯定会受此影响, 而希望能够弥补为了可靠性牺牲的速度, 引入了滑动窗口

相比发送一条数据, 收到ACK后发送下一条, 滑动窗口可以一次性发送 N 条数据报(N 可以视为窗口的大小), 收到 M 条 ACK 的应答后, 窗口向右移动 M 个位置, 并继续发送窗口中没有发送的数据。这样就可以做到将多个 ACK 的等待时间重叠在一起, 降低等待总时间, 提升发送效率

如下, 假设发送方发送的数据和序号如下, 窗口内的数据报如果收到ACK就表明完成了传输, 窗口就可以移动继续传输其他数据报

在这里插入图片描述连续发送多条数据后, 数据最终都会在接收缓冲区中按照数据报的序号进行排序, 保证有序性

那如果这期间发生了某些意外怎么办 ? 比如 (1) 数据报正常到达, ACK 丢失 (2) 数据报丢失

(1) 数据报到达, ACK 丢失

如下图, 假设发送方发送的数据和序号如下, 虽然2001-3000, 和3001-4000的数据发到了, 但是确认序号为3001和4001的ACK丢失了, 这时候会重新发送2001-3000和3001-4000的数据吗? 不会 ! 因为发送方收到了确认序号为5001的应答报文, 而应答报文表示, 这个确认序号之前的数据都接收到了, 即2001-4000都收到了
在这里插入图片描述
(2) 数据报丢失

例如, 2001-3000的数据报丢失

在这里插入图片描述接收方返回2001的ACK后, 接收方下一个接收的序号就应该从2001开始, 但是2001-3000的包丢了, 后面发送的数据的序号都不是2001开始的, 接收方就会连续多次发送 2001 的ACK报文, 发送方收到了三个2001的ACK后就会重传2001-3000的数据报, 如下图
在这里插入图片描述
重传完2001-3000的数据后, 这时候返回的不应该是3001吗? 也不是 ! 因为除了2001-3001那块丢失的数据, 其他数据都是收到了的, 现在发送方补发丢失的数据后, 接收方下一个应该收到的数据报的序号就应该从8001开始, 于是发送了确认序号为8001的ACK, 表明8001前面的数据都已经收到

流量控制

理论上来说, 滑动窗口越大, 发送速度就越快, 但是这也需要兼顾接收方的接收速度, 如果发送速度太快, 接收速度又跟不上, 反而容易导致频繁丢包, 增加超时重传的次数, 反而降低了速度, 所以理想的情况应该是你发过来的, 我刚好都能处理, 于是有了"流量控制"这一机制, 目的就是限制滑动窗口的大小, 尽量让发送速率和接收速率对等

在网络通信中, 发送方的数据会通过网卡发送到B的接收缓冲区中, 接收缓冲区在操作系统内核中, 可以视为一个阻塞队列, 而接收方的应用程序则是会不断从接收缓冲区中处理数据。

在这里插入图片描述

流量控制会根据接收缓冲区中的剩余容量来判断计算滑动窗口的大小——我能装多少, 你就发多少。这个窗口大小信息会通过 ACK 的方式告知发送方窗口大小, 如下图, 存储在 TCP 报文中的16位窗口大小字段中, 并且只有 ACK 为1的时候, 这个字段才有效。总结: 这个字段用于描述接收缓冲区大小的,发送方再根据这个数据计算滑动窗口的大小

在这里插入图片描述
但是如果接收缓冲区满了怎么办 ? 这时候窗口为0, 也就是发送方暂时发不了数据了, 只能等待缓冲区位置空出来, 这期间会向接收方发送探测报文, 这个数据报的作用就是让接收方返回带有滑动窗口大小数据的 ACK 数据报, 以重新调整窗口大小

拥塞控制

好了, 有了滑动窗口和流量控制, 发送方和接收方满意了, 但是中间商(中间设备)不满意了, 如果发送速度对中间设备来说太快也不行, 他们忙不过来还是照样得丢包, 最终速度还是不满意, 那只能通过另一个机制来限制发送速度, 来同时兼顾中间设备和接收方的接收速度, 这就是拥塞控制。

拥塞控制有点不一样, 拥塞控制是通过做实验的方式来得出滑动窗口的大小值的, 我们以这个网图为例

  1. 首先先以一个很小的窗口值启动(cwnd是几, 当前窗口就能发送几个数据报), 比如1
  2. 然后窗口数量以指数形增长, 到达ssthresh(阈值)后, 就改变为线性增长, 慢慢增加
  3. 增加到一定数量(如图中24)的时候发现出现了丢包情况, 并判断这情况下网络环境拥塞
  4. 更新阈值为刚刚出现拥塞状态时的一半, 窗口数量重新定为1
  5. 重复上述过程

这样就可以尽可能地得到窗口值最大, 丢包率又低的窗口数。像图中红线左侧就是一个循环

在这里插入图片描述
而我们说到流量控制和拥塞控制都要用来控制滑动窗口大小, 具体点, 就是Math.min(流量控制窗口大小, 拥塞控制窗口大小) , 这也好理解, 要尽量避免超过中间设备和接收方的处理能力, 兼顾两者的处理速度。

延时应答

设想一下, 在流量控制中, 在某时刻, 接收方会发送含窗口大小信息的ACK, 那么如果晚一段时间后再发ACK会怎么样 ? 会发现这段时间内, 接收方的应用程序会持续消耗接收缓冲区中的数据, 也就是说, 晚一点发意味着接收缓冲区的剩余空间更大, 进一步, 返回给发送方的ACK中窗口大小字段会更大, 增加发送速率。
在这里插入图片描述
简单来说, 就是 ACK 会延迟发送, 让缓冲区剩余空间更大, 让滑动窗口调整到更合适的大小, 即延迟应答

捎带应答

捎带应答是实现在延时应答的基础上的, 如果接收方的ACK延时应答了, 然后刚好又遇到了一个要发送的数据, 这时候就顺带把这两个一起发送出去, 例如"三次握手"中四个步骤合并成三个步骤

在这里插入图片描述

粘包问题

由于 TCP 是面向字节流的, 它发送的数据报之间, 并不会主动去调整数据报之间的边界, 所以在接收方接收数据的时候, 也无法辨别从哪到哪是一个数据报中的数据

如下, 三个数据报发过去之后, 接收方无法识别数据的边界, 对接收方来说, 就是所有数据都粘在一起了
在这里插入图片描述为了解决这个问题, 我们通常有两种方法 (1) 利用分隔符作为数据报的边界 (2) 规定数据的长度

这里我们以 (1) , 为例, 我们规定每一份数据报中的数据最后都要有 \n 符号作为分隔符, 而接收方遇到 \n 符后, 就知道当前数据报到此结束, 就能够将不同的数据报区分开来了如下

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

TCP协议及特性详解 的相关文章

随机推荐

  • DNS请求流程和报文解析

    DNS 域名系统 英文 Domain Name System 缩写 DNS 是互联网的一项服务 它作为将域名和 IP 地址相互映射的一个分布式数据库 能够使人更方便地访问互联网 DNS 的作用是将人类可读的域名 如 www example
  • QT QMap-QMultiMap类实现一键多值的具体应用 (**)

    QT QMap QMultiMap类实现一键多值的具体应用 1 QMap 和 QMultiMap 都具有 一键多值 只是它们的成员函数有些不同 QT QMap QMultiMap类实现一键多值的具体应用 QT QMap QMultiMap类
  • 分数的拆分原理和方法_大梦简书——分数巧算(已更新分数裂项)

    今天大梦老师给大家梳理一下目前阶段会用到的分数巧算 这个也是拖更很久了 今天也终于在寒假上课前有机会给大家系统的写一写 依然是会一直更新的一个帖子 更新日志 19 2 28 更新分数裂项 小升初重点 已标红 PS 注意只有从微信公共号底端菜
  • 卷积神经网络(CNN)

    卷积神经网络 Convolutional Neural Network 简称CNN 是一种前馈神经网络 人工神经元可以响应周围单元 可以进行大型图像处理 卷积神经网络包括卷积层和池化层 在影像处理中 一张图片会被处理成三维矩阵 图片的长宽和
  • 使用jquery解析XML的方法,很简单

    尽量使用高版本的的jquery 有的jquery版本会报没有parseXML属性的错误 我用的jquery 1 7 2 min js xml文件格式
  • 多阶段构建Golang程序Docker镜像方法详解

    为什么要多阶段构建 大家都知道Golang是编译型语言 源码需要先编译再运行 编译过程中需要下载依赖包 最终编译成可执行的二进制文件 只需要部署这个二进制文件即可运行 现在基本都是采用容器化部署方式 打包出的镜像体积越小越好 和程序运行无关
  • Django入门之定义模型和表迁移

    django3 0 定义表模型并通过定义好的模型实现源代码创建数据表 目录 概述 定义表模型 引入模型类 继承 创建表模型 注意 创建数据表 1 生成迁移文件 2 执行迁移 3 更新表文件 总结 概述 模型是一个用于表示数据的Python类
  • 《算法和数据结构》从语言到算法的过渡篇

    本文已收录于专栏 夜深人静写算法 前言 看到太多爆肝熬夜整合的内容 又是几万字 又是爆肝 我也来试试看能不能扛得住 试完后发现 果然还是扛不住啊 但是既然整理完了 那就把我的 算法学习路线 发出来吧 我把整个算法学习的阶段总结成了五个步骤
  • 现代C++教程 笔记

    写在前面 记录一下 现代C 教程 中的要点 现代C 是指C 11之后的语法特性 如无特别说明 下面的语法特性均是C 11后才可使用 一 语言可用性的强化 1 常量 1 1 nullptr 作用 代替NULL赋空指针 使用 char a nu
  • 《ESP32 学习笔记》 之 ESP32 引脚图 及 个引脚特定功能 概览

    ESP32 S 模组 NODEMCU 32S 原理图 各个IO口功能
  • Qt 多线程之线程事件循环(深入理解)

    Qt支持三种类型的信号 槽连接 1 直接连接 当signal发射时 slot立即调用 此slot在发射signal的那个线程中被执行 不一定是接收对象生存的那个线程 2 队列连接 当控制权回到对象属于的那个线程的事件循环时 slot被调用
  • 在pl/sql中执行动态sql

    动态sql就是把sql写在一个字符串里 在存储过程中解析字符串执行sql 这种动态sql很多时候会在别的语言里写 再连接数据库进行操作 这样的确方便很多 例如在java中使用JDBC 但是如果涉及到sql变化很多次 直接在存储过程中写动态s
  • Linux嵌入汇编1- 详解

    Linux上的 GNU C 编译器 GCC 使用 AT T UNIX 汇编语法 源操作数与目的操作数顺序 AT T 语法的操作数方向和 Intel 语法的刚好相反 在Intel 语法中 第一操作数为目的操作数 第二操作数为源操作数 然而在
  • python 使用node_vm2执行js

    有时候 一些js需要调用 之前都是用nodejs比较多 但是有些js会验证是否使用的是node 就比如某头条的加密 为了能本地调用扣下来的js 这里就不能用nodejs或者execjs 需要用到vm2 步骤 1 下载vm2 pip inst
  • 排序与查找代码总结-数据结构与算法python版

    代码来源于北京大学的数据结构与算法课 Python版 注释为本人自己加上的 可供学习使用 不可用于商业转载 有错误烦请指出 感谢 目录 二分查找 普通版 递归版 冒泡排序 普通版 加了是否发生交换的监测 选择排序 插入排序 希尔排序 归并排
  • C语言

    C 菜鸟教程 C 结构体位域
  • win7搭建虚拟pppoe服务器,Win7在桌面建立一个pppoe宽带自动连接器的方法

    本教程告诉大家如何在Win7在桌面建立一个pppoe宽带自动连接器教程 现在电脑已经普及使用了 每次开机都要连接宽带上网 很多用户说如何快速在Windows桌面建立一个PPPOE宽带连接 方便直接连接 之前在xp系统可以建立pppoe宽带自
  • 合并两个有序链表 c++

    LeetCode 21 合并两个有序链表 题目 21 合并两个有序链表 代码 Definition for singly linked list struct ListNode int val ListNode next ListNode
  • 哪些工具可以实现在线ps的需求

    在线Photoshop有哪些工具可以选择 在 Adobe 的官网上就能够实现 很惊讶吧 其实 Adobe 官方推出了在线版本的 Photoshop 的 尽管目前还是 Beta版本 但其实也开放了蛮久了 编辑切换为居中 添加图片注释 不超过
  • TCP协议及特性详解

    文章目录 TCP 确认应答 超时重传 连接建立与断开 三次挥手 四次挥手 四种常见状态 效率提升机制 滑动窗口 流量控制 拥塞控制 延时应答 捎带应答 粘包问题 TCP TCP 协议是一个有连接 可靠传输 面向字节流 全双工的传输层通信协议