TCP传输的数据大小大于MTU(1500)

一、redis写入数据

public class JedisDemo {
    static String host = "192.168.91.128";
    static int port = 6379;
    
    public static void main(String[] args) throws Exception {
        try (JedisPool jedisPool = new JedisPool(host, port)) {
            Jedis jedis = jedisPool.getResource();
            StringBuilder sb = new StringBuilder();
            for (int i = 1; i < 10000; i++) {
                sb.append(i);
            }
            String trueString = sb.toString();
            System.out.println(trueString.getBytes().length);//长度为38889
            jedis.set("thisiskey", sb.toString());
        }
    }
}

一次性向redis中写38889字节的数据。

接下来通过抓包来看redis是tcp是如何传输这些数据的。

二、抓包redis网络交互数据

在redis服务器抓包

tcpdump -w redis_dump.cap -i eth0

打开dump

TCP传输的数据大小大于MTU(1500)

可以看到握手后本机向redis服务器分别传输了两次数据,大小分别为14654字节和24345字节。

初看这里是不对的,为什么呢?

因为以太网链路层的最大传输单元(MTU:Maximum Transmission Unit)是1500字节。

上面抓的包里面每次传输的数据是10倍的MTU,明显是不合理的。

问题出在哪里?

难道一次的传输数据会超过MTU的限制?

三、网卡TSO和GRO

先说结论:每次传输的数据量大小是不会超过MTU的限制的

这就类似于路只有一米宽,那么最多也就允许一米的车行驶。无论如何都不可能有超过1米的车在上面行驶。

那么为什么会抓包抓到远远大于MTU的包呢?

这里需要引入两个概念:TSO和GSO(linux默认是打开的)

3.1 TSO

TSO(TCP Segmentation Offload)

把TCP分段的过程直接移到网卡中进行,当发送的机器网卡支持TSO时,TCP层不再对大于MSS的数据进行分片,而是直接把数据传送到网卡,由网卡渠道进行分片,从而避免在TCP分片而占用CPU。

  1. 数据拆分所需要的CPU开销挪到网卡
  2. 实际在链路层还是按照MTU进行传输

TSO示意图:

TCP传输的数据大小大于MTU(1500)

这个需要在请求发送的机器上进行抓包验证(本文没有实际操作)。

3.2 GRO(LRO)

Generic Receive Offload(Large Receive Offload)

在网卡驱动层面将受到的多个数据包聚合成一个大的数据包,一次性交给上层传输层,减少上层协议栈对应的CPU的开销。

LRO示意图:

TCP传输的数据大小大于MTU(1500)

这个需要在接受请求的机器进行抓包验证,本文的例子就是基于接受请求的机器抓包而演示的。

3.3 关闭GRO后再次抓包

关闭redis服务器的GRO。

ethtool -K eno1  gro off

再次在redis服务器抓包。

TCP传输的数据大小大于MTU(1500)

可以看到每次的传输都变成了1514字节,其中数据为1500字节,和MTU的大小一样。

上一篇:带你深入熟悉你所不知道的ICMP


下一篇:《TCP/IP详解卷1:协议》学习笔记---IP分片