- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
作者:小林coding
图解计算机基础网站:https://xiaolincoding.com
大家好,我是小林。
忽然思考一个问题,TCP 通过序列号、确认应答、超时重传、流量控制、拥塞控制等方式实现了可靠传输,看起来它很完美,事实真的是这样吗?TCP 就没什么缺陷吗?
所以,今天就跟大家聊聊,TCP 协议有哪些缺陷?主要有四个方面:
接下来,针对这四个方面详细说一下。
TCP 协议是诞生在 1973 年,至今 TCP 协议依然还在实现更多的新特性。
但是 TCP 协议是在内核中实现的,应用程序只能使用不能修改,如果要想升级 TCP 协议,那么只能升级内核。
而升级内核这个工作是很麻烦的事情,麻烦的事情不是说升级内核这个操作很麻烦,而是由于内核升级涉及到底层软件和运行库的更新,我们的服务程序就需要回归测试是否兼容新的内核版本,所以服务器的内核升级也比较保守和缓慢。
很多 TCP 协议的新特性,都是需要客户端和服务端同时支持才能生效的,比如 TCP Fast Open 这个特性,虽然在2013 年就被提出了,但是 Windows 很多系统版本依然不支持它,这是因为 PC 端的系统升级滞后很严重,W indows Xp 现在还有大量用户在使用,尽管它已经存在快 20 年。
所以,即使 TCP 有比较好的特性更新,也很难快速推广,用户往往要几年或者十年才能体验到。
基于 TCP 实现的应用协议,都是需要先建立三次握手才能进行数据传输,比如 HTTP 1.0/1.1、HTTP/2、HTTPS。
现在大多数网站都是使用 HTTPS 的,这意味着在 TCP 三次握手之后,还需要经过 TLS 四次握手后,才能进行 HTTP 数据的传输,这在一定程序上增加了数据传输的延迟。
TCP 三次握手和 TLS 握手延迟,如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EpBkBcBC-1652079594146)(https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/%E7%BD%91%E7%BB%9C/http3/TCP%2BTLS.gif)]
TCP 三次握手的延迟被 TCP Fast Open (快速打开)这个特性解决了,这个特性可以在「第二次建立连接」时减少 TCP 连接建立的时延。
过程如下:
Cookie
(已加密)并通过 SYN、ACK 包一起发给客户端,于是客户端就会缓存这个 Cookie
,所以第一次发起 HTTP Get 请求的时候,还是需要 2 个 RTT 的时延;Cookie
发给服务端,就提前可以跳过三次握手的过程,因为 Cookie
中维护了一些信息,服务端可以从 Cookie
获取 TCP 相关的信息,这时发起的 HTTP GET 请求就只需要 1 个 RTT 的时延;TCP Fast Open 这个特性是不错,但是它需要服务端和客户端的操作系统同时支持才能体验到,而 TCP Fast Open 是在 2013 年提出的,所以市面上依然有很多老式的操作系统不支持,而升级操作系统是很麻烦的事情,因此 TCP Fast Open 很难被普及开来。
还有一点,针对 HTTPS 来说,TLS 是在应用层实现的握手,而 TCP 是在内核实现的握手,这两个握手过程是无法结合在一起的,总是得先完成 TCP 握手,才能进行 TLS 握手。
也正是 TCP 是在内核实现的,所以 TLS 是无法对 TCP 头部加密的,这意味着 TCP 的序列号都是明文传输,所以就存安全的问题。
一个典型的例子就是攻击者伪造一个的 RST 报文强制关闭一条 TCP 连接,而攻击成功的关键则是 TCP 字段里的序列号位于接收方的滑动窗口内,该报文就是合法的。
为此 TCP 也不得不进行三次握手来同步各自的序列号,而且初始化序列号时是采用随机的方式(不完全随机,而是随着时间流逝而线性增长,到了 2^32 尽头再回滚)来提升攻击者猜测序列号的难度,以增加安全性。
但是这种方式只能避免攻击者预测出合法的 RST 报文,而无法避免攻击者截获客户端的报文,然后中途伪造出合法 RST 报文的攻击的方式。
大胆想一下,如果 TCP 的序列号也能被加密,或许真的不需要三次握手了,客户端和服务端的初始序列号都从 0 开始,也就不用做同步序列号的工作了,但是要实现这个要改造整个协议栈,太过于麻烦,即使实现出来了,很多老的网络设备未必能兼容。
TCP 是字节流协议,TCP 层必须保证收到的字节数据是完整且有序的,如果序列号较低的 TCP 段在网络传输中丢失了,即使序列号较高的 TCP 段已经被接收了,应用层也无法从内核中读取到这部分数据。如下图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cuCVeZm2-1652079594149)(https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost4@main/%E7%BD%91%E7%BB%9C/http3/tcp%E9%98%9F%E5%A4%B4%E9%98%BB%E5%A1%9E.gif)]
图中发送方发送了很多个 packet,每个 packet 都有自己的序号,你可以认为是 TCP 的序列号,其中 packet #3
在网络中丢失了,即使 packet #4-6
被接收方收到后,由于内核中的 TCP 数据不是连续的,于是接收方的应用层就无法从内核中读取到,只有等到 packet #3
重传后,接收方的应用层才可以从内核中读取到数据。
这就是 TCP 队头阻塞问题,但这也不能怪 TCP ,因为只有这样做才能保证数据的有序性。
HTTP/2 多个请求是跑在一个 TCP 连接中的,那么当 TCP 丢包时,整个 TCP 都要等待重传,那么就会阻塞该 TCP 连接中的所有请求,所以 HTTP/2 队头阻塞问题就是因为 TCP 协议导致的。
基于 TCP 传输协议的 HTTP 协议,由于是通过四元组(源 IP、源端口、目的 IP、目的端口)确定一条 TCP 连接。
那么当移动设备的网络从 4G 切换到 WIFI 时,意味着 IP 地址变化了,那么就必须要断开连接,然后重新建立 TCP 连接。
而建立连接的过程包含 TCP 三次握手和 TLS 四次握手的时延,以及 TCP 慢启动的减速过程,给用户的感觉就是网络突然卡顿了一下,因此连接的迁移成本是很高的。
我记得之前在群里看到,有位读者字节一面的时候被问到:「如何基于 UDP 协议实现可靠传输?」
很多同学第一反应就会说把 TCP 可靠传输的特性(序列号、确认应答、超时重传、流量控制、拥塞控制)在应用层实现一遍。
实现的思路确实这样没错,但是有没有想过,既然 TCP 天然支持可靠传输,为什么还需要基于 UDP 实现可靠传输呢?这不是重复造轮子吗?
所以,我们要先弄清楚 TCP 协议有哪些痛点?而这些痛点是否可以在基于 UDP 协议实现的可靠传输协议中得到改进?
现在市面上已经有基于 UDP 协议实现的可靠传输协议的成熟方案了,那就是 QUIC 协议,QUIC 协议把我本文说的 TCP 的缺点都给解决了,而且已经应用在了 HTTP/3。
所以,下次再聊聊 QUIC 协议是怎么实现可靠传输的。
我的 slideToggle 在幻灯片切换的底部有点跳动。会不会是因为那里有一个按钮之类的。任何使它更平滑的方法。尝试使用缓动但不是很成功。有什么建议 点击视频设置自己看看 The site $(do
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
概述: 我的 B 对象是一个 100 000 * 5000 的 2 GB 大矩阵 我的 A 对象较小 1000 * 5000 analyse_with_glm <- function(Y) { c
关闭。这个问题是opinion-based .它目前不接受答案。 想改善这个问题吗?更新问题,以便可以通过 editing this post 用事实和引文回答问题. 6年前关闭。 Improve t
我在从 SQL Server (2008R2) 数据库中的 NVARCHAR 字段检索加密数据时遇到了问题,对于某些记录,我的 C# .NET 应用程序中数据的字符串值与数据库记录中的数据字符串值不同
我从 main.cpp 中包含两个头文件,它们在匿名命名空间中具有以下定义:const string strToken = ("%");使用 g++ 4.9 版编译结果如下: In file incl
我正在测试的代码中出现信任边界冲突。该代码在 session 中添加表单,并且由于违反信任边界而存在缺陷 Inside Struts Action class execute method { Ed
这个问题没有与之相关的实际问题,它更多的是一个好奇的问题,想知道我是否过于字面意思;)。 所以我一直在努力尽可能多地理解 c++ 标准。今天,在深入研究标准时,我注意到了这一点 (ISO/IEC 14
在我的数据库中,我必须做一个Circular Reference(cycle)来获取我想要的数据,我不知道如何重新排序表来获取我想要的数据而不需要循环. 这是我的数据库的模式(或模型)——只有表名是英
我有一个代表一组数字的类。构造函数接受三个参数: startValue 、 endValue 和 stepSize 。 该类负责保存一个列表,其中包含考虑 stepSize 的开始值和结束值之间的所有
如何删除下图中标记的三个间隙? 此代码可在 http://jsfiddle.net/69zj6smo/ 获得- 调整渲染区域的大小以查看通常存在的一些线条。 让我感到困惑的是,我认为我总是创建这样的流
double 的位格式在第一位存储符号。 double的C#哈希算法是高低32位二进制异或。 因此,当您对 double A 及其负数 -A 进行哈希处理时,哈希值的唯一区别在于第一位。 要散列多个字
当我在 Action 中使用重定向时,afterAction 方法(在 controller.php 中)不起作用!我该如何解决这个问题? 注意:我不能使用 beforeAction 因为我在我的 A
毫无疑问,还有其他可能更好的方法可以做到这一点,但我正在努力了解这里发生了什么。 在下面的示例中,coverity 在第四行报告了 FORWARD_NULL 缺陷。 double? foo = nul
我们希望针对 Jenkins 中失败的构建自动在 Jira 中创建缺陷。如果您成功完成了此操作,可以与我分享吗? 最佳答案 您应该能够使用JIRA plugin来做到这一点,以及“JIRA:创建问题”
有人能解释一下为什么 VeraCode 似乎认为使用 name 作为公共(public)属性(property)是一个坏主意,并提出了一个好的缓解措施吗? 代码(JavaScript): var Ba
我认为这是 C++11 标准中的一个(次要)缺陷。在 [dcl.dcl] 中我们有: simple-declaration: decl-specifier-seqopt init-
我做了以下... private static IDbConnectionProvider CreateSqlConnectionProvider(DbConfig dbConfig) { r
我现在在运行我的 cakephp 应用程序时遇到了很多麻烦。 在将 vom lenny 升级为 squeeze(甚至尝试完全重新安装 sqeeze)之后,imagick 的速度非常慢,以至于它只是关闭
我收到来自 Veracode 的信任边界违规。我的代码是 userName= req.getParameter(Constant.USERNAME); session.setAttribute(Con
我是一名优秀的程序员,十分优秀!