问问问答答答!

简介: 这次,我就收集了几个最近大家问的问题。• TCP 头部中「长度字段」的长度只有 4 字节,为什么可以包含 TCP option 的长度?• TCP 时间戳回绕了怎么办?• 为什么重复的 ACK 无法判断要重传哪些数据?• 为什么 IO 多路复用要搭配非阻塞 IO?• 自旋锁为什么是悲观锁,而不是乐观锁?• 关于 HTTP cookie、sessionid、token 的问题• HTTP/1.0 可以开启长连接吗?

大家好,我是小林。

最近每天都挺多人问一些问题,基本上都是看图解网络和图解系统文章时的提出的问题。

我也会在每天忙完后,抽 1 个时间去回答大家的问题,但是不一定每个人我都能回答的到,因为有时候信息太多,可能没有看到你的问题。

有些读者问的问题也很有代表性的,也是值得分享出来的,所以我打算不定期收集一波问题,在公众号分享一下。可能有些问题,也是你们的疑惑点。

这次,我就收集了几个最近大家问的问题。

  • TCP 头部中「长度字段」的长度只有 4 字节,为什么可以包含 TCP option 的长度?
  • TCP 时间戳回绕了怎么办?
  • 为什么重复的 ACK 无法判断要重传哪些数据?
  • 为什么 IO 多路复用要搭配非阻塞 IO?
  • 自旋锁为什么是悲观锁,而不是乐观锁?
  • 关于 HTTP cookie、sessionid、token 的问题
  • HTTP/1.0 可以开启长连接吗?


TCP 头部中「长度字段」的长度只有 4 位,为什么可以包含 TCP option 的长度?


先给大家看看 TCP 包头结构:

11.jpg

之前有位读者的疑惑,说 TCP 的「首部长度」字段只有 4 位,这样算最大值就是 15,而「首部长度」字段是定义 TCP 包头的长度的,按道理 TCP 最大的包头的长度是:固定包头 20 字节+ 选项最长 40 字节 = 60 字节。为什么可以用 4 位的「首部长度」字段来定义 TCP 包头的长度?

我的回答:

首先,「首部长度」字段确是定义 TCP 包头的长度的,但是不是你这样计算的。

「首部长度」字段的意思是有多少个 4 字节,比如如果首部长度为 1111(最大值),十进制就是 15,所以「首部长度」字段的意思是有 15 个 4 字节,也就是 15 * 4 = 60


TCP 时间戳回绕了怎么办?


在这篇文章中:TCP 是如何避免历史报文的?。提到因为 TCP 序列号会有回绕的问题,所以需要用时间戳的机制来判断历史报文(简称 PAWS),然后有读者问了这么一个问题:

9.pngimage.gif

时间戳的大小是 32 bit,所以理论上也是有回绕的可能性的。

时间戳回绕的速度只与对端主机时钟频率有关。

Linux 以本地时钟计数(jiffies)作为时间戳的值,不同的增长时间会有不同的问题:

  • 如果时钟计数加 1 需要1ms,则需要约 24.8 天才能回绕一半,只要报文的生存时间小于这个值的话判断新旧数据就不会出错。
  • 如果时钟计数提高到 1us 加1,则回绕需要约71.58分钟才能回绕,这时问题也不大,因为网络中旧报文几乎不可能生存超过70分钟,只是如果70分钟没有报文收发则会有一个包越过PAWS(这种情况会比较多见,相比之下 24 天没有数据传输的TCP连接少之又少),但除非这个包碰巧是序列号回绕的旧数据包而被放入接收队列(太巧了吧),否则也不会有问题;
  • 如果时钟计数提高到 0.1 us 加 1 回绕需要 7 分钟多一点,这时就可能会有问题了,连接如果 7 分钟没有数据收发就会有一个报文越过 PAWS,对于TCP连接而言这么短的时间内没有数据交互太常见了吧!这样的话会频繁有包越过 PAWS 检查,从而使得旧包混入数据中的概率大大增加;

Linux 在 PAWS 检查做了一个特殊处理,如果一个 TCP 连接连续 24 天不收发数据则在接收第一个包时基于时间戳的 PAWS 会失效,也就是可以 PAWS 函数会放过这个特殊的情况,认为是合法的,可以接收该数据包。

// tcp_paws_check 函数如果返回 true 则 PAWS 通过:
static inline bool tcp_paws_check(const struct tcp_options_received *rx_opt, int paws_win)
{
......
   //从上次收到包到现在经历的时间多于24天,返回true
 if (unlikely(get_seconds() >= rx_opt->ts_recent_stamp + TCP_PAWS_24DAYS))
    return true;
.....
    return false;
}

要解决时间戳回绕的问题,可以考虑以下解决方案:

1)增加时间戳的大小,由32 bit扩大到64bit

这样虽然可以在能够预见的未来解决时间戳回绕的问题,但会导致新旧协议兼容性问题,像现在的IPv4与IPv6一样

2)将一个与时钟频率无关的值作为时间戳,时钟频率可以增加但时间戳的增速不变

随着时钟频率的提高,TCP在相同时间内能够收发的包也会越来越多。如果时间戳的增速不变,则会有越来越多的报文使用相同的时间戳。这种趋势到达一定程度则时间戳就会失去意义,除非在可预见的未来这种情况不会发生。

3)暂时没想到


为什么重复的 ACK 无法判断要重传哪些数据?


这篇文章「你还在为 TCP 重传、滑动窗口、流量控制、拥塞控制发愁吗?看完图解就不愁了」经常有读者问我这个问题,为什么重复的 ACK 无法判断要重传哪些数据?

8.jpg

image.gif7.jpg

image.gif

我的回答:

如果 seq2 和 seq3 都丢了,接收方收到seq4会回ack2,收到seq5会回ack2,seq6会回ack2。三个 ack 都是一样的,你怎么知道是要重传seq2,还是seq2、seq3呢?

这三个都是重复的ACK报文,seq 和 ack 都是一样的,如下图抓包图:

6.jpg

所以,无法根据重复的 ACK 来判断要重传哪些数据的(注意是哪些,不是哪个),想要具体实现要重传哪些数据,就要使用 sack 这个机制(具体在图解网络已经介绍了,这里不多说啦)。


自旋锁为什么是悲观锁,而不是乐观锁?


我图解系统里提到,自旋锁是悲观锁,然后有个读者说自旋锁底层是 CAS 实现的,为什么不是乐观锁呢?

5.jpg

我的回答:

乐观锁是先修改同步资源,再验证有没有发生冲突。

悲观锁是修改共享数据前,都要先加锁,防止竞争。

CAS 是乐观锁没错,但是 CAS 和自旋锁不同之处,自旋锁基于 CAS 加了while 或者睡眠 CPU 的操作而产生自旋的效果,加锁失败会忙等待直到拿到锁,自旋锁是要需要事先拿到锁才能修改数据的,所以算悲观锁。


为什么 IO 多路复用要搭配非阻塞 IO?


这个问题在 man select 就有说明了。


Under Linux, select() may report a socket file descriptor as "ready for reading", while nevertheless a subsequent read blocks.  This could for example happen whendata has arrived but upon examination has wrong checksum and is discarded.  There may be other circumstances in which a file descriptor is spuriously reported  asready.  Thus it may be safer to use O_NONBLOCK on sockets that should not block.

翻译一下就是:


在 Linux 下,select() 可能会将套接字文件描述符报告为“准备好读取”,但随后会出现读取块。例如,当数据到达但检查时校验和错误并被丢弃时,可能会发生这种情况。可能存在文件描述符被虚假报告为已就绪的其他情况。因此,在不应阻塞的套接字上使用 O_NONBLOCK 可能更安全。


简单来说,select 返回了读事件,但是该内核中不一定有数据可读,因为有可能被内核丢弃。

关于 HTTP cookie、sessionid、token 的问题

有位读者问下面 3 个 问题:

4.png

由于问题比较多,我也写了一个小短文回答他的问题。

回答问题一:

3.png

回答问题二:

2.png

回答问题三:

1.png

HTTP cookie、sessionid、token 的知识,也是比较常问的,我图解网络里还没写过,找个时间补一下。


HTTP/1.0 可以开启长连接吗?


0.jpgimage.gif

HTTP/1.0 是可以开启长连接的,只不过它是默认关闭的,如果浏览器要开启 Keep-Alive,它必须在请求的包头中添加:

Connection: Keep-Alive

然后当服务器收到请求,作出回应的时候,它也添加一个头在响应中:

Connection: Keep-Alive

这样做,连接就不会中断,而是保持连接。当客户端发送另一个请求时,它会使用同一个连接。这一直继续到客户端或服务器端提出断开连接。

从 HTTP 1.1 开始, 就默认是开启了 Keep-Alive,如果要关闭 Keep-Alive,需要在 HTTP 请求的包头里添加:

Connection:close

现在大多数浏览器都默认是使用 HTTP/1.1,所以 Keep-Alive 都是默认打开的。一旦客户端和服务端达成协议,那么长连接就建立好了。

相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。     相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
8月前
|
机器学习/深度学习 人工智能 监控
别把模型当宠物养:从 CI/CD 到 MLOps 的工程化“成人礼”
别把模型当宠物养:从 CI/CD 到 MLOps 的工程化“成人礼”
639 163
|
设计模式 算法 Java
设计模式觉醒系列(04)策略模式|简单工厂模式的升级版
本文介绍了简单工厂模式与策略模式的概念及其融合实践。简单工厂模式用于对象创建,通过隐藏实现细节简化代码;策略模式关注行为封装与切换,支持动态替换算法,增强灵活性。两者结合形成“策略工厂”,既简化对象创建又保持低耦合。文章通过支付案例演示了模式的应用,并强调实际开发中应根据需求选择合适的设计模式,避免生搬硬套。最后推荐了JVM调优、并发编程等技术专题,助力开发者提升技能。
|
前端开发 API C#
使用Unity 接入 Stable-Diffusion-WebUI的 文生图api 并生成图像
本文介绍了如何将Unity游戏引擎与Stable-Diffusion-WebUI的文生图API相结合,实现在Unity中生成图像的功能。内容包括启动Stable Diffusion的API设置、在Unity中创建脚本与UI配置,以及通过按钮点击事件触发图像生成的详细步骤和测试过程。
使用Unity 接入 Stable-Diffusion-WebUI的 文生图api 并生成图像
|
人工智能 Java 数据库连接
MyBatis Plus 使用 Service 接口进行增删改查
本文介绍了基于 MyBatis-Plus 的数据库操作流程,包括配置、实体类、Service 层及 Mapper 层的创建。通过在 `application.yml` 中配置 SQL 日志打印,确保调试便利。示例中新建了 `UserTableEntity` 实体类映射 `sys_user` 表,并构建了 `UserService` 和 `UserServiceImpl` 处理业务逻辑,同时定义了 `UserTableMapper` 进行数据交互。测试部分展示了查询、插入、删除和更新的操作方法及输出结果,帮助开发者快速上手 MyBatis-Plus 数据持久化框架。
1133 0
|
安全 自动驾驶 物联网
新四化驱动,如何构建智能汽车的“全场景”可进化互联网络?
在智能化、电动化、网联化、共享化的时代浪潮中,汽车正从单纯的 “机械产品” 进化为先进的 “移动智能终端”。在软件定义汽车(SDV)的崭新时代,每一次 OTA 升级的顺利完成、每一秒自动驾驶的精准决策、每一帧车载娱乐交互的流畅呈现,都离不开一张实时响应、全域覆盖、安全可靠的广域网络。
|
Java 关系型数据库 MySQL
springboot学习四:springboot链接mysql数据库,使用JdbcTemplate 操作mysql
这篇文章是关于如何使用Spring Boot框架通过JdbcTemplate操作MySQL数据库的教程。
1653 0
springboot学习四:springboot链接mysql数据库,使用JdbcTemplate 操作mysql
|
网络安全 网络虚拟化 数据安全/隐私保护
使用Python实现VPN搭建的流程步骤
保护个人隐私和数据安全变得尤为重要。VPN(虚拟私人网络)是一种有效的解决方案,可以帮助我们在网络上匿名浏览,保护数据传输的安全性。虽然市面上有许多商业VPN服务,但你也可以通过Python自己搭建一个简单的VPN。本文将介绍如何用Python建立自己的VPN。
|
机器学习/深度学习 Ubuntu Linux
在Linux中,如何按照该要求抓包:只过滤出访问http服务的,目标ip为192.168.0.111,一共抓1000个包,并且保存到1.cap文件中?
在Linux中,如何按照该要求抓包:只过滤出访问http服务的,目标ip为192.168.0.111,一共抓1000个包,并且保存到1.cap文件中?
|
定位技术 图形学
【用unity实现100个游戏之1】制作类元气骑士、挺进地牢——俯视角射击游戏多种射击效果(一)(附源码)
【用unity实现100个游戏之1】制作类元气骑士、挺进地牢——俯视角射击游戏多种射击效果(一)(附源码)
1741 0
|
存储 消息中间件 Kafka
Flink 实时数仓(一)【实时数仓&离线数仓对比】(1)
Flink 实时数仓(一)【实时数仓&离线数仓对比】