当前位置: 首页 > article >正文

TCP Listen 原语:端口失衡、对称性及协议演进

TCP 最初就是作为 server 存在的,参见 RFC675 The TCP as a POST OFFICE,但最初它以一种很奇怪的方式定义这个 C/S 结构:

  • Server 指定 local address 和 foreign address,只有来自该特定 foreign address 的 packet 发往该 local address 被接受;
  • Server 指定 local address 但不指定 foreign address,任何 packet 发往 local address 被接受;

好比你去一个办事机构办事,要么预约专门的业务员,他只接待你,要么直接去大厅,有空闲业务员就为你服务。

现在我们知道,RFC793 将这些操作标准化成了 TCP Listen 原语。随之 server socket 的 bind-listen-accept 三步实现就成了约定俗成,留给 client 的只有一个 connect。

基于以上 TCP 最初的假设,server 和 client 并不对称,但 sport 和 dport 却是对称的,均为 16bit,这就带来了问题。我们知道,由于 server TCP 的 Listen 状态属性,client 数量在概率上一定比 server 更多,因此在概率上 sport 和 dport 的使用明显出现偏斜,client 端口逐渐不够用,server 端口用不完。

此前一篇文章的一个讨论:再谈 TCP 连接的源端口选择:

A:想起来前不久看过的这篇文章:谈谈 Linux 与 LACP 链路聚合,就是讲了因为这个看上去聪明的端口号分配优化,而导致了严重的互操作性问题。确实 16bit 端口号还是太少了,太容易碰撞了
B->A:没有那么容易碰撞,端口选择又不是每次从0蛋开始选择,你看下具体的实现里面有一个hint,标记了历史选择的位置。Eric提出的奇偶算法纯粹为了google内部的大量场景考虑的。不是16位不够用,是用什么算法的问题,算法二选一就行。
我->(A, B):
在持续连接存活时间不超过 MSL/65535 时 16bit 端口号在事实上就是不够用,神仙也救不了,说的就是这个,hint 不 hint 跟够不够用无关,如果端口号空间足够大,hint 就类似于递增一个全局计数器。
而 Google 算法只是针对特殊场景缓解了 bind 和 connect 的互斥热点问题,但它带来的问题是在端口不够用时不能快速失败从而导致了新的热点,还不如原始算法。只要把 port 增加几个 bit,算法就可以极大简化,如果 port 有 64bit,直接无脑递增全局计数器即可,而现代内存和带宽根本不在乎浪费几十个比特的空间,但程序员能掰扯的活儿也少了很多。另外,几乎所有新协议,无论 quic,还是 falcon,在多路复用标识的数位选择上都倾向于使用更多的位,以避免精巧的算法。

如果 TCP 最初标准化 Listen 结构,使用 20bit sport/12bit dport 或许更合理,在现有的实现中,我们依然可以用一种完全不同的算法将针对 sport,dport 的选择问题打偏,劳力往 server 端倾斜,毕竟 Listen socket 太省事了,get_port 四元组唯一性检测工作全都是 client 在做。

我们可以将 dport 的低 8bit 作为 server port,而高 8bit 作为 connection 唯一性标识的一部分(低 10bit,高 6bit 这么分割也 OK):

  • client 将解放 get_port 劳力,使用 24bit sport 消除端口不足以及因其导致的热点问题(比如 Google 的 bind/connect 奇偶分离);
  • server 在 Listen $port 时,实际 Listen 的端口号为 for p in range(0, 65535): lport[i] = p + port.
    server 从 65535 个 lport 中 accept。

这需要分别修改 server 和 client 的实现且无法向前兼容,适用于能自主控制的系统,但如果都能自主控制了,直接改协议不香吗?

但说白了还是端口号空间太小,不够用,你看 Quic 的 short-header,long-header 的多字节 Connection ID 以及 Falcon header 中 24bit ID,都是足足够用的,这让连接唯一性保证更加简单。

更值得一提的是,与 TCP 直接用 dport 解复用不同,Quic 直接在 UDP 层面接收所有报文,在服务自身去维护状态机和连接管理,而 Falcon 用 <Host, PF, VF> 这种具备明确含义的 tuple 编码来解复用,相当于自带了寻址,商品和服务在摆在那里,随到随用,这种自助结构比 TCP Server 的 Listen 结构有了更多弹性。

TCP 需要在服务标识和 dport 之间加一个中间层才能形成类似的结构,这对新协议的设计具有意义。以一个 Http 服务为例,服务发现机制应该返回一个 ‘该 Http 服务标识’,而不是一个 Host:Port 对。针对该服务的 Packet 应该对 ‘该 Http 服务标识’ 寻址,随后 ‘该 Http 服务标识’ 到 port 的映射自然就可以一对多了。就像我上面的新改版,client 通过服务发现机制获取 IP:80 作为 ‘该 Http 服务标识’,而 ‘该 Http 服务标识’ 则映射了 65535 个端口。

回到 TCP 最初,如果没有 Listen 原语,TCP 或可以用另一种更对称的方式打开一个连接,事实上 TCP 一直支持同时打开,参考 RFC793 Figure 8.:

      TCP A                                            TCP B
  1.  CLOSED                                           CLOSED
  2.  SYN-SENT     --> <SEQ=100><CTL=SYN>              ...
  3.  SYN-RECEIVED <-- <SEQ=300><CTL=SYN>              <-- SYN-SENT
  4.               ... <SEQ=100><CTL=SYN>              --> SYN-RECEIVED
  5.  SYN-RECEIVED --> <SEQ=100><ACK=301><CTL=SYN,ACK> ...
  6.  ESTABLISHED  <-- <SEQ=300><ACK=101><CTL=SYN,ACK> <-- SYN-RECEIVED
  7.               ... <SEQ=101><ACK=301><CTL=ACK>     --> ESTABLISHED

                Simultaneous Connection Synchronization

摆脱了 Listen 原语的调用,但却对调用时序的要求更严格。如果为了实现完全的非 Listent 对称被动打开,被动打开方只需收到 SYN 后发 SYN 即可,合并 SYN-SENT,SYN-RECEIVED 状态。

以这种方式打开的 TCP 连接 “更容易” 获得对称性,sport + dport 逐渐就成了类似 Quic,Falcon 等协议中 Connection ID 的含义。这其中就是协议演进本身的过程。

仍以目标 80 端口为例看 TCP server 和 client 的行为:

  • TCP client:自选并填充 sport + dport 高 8bit,dport 低 8bit 固定为 80,发起 SYN;
  • TCP server:dport 低 8bit 80 定位服务,sport + dport 高 8bit 标识连接,发起 SYN;
  • 执行同时打开。

总结,TCP Listen 原语导致了 sport,dport 用量的倾斜,特别在 Web 兴起之后加重了倾斜,而 sport 16bit 空间太小不足以支撑这种倾斜的力量。

浙江温州皮鞋湿,下雨进水不会胖。


http://www.kler.cn/a/470485.html

相关文章:

  • 搭建企业AI助理的创新应用与案例分析
  • 微信小程序获取图片使用session(上篇)
  • 使用图像过滤器在 C# 中执行边缘检测、平滑、浮雕等
  • 形态学:图像处理中的强大工具
  • 旷视科技C++面试题及参考答案
  • 【UI自动化测试】selenium八种定位方式
  • Linux 磁盘管理命令:mkinitrd :建立要载入ramdisk 的映象文件ssm:命令行集中存储管理工具
  • 利用API接口提升电商平台用户体验的实践
  • 【HarmonyOS】鸿蒙应用实现屏幕录制详解和源码
  • 【Linux】深入理解进程信号机制:信号的产生、捕获与阻塞
  • Kafka【应用 04】Java实现筛选查询Kafka符合条件的最新数据(保证数据最新+修改map对象key的方法+获取指定数量的记录)源码分享粘贴可用
  • 生信技能69 - 使用deepvariant进行对基因组指定区域Calling SNPs/Indels
  • 机器学习经典算法——线性回归
  • Spring Boot(4)使用 IDEA 搭建 Spring Boot+MyBatis 项目全流程实战
  • 【PPTist】批注、选择窗格
  • 关于物联网的基础知识(一)
  • 容器技术思想 Docker K8S
  • 关于Java面试题大全网站无法访问的解决方案
  • CSS的常规布局——盒子模型
  • 云计算是如何帮助企业实现高可用性的
  • VSCode报错Module ‘“xx.vue“‘ has no default export.Vetur(1192)
  • Git的简单介绍与如何安装Git
  • node.js内置模块之---fs 模块
  • WebSocket底层原理及 java 应用
  • 考研助手|基于SSM+vue的考研助手系统的设计与实现(源码+数据库+文档)
  • NebulaGraph学习笔记-自定义SessionPool