周末水一篇,简单闲聊应用层的端口复用原理与流程。不涉及内核层的 SO_REUSEPORT 具体代码实现,只为简单懂原理懂流程,主要讨论应用层的做法与常见工作模式
网络转发工具常可用于实现“端口复用”——也就是让同一个监听端口把流量转发到本地的多个后端端口或进程上.端口重定向(port redirection)通常是利用本地回环地址 127.0.0.1 把外来连接转发到本地其它端口或进程;端口复用(port multiplexing/reuse)在应用层上更多是利用 socket/转发逻辑和连接映射策略来把同一监听端口的请求分发到不同后端
假设配置
listen = "0.0.0.0:10000"
remote = "1.1.1.1:10000"
extra_remotes = ["127.0.0.1:20001", "127.0.0.1:20002"]
虽然 listen 和 remote 都写了 10000 端口,但 listen 描述的是本地监听的地址(即接收端),而 remote 描述的是转发目标(即目的端)。因此两者并不冲突。
listen 固定的是源(本地)监听地址与端口,变化的是要转发到的目的 IP 与端口。比如 127.0.0.1:20001、127.0.0.1:20002 这些在本地不同端口的服务,就可以通过转发工具实现对同一个外部监听端口的复用。到这里,构成了端口复用的基础。要让它真正跑起来,常见有两种模式
- 负载均衡式端口复用(Load-balancing)
[[endpoints]]
listen = "0.0.0.0:10000"
remote = "127.0.0.1:20000"
extra_remotes = ["127.0.0.1:20001", "127.0.0.1:20002"]
balance = "roundrobin: 4, 2, 1"
轮询(roundrobin):可按权重分配
顺序分配 (from top to bottom):每次请求依次从上到下
其他策略(如最少连接、随机分配)也可,特点:不关心协议内容,仅按策略把新连接/请求分配到后端。实现上通常是接收外部连接后按策略选择后端并建立到后端的连接或把连接代理到后端
- 协议分流式端口复用(Protocol-based routing)——常用
在转发器前加入一个首包识别分流器(first-packet classifier),对连接的首包进行协议识别,然后根据匹配规则将流量分发到对应后端,避免“先尝试再命中”的低效逻辑
示例配置:
first_pkt_len = 64 # 首包抓取字节数
first_pkt_timeout = 3000 # 首包抓取超时时间
default_remote = "127.0.0.1:22" # 默认后端(可选)
[[endpoints]]
listen = "0.0.0.0:10000"
remote = "127.0.0.1:22"
extra_remotes = ["127.0.0.1:80", "127.0.0.1:443"]
rules = [
{ name = "ssh", regex = "^SSH-", remote = "127.0.0.1:22" },
{ name = "http", regex = "^GET |^POST |^HTTP/", remote = "127.0.0.1:80" },
{ name = "tls", regex = "^\x16\x03", remote = "127.0.0.1:443" }
]
工作流程简述:
-
接收连接后抓取首包(或首 N 字节),等待不超过 first_pkt_timeout。
-
用 rules 中的正则或特征匹配首包,匹配到即把该连接分发到对应后端。
-
若未命中规则,使用 default_remote
-
也可以定义把某类流量丢弃的规则,例如:
rules = [
{ name = "http", regex = "^GET |^POST |^HTTP/", remote = -1 }
]
协议分流式端口复用通常更灵活且更高效,关键在于数据包识别分流器的准确性。实现方式也有多种:直接使用支持识别的转发工具、在主程序前挂 hook、或者用本地端口代理中转来实现分流器逻辑,而不一定把所有字段写进主程序配置
本文涉及协议特征为简单写法,仅方便讲述,闲聊式讲解,着重说明思路与常见做法,细节实现会随具体工具和语言而不同
在传输层,通常用五元组(SrcIP、SrcPort、DstIP、DstPort、Proto)来唯一标识一条网络流(flow);若两个连接的五元组完全相同,它们在协议层面上就是同一条流,会造成冲突或被视为重复。但在应用层实现端口复用时,常见做法是一个监听 socket 接收到外部连接后,再在应用逻辑中把该连接(或该流)映射/代理到不同的后端目标——此时后端连接的五元组与原始入站连接不同,因此不会与后端已有连接冲突。注意:绑定(bind)同一本地 IP:port 的行为还会受操作系统 socket 规则限制,这些属于内核层面的问题,不应与应用层的转发逻辑混淆
欢迎补充修正,讨论分享,交流群tg开源闲聊群