MoreRSS

site iconLai Xintao修改

新加坡 Shopee Python SRE, iredis开发者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Lai Xintao的 RSS 预览

AoC 2025 通关留念

2025-12-12 23:53:21

[剧透警告]

今年又来玩了 Advant of Code1,最近看 C 语言的代码比较多,就用尽量用 C 语言做。

第 9 和和第 10 天最难,尤其是第 10 天,看着挺简单,实际是从来没有听说过的整数线性规划问题2

第 11 天一看要用 dict,放弃 C 了。用 Python 很快就过了,一个搜索加路径缓存即可。但是如何用 C 写出来完全没有思路,最后问 chatGPT,想起来还有邻接表这种巧妙的表示方法。

第 12 天最搞笑,以前是做 part1 顺利,做 part2 的时候时间爆炸。day 12 做 sample 的时候就感觉时间要爆炸了。加了几个剪枝,part1 居然过了。我想完了,part2 肯定是让我找所有的可能的情况,一定会爆炸。结果点开 part——就通关了。今年居然只有 12 天?那距离圣诞节还有 13 天呢,我做啥?

  1. https://adventofcode.com/ ↩
  2. 高铭骏 写的:整数线性规划 ↩

fuglede 的代码太优雅了:https://github.com/fuglede/adventofcode/tree/master/2025,不愧是做量化的。

如何把网络设备从 traceroute 中隐藏

2025-12-12 11:44:33

在和朋友一起吃饭的时候,A 提了一个有意思的问题:怎样可以把一个机房内的路由设备从互联网「隐藏」呢?

「隐藏就是没有人可以知道这个设备的 IP 地址,这还不简单,只要禁用 ICMP 就可以了」, B说。

A 说,这样是可以。很多安全团队在实施起来也确实是这么做的,但是这样并不好:机房内所有的 IP 都无法 ping 通了。这样会增加 debug 的难度,得不偿失呀!

C 说,那就依然转发 ICMP 包,但是如果是 TTL=1 的包,就不要回复 ICMP Time Exceeded 了。

B 说,人家要的是「隐藏」,要是像你说的这么做,别人还是知道中间有一个设备的存在,没有完全符合要求。

一个 traceroute 的例子:第7跳直接丢弃 TTL=1 的包,不返回错误1

事实是这样的。假设一个简单的物理拓扑是 A -> B -> C,B 不回复 ICMP Time Exceeded,那么 traceroute 看起来就是 A ? C,可以猜测得到中间有一个路由器,但是已经禁止回复 ICMP Time Exceeded。看起来像下面这样。

C 说,traceroute 的原理是发送 TTL=1, 2, 3, … 的包,不断让路由器回复 ICMP Time Exceeded 信息,来得到每一跳的 IP 地址。要想完全隐藏,只需要:

  • 自己不回复 ICMP Time Exceeded
  • 让下一跳回复,仿佛下一跳就在自己的位置;

这样就可以完全隐藏了。要达到这个目的,只需要:

  • 对于 TTL=1 的包,不是丢弃,而是转发给下一跳,并且 TTL 依然保持为 1,即可。A ---[TTL=1]---> B ---[TTL=1]---> C, 对于客户端的 traceroute,看起来就像:A → C。

这样(理论上)好像确实可行了。三人对这个结论满意了。

后来我把这个讨论记录在了博客上(你现在正在阅读的一个),一位读者马上就发现了问题:可是这样 C 会出现两次吧!

确实是这样,假设在 A -> B -> C 的链路中:

  • TTL = 1 从 A 进入的时候,A 会在 ICMP 中回复自己的 IP;
  • TTL = 2 从 A 进入的时候,B 会直接转发给 C,C 会在 ICMP 中回复自己的 IP;
  • TTL = 3 从 A 进入的时候,B 会 TTL -1 转发给 C,C 会在 ICMP 中回复自己的 IP;

这样 C 就出现了 2 次!

看来,B 必须完全不减 TTL,直接转发,才能隐藏自己。不过这样就有出现环路2的风险了。

  1. 使用 mtr 检查网络问题,以及注意事项 ↩
  2. 网络中的环路和防环技术 ↩

博客维护:升级到 Ubuntu 24.04

2025-12-10 23:12:49

这个博客在四年前做了一次迁移1,目前是架设在一台 DigitalOcean 的 VPS 上,前面用 Cloudflare 作 CDN,得益于我选择的这两家公司非常靠谱(但是最近 Cloudflare 的事故23让我这个 CF 吹有一些尴尬),自从搭建起来之后,我几乎没有维护过。

最近后台一直提示我 PHP7.4 EOL 了,今天终于打起精神来决定升级一把。

一不做而不休,干脆直接把 4 年半前启动的这台 VPS 全升了吧:

  • Ubuntu 升级到 24.04;
  • Mysql 升级到 8.0;
  • PHP 升级到 8.3;

因为我是专业的 SRE,所以这次升级读者感受不到任何区别。

整体比较顺利,唯一遇到的问题是,我用的 wordpress theme 太老了,一样的代码居然在 PHP8.3 挂了。看提示是函数参数少传了,在 PHP7.4 是 Warning,在 PHP8.4 是直接 Fatal。

本来打算放弃治疗,直接用一个新的 wordpress 官方主题得了,省心。结果一个二〇二五这些主题,都是什么玩意,行距看着都难受。又回来决定修好主题的代码。得亏 ChatGPT,没想到意外地顺利,很快就跑起来了,目前也没发现什么问题。

安全方面上顺便做了一个加强,以前的架构是:域名解析到 Cloudflare,Cloudflare proxy 到我的 Nginx,Nginx 只接受 Cloudflare 的 IP4,其他的一概拒绝。自己以为很安全了,没有人知道我的真实 IP。然后自己一查,居然早已经暴露了。

censys 的查询结果

我也不知道什么时候暴露的。

这次直接用了 cloudflared,原理是,我的服务器 Nginx 只 listen localhost 的端口,我的服务器安装一个 cloudflared,cloudflared 会去主动连接 cloudflare,这样,在 cloudflare 收到请求的时候,会通过 –> cloudflared –> nginx 转发到我的机器上。有点像 FRP5 穿透。如此一来,我的 IP 完全没有暴露在公网上了。过段时间再去搜索一下,看暴露了没有。

欢迎读者留言 ; D

  1. 博客迁移到 Cloudflare ↩
  2. Cloudflare outage on November 18, 2025 ↩
  3. Cloudflare outage on December 5, 2025 ↩
  4. Cloudflare 的 IP range:https://www.cloudflare.com/ips/ ↩
  5. https://github.com/fatedier/frp ↩

Harbor GC 问题

2025-12-06 16:01:16

最近的工作比较忙,以至于网络技术的系列文章1许久不更新了。这几天在解决的问题是镜像存储服务 Harbor2,存储的 docker image 太多了。

虽然我之前在博客里面分享了一些 Docker image 构建的技巧3,以及炫耀了构建一个最小的 Redis Docker 镜像才不到 2MiB4,但是无奈,我的博客基本没有人看,所以同事上传的 image 都非常可怕,动辄就上 G,20+ GiB 的都有。现在我们的 Harbor 存储已经是 PiB 级别了。

多余的 image 就删除就好了,问题就在于,删除 image 比较复杂。分成几个步骤:

  1. 删除 image 的 Tag5
  2. 扫描整个数据库,找到没有被任何其他 image 和 tag 引用的 blob;
  3. 删除这些未被引用的 blob;

第二步尤其重要,简单来说,image 是分层的,一层就是一个 blob,一个 image 可以引用多个 blob。比如服务 A 的 Dockerfile 开头是 From: ubuntu:24.04,另一个服务 B 的 Dockerfile 开头也是 From: ubuntu:24.04,那么这两个 image 都是引用了 ubuntu 的 blob。删除服务 A 的 image 的时候,不能把 A 的 blob 都删除,因为这样的话 ubuntu 的 base image 就连带被删除了。所以我们在删除一个 image 的时候,其实并没有释放任何空间,而只是删除了 image 对 blob 的引用。这时候还不知道哪些 blob 是可以释放的,要知道哪些 blob 可以删除,就必须扫描全部的数据库,找到没有任何引用的 blob,才可以删除。难题就在扫全表这里。

这个问题就和编程语言的 GC 问题很像,不过更加简单一些,因为引用只存在于 tag 到 blob,tag 之间和 blob 之间不存在引用,也就没有环的问题。

引用计数

引用计数比较合适这个场景,因为没有环路,所以引用计数到 0 就可以直接删除,不需要扫表找孤零零的环。但是 Harbor 本身没有用这种方案,估计是因为引用记录维护起来比较难,必须准确并且处理好并发,处理不当很容易有数据误删或者出现永久的垃圾。

Mark and Sweep

这是官方的代码采用的方案,基本思路是,扫描所有的 image,对它们引用的 blob 标记为在使用中。扫描完成之后,所有从未被标记过的 blob 直接删除。

问题

如果直接用 Harbor 的 GC 方案,那么运行一次 GC 需要超过一个月的运行时间(不知道具体需要多久,因为从来没有成功跑完过)。之前的负责人设计了一个很聪明的方案,基本思路是,找到系统性能低的瓶颈,然后针对性地处理这些瓶颈。

对于前面的 3 个步骤:

  1. 删除 image 的 Tag:直接用 SQL 从数据库查询出来 image,判断是否需要保留(规则是每一个 image 只保留最近的 3 个版本),如果不需要保留,通过 API 删除;
  2. 扫描整个数据库,找到没有被任何其他 image 和 tag 引用的 blob:这一步因为是 Harbor 代码的 GC 逻辑,比较负载,还是通过 web UI 来触发的;
  3. 删除这些未被引用的 blob:Harbor 本身 sweep 的过程很慢,原因是没有并发,一个一个删除的,改进是直接通过并发删除。

这样,整体运行一次只需要一个月。

目前还是存在很多问题。我接受之后又做了一些改进:

  1. 之前的 PIC 显然是一个脚本大师,所有的工作都是通过 bash,awk,curl 这些工具完成的,每一步都需要人工操作 -> 等待完成 -> 人工操作下一步,比如到 mark and sweep 的这一步,需要人工去页面上触发 GC,然后关注执行的进度,在执行到 sweep 阶段的时候手动结束,开始运行下一步的脚本;我写了一个 300 多行的 Python 脚本,把所有的步骤串起来,这样就有了 crontab 定期执行的条件。
  2. 在第一步删除 image 的时候还是很慢,30s 只能删除一个 image,我们有千万个 image。解决办法是读了 harbor 的代码,发现 blobMgr.CleanupAssociationsForProject 这一步其实是最费时间且多余的,后面执行 GC mark 的时候一定会运行一遍。删除这个逻辑之后只需要 0.1s 就可以删除一个 image;
  3. 最后一步通过 API 删除 S3 上的数据,之前还是脚本用 curl 触发,速度太慢。使用 Python 之后就可以用 connection pool 并发删除了;
  4. 还做了其他的功能,比如支持不同的 project 自定义删除逻辑,「删除最近1年没有 pull 记录的 image」这种。

本质上是用最少的改动自动化原来的 GC 逻辑,目前运行一次的时间是 3 天。已经足够满足需求了,因为不需要人工执行,所以 3天和 3 个小时区别不大。

上一个负责人留下的文档详细记录的 Harbor GC 的逻辑以及改进点,比 Harbor 官方的文档还要详细。有了这些我半重写 GC 的逻辑就简单很多。

在他之前,是另一个负责 Harbor 的同事。阅读代码并找到瓶颈是需要很大的勇气的,且不一定行得通,可能花了很大的力气,最后发现这个事情做起来就只能这么慢。

但是问题还是要解决。所以他那时用了另一个有意思的方案:

  1. 搭建另一套一模一样的 Harbor 集群,复制以前的用户名,权限,project 等数据,但是把 blob 和 image 数据删除;
  2. 搭建一套 Nginx 代理,Nginx 转发逻辑是:
    • 对于 push,转发到新集群;
    • 对于 pull,先 pull 新集群,如果得到 404,就转发到老的集群,这样以前的数据都可以读;
  3. 在 1年之后,完全删除老的集群;

这是一个很有意思的「用运维手段解决技术问题」的例子,在 SRE 的工作中,迫于没有对软件的实现的控制力,我们经常需要用运维手段来解决代码实现上的问题。

  1. 计算机网络实用技术 ↩
  2. https://goharbor.io/ ↩
  3. Docker 镜像构建的一些技巧 ↩
  4. Build 一个最小的 Redis Docker Image ↩
  5. https://docs.docker.com/reference/cli/docker/image/tag/ ↩

大巴

2025-12-03 23:44:15

我上初中的时候,村里去县城唯一的公共交通就是一辆大巴车。「大」也说不上,应该叫中巴车。车是邻村一个叫王文义的人买的。他把车停在我们村,每天早上骑摩托车从自己的村子跑到我们村,然后以我们村为起点,一路接上邻村的人,去县城的停车场。从我们村走,可以拉更多的人。走到王文义的村子,顺路接上王文义的老婆,王文义的老婆在车上卖票,王文义开车,一张票 7 块,过年的时候一张票 10 块。

我 12 岁的时候上初中。我们村的学生大部分都去镇上的中学读,我爸重视教育,送我到县城读。每天就是坐这个中巴车去学校,在学校住 12 天,每两个星期回家一次。刚开始每天都想家,住在学校里很不习惯,12 个人一间宿舍,没有办法洗澡,宿舍臭烘烘的。周五下午离开学校,坐在车上,是心情最好的时候,因为从现在开始局距离学校越来越远了。周日的下午在车上,是心情最差的时候,在家收拾好东西,上了车,就没有退路了,只有一条去学校的路。

读高中的学生也是坐这辆车,需要去县城的村民也是坐这辆车,所以每周日下午格外人多。座位上全部坐满,车的走道也是挤满了人, 挤得满满当当,超载了两倍还多。

车比较破,车内的地板是一张铁皮,有的地方还破了小洞。有一次我坐在破洞的旁边,怀着郁闷的心情,透过洞看路上的小石子向后飞去。奔驰的客车,我和路面只有一张铁皮之隔。

后来王文义换了一辆新的宇通牌客车,涂着崭新的绿色油漆,座位也没有污渍,比以前也大了,可以叫做「大巴车」了。

高中生是周日中午回学校,初中生是周一早上。冬天天短,起个大早去坐车的只有我一个初中生。有一个冬天的早晨,格外的冷,我去车站的时候还伸手不见五指。我到车站,等了一会,王文义骑着摩托车来了。还不到发车时间,他打开门让我上车等,然后去发动车子。却发现油箱被冻住了,车发动不起来。他让我在上面坐着,我从玻璃看到他从附近的人家门外扯了一把干草,点了火塞在油箱下面烤,也顺便点了一只烟。看到这我就不淡定了,赶紧下了车,站在他旁边,看着火焰在车底燃烧,想象着发生巨大爆炸,把这几吨重的铁皮炸到天上的情形。过了一会,王文义的烟抽完了,回到驾驶室尝试发动,结果还真发动着了。他下车灭了火,就起步去县城了,车上还是只有我一个人。

3 年之后,我开始读高中,还是每两个周坐他的车上学,回家。生活还是一样的麻木,每天学习超过18个小时。最喜欢周六的中午坐车回家,最讨厌周日下午坐在回县城的车上。每两周的休息时间只有不到一天。

后来我去读大学,就再也没有坐过他的车了。每次回家我坐飞机去机场,然后坐芳姐的车去滨海酒店。父亲会去滨海酒店等我,开车带我回家。

芳姐是我哥介绍给我的,任何时候需要去坐飞机,或者从机场回家,只需要在微信上和芳姐说一声,芳姐安排一辆车点对点送到机场,除了我还会顺路接上其他需要去机场的人,一个人 60,7 座车跑一趟可以赚 360。 这些人常年跑机场,从来没有误过飞机。

只是过年的时候人流量大,活也多,一次司机和我说他三天只睡了6个小时,让我听了有些害怕。

大学快毕业的时候,村里就通了公交车,去县城价格更便宜了,但是为了连接更多的村庄,公交车也绕了,原来需要 40 分钟,现在要至少一个半小时。王文义的车也再也没有出现在我们村里。

LRO/GRO 对于网络吞吐的影响

2025-11-21 15:39:40

打开这个抓包文件,可以马上确认这是一个发送的数据比较多的连接1,因为 TCP sequence number 上升的很快,IP 层的包都是用最大的 MTU 发送的。

抓包文件截图

分析长肥管道,可以使用之前介绍过的技术,用 tcptrace 来分析。

打开 Statistics > TCP Stream Graphs > Time Sequence (tcptrace),可以看到下图。(如果是一个直线,说明方向看反了,点击 Switch Direction.

tcptrace 图

由于没有抓到这个 stream 的 TCP 3次卧手包,我们不知道 window scaling 是多少,所以这条绿线就可以直接忽略了。剩余的看起来一点问题没有,cwnd 打开并且保持的很好,也没有很多 SACK。在 200ms 左右有一次丢包。但是看 sequence 上涨的趋势来说,并没有造成多大的影响,很快补回来了。所以这里不是主要原因。

Sequence 上涨的趋势没有太大问题,还会有超时,那么问题就可能出在——上涨的速度不够快。同样的转发链路,我们不禁怀疑,是不是新的设备比旧的设备转发性能低?每一个包都慢几个 us,总的吞吐就低?

可以打开正常的转发抓包做对比:

转发效率高的 tcptrace

这个线确实可能更加斜一点,但是斜多少呢?我们可以看吞吐的图。

性能低的 tcpdump throughput
性能正常的 tcpdump throughput

棕色的线对应实际的传输速率(右侧的 Y 轴)。可以看到,正常情况下吞吐可以达到 220Mbps 左右,但是换上新的设备只有 140Mbps 左右。在大部分 HTTP 请求中,对于小的包,延迟的变化不会特别大,但是在长肥管道中,吞吐低就会造成传输数据就会出现差距。导致部分请求超时。

其实,新旧设备的转发速度并没有根本的区别,造成吞吐不同的原因,发生在别处。

这两幅图的对比也揭示了更加深层次的原因:即左侧的 Y 轴。

左侧 Y 轴,以及图中的蓝色点,含义是 packet 的 size 的分布,每一个点代表了一个 packet size。第一幅图中,所有的 packet 都是使用最大的 MTU 发送的。内层 overlay(VxLAN Tunnel 里面)的 MTU 是 1450.

而下图中,packet 的 size 居然超过了 MTU!

之前的一篇有关 MTU 的讨论2,我们知道,发送超过 MTU 的包是会被其他的设备丢弃的,那么为什么我们从 tcpdump 能看到超过 MTU 的包呢?这是因为网卡帮我们把收到的多个小包给合并成了一个大包,再交给操作系统(Kernel)处理,这部分现在一般是在网卡的硬件上来完成的,所以我们抓包看到的(即操作系统看到的)是网卡合并处理之后的包。这叫做 Large Receive Offload,LRO。

LRO

为什么要这么做呢?因为 CPU 是通用处理器,它能做很多事情。很忙。为了提高性能,在硬件上做的很多优化都是让其他的硬件去分担 CPU 的工作。比如:

  • 让 GPU 来代替 CPU 做矩阵运算;
  • 用专用的设备来卸载 TLS3
  • 让网卡卸载 vlan,把小包合成大包,等等;

网卡擅长做重复但是简单的事情,合并小包再是再合适不过啦!

而 CPU 的工作量主要和处理多少包有关,和包的长度关系不大,长度是 1 的包(在 kernel 里面是 skb)和长度是 10000 的包,对于 cpu 来说,只是一个 length 的 value 不同而已。包的内容是业务逻辑,主要是由应用程序处理的,在 Kernel 里面,主要关注的是包的 header。假设 CPU 的能力是每秒处理 10 万个包,如果每一个包的长度是 1Kb,那么吞吐就是 10Mbps;但如果包的平均长度是 100Kb,那么吞吐可以达到 1Gbps。所以有了网卡给我们做 LRO,就可以有效提高 CPU 的吞吐。

到现在,原因就清晰了:新设备上了之后 LRO 失效,由于服务器的网卡不再执行 LRO 功能,吞吐就下降了很多,导致了部分请求超时。

那么为什么换了新的设备之后,服务器的网卡 LRO 就失效了呢?服务器网卡 LRO 和网络设备又有什么关系?

由于做不做 LRO 是服务器的网卡的硬件实现,我们无法查看硬件的设计。但是从其他地方对于 LRO/GRO 的描述,我们可以得到一些启发。

Linux 可以在没有硬件的支持下,用软件的方式实现 Generic Receive Offload, GRO (当然了,性能肯定是要差一些)。Kernel 的文档对于 GRO 的描述4如下:

Generic receive offload is the complement to GSO. Ideally any frame assembled by GRO should be segmented to create an identical sequence of frames using GSO, and any sequence of frames segmented by GSO should be able to be reassembled back to the original by GRO. The only exception to this is IPv4 ID in the case that the DF bit is set for a given IP header. If the value of the IPv4 ID is not sequentially incrementing it will be altered so that it is when a frame assembled via GRO is segmented via GSO.

除了 GRO,还有一种机制是 GSO,即 Kernel 在发送 TCP 流的时候,无须自己把每一个 Segment 切分成符合 MTU 大小再发送,而是可以直接发送,由网卡硬件来做这个切分操作。

为了让 GRO 和 GSO 是互相可逆的,即 GRO 之后的包可以通过 GSO 还原出来。需要保证:

  • IP 包的 DF 设置为1,禁止 IP Fragmentation;
  • IP 包的 DF 如果是0,那么 IP 的 ID 必须是连续的;

两个规则只要符合一条即可。

如果 DF 为1,很好理解,GRO 和 GSO 很容易逆向出来。

如果 DF 为0,ID 连续,比如 100,101,102,那么合成一个大包,大包的 ID 是 100,也可以逆向出来。但是如果 ID 不连续,比如 101,105,107,那么合成一个大包之后,就丢失原始的信息了。

对于 VxLAN 的包,在 DPDK 的文档5中,由明确要求外层的 IP 包和内层的 IP 包都要遵守这个规则:

  • outer IPv4 ID. The IPv4 ID fields of the packets, whose DF bit in the outer IPv4 header is 0, should be increased by 1.
  • inner TCP sequence number
  • inner IPv4 ID. The IPv4 ID fields of the packets, whose DF bit in the inner IPv4 header is 0, should be increased by 1.

查看吞吐慢的 tcpdump,可以发现 outer 的 ip.df 是0,而且 ID 不连续,所以无法做 LRO/GRO。

外层 ip.id 不连续

虽然我没有想到保证可逆可以带来哪些好处,但是从网上找到的资料来看,这个是在「ip.df=0 并且 ip.id 不连续的时候,不做 GRO」唯一的理由了。在另一处的邮件讨论中6,netdev 维护者以这个原则为理由拒绝了合并。起因是 Alexander Duyck 希望添加这个 patch,以达到效果:对于 overlay 的包,GRO 不再看外层包的 ip.id ,外层可以使用 fixed header,只看内层包的 ip.id 是否连续。这样,很多(实现不正确的)网络设备也可以享受 GRO 的好处了,但是因为会打破可逆的原则,所以没有被合并。

PS:上一篇文章问题中很多读者提到 GSO,为什么是 GSO 而不是 GRO 呢?因为 9999 是 server 端口,所以 192.168.1.100:9999 是 server 端,抓包文件显示的主要流量是 client 上传给 server 的,不是 server 发给 client 的。另一个细节是,.100 发给 .200 的 delta time,一般比 .200 发给 .100 的 delta time 要低,也可以佐证 .100 是 server 端。

  1. TCP 长肥管道性能分析 ↩
  2. 有关 MTU 和 MSS 的一切 ↩
  3. https://en.wikipedia.org/wiki/TLS_acceleration ↩
  4. Segmentation Offloads ↩
  5. VxLAN GRO ↩
  6. [RFC,7/9] GSO: Support partial segmentation offload ↩

==计算机网络实用技术 目录==

这篇文章是计算机网络实用技术系列文章中的一篇,这个系列正在连载中,我计划用这个系列的文章来分享一些网络抓包分析的实用技术。这些文章都是总结了我的工作经历中遇到的问题,经过精心构造和编写,每个文件附带抓包文件,通过实战来学习网路分析。

如果本文对您有帮助,欢迎扫博客右侧二维码打赏支持,正是订阅者的支持,让我公开写这个系列成为可能,感谢!

没有链接的目录还没有写完,敬请期待……

  1. 序章
  2. 抓包技术以及技巧
  3. 理解网络的分层模型
  4. 数据是如何路由的
  5. 网络问题排查的思路和技巧
  6. 不可以用路由器?答案和解析
  7. 网工闯了什么祸?答案和解析阅读加餐!
  8. 延迟增加了多少?答案和解析
  9. 压测的时候 QPS 为什么上不去?答案和解析
  10. 重新认识 TCP 的握手和挥手答案和解析
  11. TCP 下载速度为什么这么慢?答案和解析
  12. 请求为什么超时了?答案和解析
  13. 0.01% 的概率超时问题答案和解析
  14. 后记:学习网络的一点经验分享
与本博客的其他页面不同,本页面使用 署名-非商业性使用-禁止演绎 4.0 国际 协议。