
本文介绍如何在 python asyncio streams 基础上,将多个独立 tcp 连接复用到单一 socket 通道中,实现对上层客户端/服务端软件完全透明的端口转发,避免修改原有网络逻辑。
本文介绍如何在 python asyncio streams 基础上,将多个独立 tcp 连接复用到单一 socket 通道中,实现对上层客户端/服务端软件完全透明的端口转发,避免修改原有网络逻辑。
在实际网络代理或内网穿透场景中,常遇到“多端口服务需经单一长连接透传”的需求——例如一个客户端软件同时连接 :80(HTTP)、:443(HTTPS)和 :22(SSH)三个端口,但网络出口仅允许建立一条 WebSocket 或 TLS 封装的长连接。此时,直接一对一转发不可行,必须引入连接复用(Multiplexing)机制:在单个底层流(如 asyncio.StreamReader/StreamWriter)之上,为每个原始 TCP 连接分配唯一标识,并封装/解封装数据帧,从而实现逻辑隔离与并发传输。
核心思路:协议层复用
推荐采用轻量、可自定义的二进制帧协议,例如:
+--------+--------+-----------------+ | VER(1) | TYPE(1) | PAYLOAD LENGTH(2) | +--------+--------+-----------------+ | CHANNEL_ID(4) | ... PAYLOAD ... | +------------------+-----------------+
-
CHANNEL_ID全局唯一,标识某次 TCP 连接(如由客户端随机生成或服务端分配); -
TYPE区分控制帧(0x01新建连接)、数据帧(0x02)、关闭帧(0x03); - 所有原始 TCP 数据均被切片、打标、序列化后写入同一
StreamWriter。
示例:简易 Multiplexer 实现(客户端侧)
import asyncio
import struct
class MuxClient:
def __init__(self, upstream_reader: asyncio.StreamReader, upstream_writer: asyncio.StreamWriter):
self.upstream_r = upstream_reader
self.upstream_w = upstream_writer
self.next_cid = 1
async def open_channel(self, host: str, port: int) -> int:
cid = self.next_cid
self.next_cid += 1
# 发送 CONNECT 帧
frame = struct.pack("!BBHI", 1, 1, 6, cid) + host.encode().ljust(256, b'\0') + struct.pack("!H", port)
self.upstream_w.write(frame)
await self.upstream_w.drain()
return cid
async def write_to(self, cid: int, data: bytes):
header = struct.pack("!BBHI", 1, 2, len(data), cid)
self.upstream_w.write(header + data)
await self.upstream_w.drain()
# 使用时可包装原始 socket.connect() 调用,对外暴露相同 API
更优实践:复用成熟方案
自行实现协议易出错且缺乏健壮性。生产环境建议优先集成以下成熟方案:
-
SOCKS5 over TLS:使用
aiohttp或proxy.py构建 SOCKS5 代理,客户端配置 SOCKS 代理后,所有connect()自动复用底层 TLS 连接; -
SSH 动态端口转发:
ssh -D 1080 user@tunnel-server启动本地 SOCKS 服务,再用asyncio的ProxyConnector(如aiohttp_socks)接入; - QUIC 隧道(如 Cloudflare Warp / WireGuard over QUIC):天然支持多路复用、0-RTT 和连接迁移,适合高丢包环境;
-
TUN/TAP 虚拟网卡:配合
aioquic或wireguard-go,将整个 IP 层流量封装,上层应用完全无感(需系统权限)。
注意事项
- 复用通道需处理流控与背压:避免单个慢连接阻塞整个通道,建议为每个
CHANNEL_ID维护独立发送队列 + 限速; - 必须实现心跳与超时机制,防止因单点故障导致全部连接僵死;
- 若需兼容现有软件(如未提供代理配置项),可结合
LD_PRELOAD(Linux)或WinDivert(Windows)进行系统级连接劫持,再路由至复用通道——但这已超出纯 Python 范畴。
综上,单 socket 多端口转发的关键不在“转发”,而在“识别与隔离”。只要为每个 TCP 流赋予唯一上下文并严格帧同步,即可在保持应用层零侵入的前提下,高效复用底层连接资源。










