还是那个面试题:如何用 TCP 实现不可靠传输?… 本文有趣,黑科技,仔细看。
…
TCP 是"运营商友好"的。运营商依据 TCP 状态机能以任意粒度对 TCP 连接进行任意管控,包括不限于限速,整形,重置,计费,TCP 和运营商是互惠的。
UDP 像脱缰野马变幻莫测,运营商只能一刀切,比如高峰期给所有 UDP 流量仅 10 % 的资源份额。
…
让运营商看起来是 TCP,但实际上是 UDP,可以考虑做一个假 TCP 封装。
最简单的,把 IP 协议号从 UDP(17) 改成 TCP(6),复杂点就换个传输头,都可取。比如换头:
GitHub - marywangran/pseudotcp-tunnel
此外,各大厂也有各类冠以自研修饰的 XX 协议,它们有多么复杂自然不必多说,主要目的是卷。
我给一个 10 行代码实现的假 TCP,找到 Linux 内核 tcp_data_queue 函数,从注释处增加代码:
static void tcp_data_queue(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
bool fragstolen;
int eaten;
if (sk_is_mptcp(sk))
mptcp_incoming_options(sk, skb);
if (TCP_SKB_CB(skb)->seq == TCP_SKB_CB(skb)->end_seq) {
__kfree_skb(skb);
return;
}
skb_dst_drop(skb);
__skb_pull(skb, tcp_hdr(skb)->doff * 4);
// 增加下列 if 语句 13 行代码,暂硬编码 5001 端口,只为 iperf 测试。
if (inet_sk(sk)->inet_num == 5001 && after(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
struct sk_buff *nskb;
u32 n = TCP_SKB_CB(skb)->seq;
if (!before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt + tcp_receive_window(tp)))
goto out_of_window;
nskb = skb_copy_expand(skb, n - tp->rcv_nxt, 0, GFP_ATOMIC);
if (likely(nskb)) {
skb_put_padto(nskb, n - tp->rcv_nxt + skb->len);
consume_skb(skb);
skb = nskb;
TCP_SKB_CB(skb)->seq = tp->rcv_nxt;
}
}
tp->rx_opt.dsack = 0;
将这代码部署在接收端,事竟成。
意思是保证发送端 una 不断前推,不会收到 sack 和 dupack,也就不会重传。如下图:
简单说,收到空洞,空洞立即被填充,丢包补零不重传,这种粗暴策略意味着即使真发生乱序,数据也会被 0 填充掉。但简单啊。
看效果:
解释一下测试结果,为什么假 TCP 对丢包无感。
假 TCP 是有损传输,iperf 并不校验这种有损,所以即使结果带宽很大,有效的到达数据却随丢包率增加而减小。strace 观测 iperf 接收端实际接收到的数据:
这些 0 即被填充的 0,即有损传输的体现。丢包率越高,0 的比例越高。
结合优质高尚的编码可最大限度消除有损传输的质量伤害,还记得传皮鞋那个吗?参见:有损传输示例
再举一例。用有损传输方式接收一个 HTML 文档,会是一堆乱码吗?并不会。
若有数据丢失,至多丢失一些 HTML 标签和内容,HTML 标签封闭嵌套特点使标签自动补全变得可行且容易,最终依然可得到一个虽缺失部分内容但浏览器依然可展示的文档。
不仅如此,这些 0 还有妙用。通过分析一段时间内这些 0 的分布,绘制一幅拥塞画像是高尚的。
说说假 TCP 有什么用,到底哪里需要把 UDP 伪装成 TCP?
仅在接收端部署那 13 行代码,TCP 就五脏俱全了,这本就是一个真的 TCP,足以糊弄运营商的行为也就不能叫欺骗。除了不重传,TCP 的特性一样不少。
不重传意味着传输有损,收益是消除了重传时延,非常适合承载时延敏感的业务,如 VNC,流媒体。它适合传输任何 UDP 数据,以绕过运营商对 UDP 的一刀切管控,用这个协议搭建的隧道是通用,高尚的隧道。
…
两三事:
- 常见的面试题,如何用 UDP 实现可靠传输,但不高尚。高尚的面试题应该是,如何用 TCP 实现不可靠传输。
- TCP 的不可靠传输:TCP的不可靠传输
- 搭建隧道时还在 TCP 和 UDP 中二选一吗?
- 还在自研万行代码级的协议吗?本文只是个把戏,实际可以做得更好,比如给个窗口 Buffer,允许乱序的发生,Buffer full 了才补零。
- 黑科技每周都有,你肯看,我就有。
周中有朋友问起假 TCP 的事,我说抽空写一篇。这种协议要看你想要多假,可以只封装个 TCP 头,但要更逼真,就要能让运营商设备识别 Session,这一步很重要,运营商正是因为容易识别 TCP Session,才视 TCP 为友好的。于是假 TCP 变得复杂… 反着做,在真 TCP 上做减法,把约束一个个卸掉,直到它足够假,也就假亦真时真亦假了。当然,三次握手要斟酌,对于长连接,这无关紧要,可对于短连接,重试成本低到是否可靠也同样无关紧要。不过我依然有办法连三次握手也优化掉,不过要下回分解。
浙江温州皮鞋湿,下雨进水不会胖。