图解:Netty的TCP_NODELAY选项

Netty TCP_NODELAY优化与SO_RCVBUF/SO_SNDBUF理解

Netty的TCP_NODELAY选项

来自社群小伙伴的交流

总目录 博客园版 为您奉上更多の珍贵的学习资源

有关本文的 脚本 和 代码,可以来 尼恩 发起的Java 高并发 疯狂创客圈 社群 交流和获取。

Netty的TCP选项的配置

DefaultSocketChannelConfig




    @SuppressWarnings("unchecked")
    @Override
    public <T> T getOption(ChannelOption<T> option) {
        if (option == SO_RCVBUF) {
            return (T) Integer.valueOf(getReceiveBufferSize());
        }
        if (option == SO_SNDBUF) {
            return (T) Integer.valueOf(getSendBufferSize());
        }
        if (option == TCP_NODELAY) {
            return (T) Boolean.valueOf(isTcpNoDelay());
        }
        if (option == SO_KEEPALIVE) {
            return (T) Boolean.valueOf(isKeepAlive());
        }
        if (option == SO_REUSEADDR) {
            return (T) Boolean.valueOf(isReuseAddress());
        }
        if (option == SO_LINGER) {
            return (T) Integer.valueOf(getSoLinger());
        }
        if (option == IP_TOS) {
            return (T) Integer.valueOf(getTrafficClass());
        }
        if (option == ALLOW_HALF_CLOSURE) {
            return (T) Boolean.valueOf(isAllowHalfClosure());
        }

        return super.getOption(option);
    }

    @Override
    public <T> boolean setOption(ChannelOption<T> option, T value) {
        validate(option, value);

        if (option == SO_RCVBUF) {
            setReceiveBufferSize((Integer) value);
        } else if (option == SO_SNDBUF) {
            setSendBufferSize((Integer) value);
        } else if (option == TCP_NODELAY) {
            setTcpNoDelay((Boolean) value);
        } else if (option == SO_KEEPALIVE) {
            setKeepAlive((Boolean) value);
        } else if (option == SO_REUSEADDR) {
            setReuseAddress((Boolean) value);
        } else if (option == SO_LINGER) {
            setSoLinger((Integer) value);
        } else if (option == IP_TOS) {
            setTrafficClass((Integer) value);
        } else if (option == ALLOW_HALF_CLOSURE) {
            setAllowHalfClosure((Boolean) value);
        } else {
            return super.setOption(option, value);
        }

        return true;
    }

Netty的TCP选项

关于 本文的技术问题, 请来尼恩 发起的Java 高并发 疯狂创客圈 社群交流 ,

Option.TCP_NODELAY

TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,同时,对方接收到数据,也需要发送ACK表示确认。

为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。

TCP/IP协议中,无论发送多少数据,总是要在数据前面加上协议头,

同时,对方接收到数据,也需要发送ACK表示确认。

为了尽可能的利用网络带宽,TCP总是希望尽可能的发送足够大的数据。(一个连接会设置MSS参数,因此,TCP/IP希望每次都能够以MSS尺寸的数据块来发送数据)。

MTU:一个网络包的最大长度,以太网中一般为 1500 字节;
MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度; 最大 1460

img

Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。

Nagle算法的基本定义是任意时刻,最多只能有一个未被确认的小段。

所谓“小段”,指的是小于MSS尺寸的数据块,

所谓“未被确认”,是指一个数据块发送出去后,没有收到对方发送的ACK确认该数据已收到。

举个例子,比如之前的blog中的实验,

一开始client端调用socket的write操作将一个int型数据(称为A块)写入到网络中,由于此时连接是空闲的(也就是说还没有未被确认的小段),因此这个int型数据会被马上发送到server端,

接着,client端又调用write操作写入‘/r/n’(简称B块),这个时候,A块的ACK没有返回,

所以可以认为已经存在了一个未被确认的小段,所以B块没有立即被发送,一直等待A块的ACK收到(大概40ms之后),B块才被发送。

整个过程如图所示:

在这里插入图片描述

这里还隐藏了一个问题,就是A块数据的ACK为什么40ms之后才收到?

这是因为TCP/IP中不仅仅有nagle算法,还有一个 ACK延迟机制 。

当Server端收到数据之后,它并不会马上向client端发送ACK,而是会将ACK的发送延迟一段时间(假设为t),

它希望在t时间内server端会向client端发送应答数据,这样ACK就能够和应答数据一起发送,就像是应答数据捎带着ACK过去。

在我之前的时间中,t大概就是40ms。这就解释了为什么’/r/n’(B块)总是在A块之后40ms才发出。

如果你觉着nagle算法太捣乱了,那么可以通过设置TCP_NODELAY将其禁用 。

当然,更合理的方案还是应该使用一次大数据的写操作,而不是多次小数据的写操作。

关于 本文的技术问题, 请来尼恩 发起的Java 高并发 疯狂创客圈 社群交流 ,

Option.SO_RCVBUF

Option(Option.SO_RCVBUF, XX),

recv_buf 不会预先分配内存, 只是个接收缓冲区size的最大限制,

不建议设置rcv_buf, linux内核会对每一个连接做动态的 调整, 一般情况下足够智能, 如果设置死了, 就失去了这个特性, 尤其是大量长连接的应用,

Option.SO_SNDBUF

Option(Option.SO_SNDBUF, XX),

send_buf 不会预先分配内存, 只是个发送缓冲区size的最大限制,

不建议设置send_buf , linux内核会对每一个连接做动态的 调整, 一般情况下足够智能, 如果设置死了, 就失去了这个特性, 尤其是大量长连接的应用,

Option.RCVBUF_ALLOCATOR

不是TCP协议选项

关于 本文的技术问题, 请来尼恩 发起的Java 高并发 疯狂创客圈 社群交流 ,

参考文献

1.疯狂创客圈 JAVA 高并发 总目录

ThreadLocal(史上最全)

  1. 3000页《尼恩 Java 面试宝典 》的 35个面试专题
  2. 40岁老架构师 尼恩 练就的 价值10W的架构师知识图谱

4、40岁老架构师 尼恩 练就的 架构师哲学

5、40岁老架构师 尼恩 练就的 3高架构知识宇宙

https://blog.youkuaiyun.com/qq_22310551/article/details/124431034

https://blog.youkuaiyun.com/u013887008/article/details/104361380

https://article.itxueyuan.com/e196bP

https://blog.youkuaiyun.com/xiaolifeidaofirst/article/details/125218511

https://blog.youkuaiyun.com/small_engineer/article/details/124190620

https://learnku.com/articles/47193

https://www.cnblogs.com/xiaolincoding/p/12995358.html

https://blog.youkuaiyun.com/sinat_20184565/article/details/104828782

https://www.cnblogs.com/qhdziyan/p/16044974.html

http://blog.youkuaiyun.com/historyasamirror/article/details/6423235

### 3.1 检查本地库路径配置 运行时无法加载 `netty_tcnative` 库通常是由于本地库路径未正确配置。确保 `.so`(Linux)、`.dll`(Windows)或 `.dylib`(macOS) 文件存在于系统库路径中,并且该路径已添加到 `java.library.path`。 在启动时使用 `-Djava.library.path` 指定本地库路径,例如: ```bash java -Djava.library.path=/path/to/native/libs -jar your-application.jar ``` 此外,可以在 Linux 或 macOS 上通过 `LD_LIBRARY_PATH` 或 `DYLD_LIBRARY_PATH` 设置库路径: ```bash export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH ``` 如果未正确设置这些路径,将导致 `UnsatisfiedLinkError` 异常,表现为日志中出现 `Unable to load the library 'netty-tcnative-linux-x86_64'` 等信息[^1]。 ### 3.2 确保本地库文件存在且兼容 检查本地库文件是否确实存在于指定路径中,并且与当前操作系统和架构兼容。例如,在 64 位 Linux 系统上,应包含 `netty_tcnative_linux_x86_64`;在 Windows 上应包含 `netty_tcnative_windows_x86_64`;在 macOS 上应包含 `netty_tcnative_osx_x86_64` 或 `aarch_64`(M1 Mac)[^3]。 可以通过以下方式获取这些库: - 使用 Maven 或 Gradle 管理依赖,确保 `netty-tcnative` 被正确引入,并且其平台适配版本被下载。 - 手动下载并复制对应的本地库文件到系统库路径中。 如果库文件缺失,将导致日志中出现 `Failed to load any of the given libraries: [netty-tcnative-linux-x86_64, netty-tcnative-linux-x86_64-fedora, netty-tcnative]` 的信息[^2]。 ### 3.3 使用内置的 Netty 依赖 确保项目中使用了正确的 `netty-tcnative` 版本,并且其平台适配版本被正确加载。例如,在 Maven 中应添加如下依赖: ```xml <dependency> <groupId>io.netty</groupId> <artifactId>netty-tcnative</artifactId> <version>2.0.56.Final</version> </dependency> ``` 如果使用了 `${os.detected.classifier}` 等动态变量,需确保构建工具(如 Maven)能够正确解析该变量并下载对应的本地库文件。否则会出现 `Failure to transfer io.netty:netty-tcnative:jar:${os.detected.classifier}:2.0.0.Final` 的错误[^5]。 ### 3.4 禁用本地库加载(可选) 如果不需要使用 `netty_tcnative` 的高性能特性(如 OpenSSL 加速),可以通过设置 `io.netty.noNative` 来禁用本地库加载: ```bash java -Dio.netty.noNative=true -jar your-application.jar ``` 此方法可以避免因本地库缺失而导致的异常,但会牺牲部分性能。例如,在使用 `epoll` 或 `kqueue` 优化 I/O 操作时,禁用本地库会导致回退到 JVM 原生的 I/O 模型,从而影响吞吐量和延迟[^4]。 ### 3.5 日志级别控制 日志中频繁出现 `Unable to load the library 'netty_transport_native_kqueue_x86_64'` 等信息,并不一定代表错误,而是 Netty 在尝试不同加载机制时的调试输出。可以通过降低 Netty 的日志级别来避免这些信息干扰正常调试: ```bash -Dio.netty.logger.level=INFO ``` 这样可以减少不必要的调试日志输出,提升日志可读性[^3]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值