文章目录
- 一、CONNECT_TIMEOUT_MILLIS
- CONNECT_TIMEOUT_MILLIS设置为1秒超时
- CONNECT_TIMEOUT_MILLIS设置为5秒超时
- 注意事项
- 二、SO_BACKLOG
- 代码示例
- 注意事项
- 三、ulimit -n(文件描述符)
- 设置文件描述符限制
- 在注意事项
- 四、TCP_NODELAY
- 使用 TCP_NODELAY 的场景
- 注意事项
- 五、SO_SNDBUF & SO_RCVBUF
- SO_SNDBUF(发送缓冲区)
- 作用
- SO_RCVBUF(接收缓冲区)
- 作用
- 六、ALLOCATOR
- 七、RCVBUF_ALLOCATOR
一、CONNECT_TIMEOUT_MILLIS
在Netty中,CONNECT_TIMEOUT_MILLIS 是一个与连接超时相关的配置选项。这个选项用于设置客户端在尝试建立连接时的最大等待时间。如果在指定的时间内未能成功建立连接,Netty 将会抛出一个 ConnectTimeoutException 异常。
- 属于 SocketChannal 参数
- 用在客户端建立连接时,如果在指定毫秒内无法连接,会抛出 timeout 异常
- SO_TIMEOUT 主要用在阻塞 IO,阻塞 IO 中 accept,read 等都是无限等待的,如果不希望永远阻塞,使用它调整超时时间
-
客户端通过 .option() 方法配置参数 给 SocketChannel 配置参数
-
服务器端
new ServerBootstrap().option() ,是给 ServerSocketChannel 配置参数
new ServerBootstrap().childOption() ,给 SocketChannel 配置参数
CONNECT_TIMEOUT_MILLIS设置为1秒超时
option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000)
不启动服务器端直接启动客户端观察控制台
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test02ConnectionTimeout {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1000)
.channel(NioSocketChannel.class)
.handler(new LoggingHandler());
ChannelFuture future = bootstrap.connect("127.0.0.1", 8080);
ChannelFuture channelFuture = future.sync();//当1秒内没有连接成功,则判断为连接超时所以抛出异常
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
log.debug("timeout");
} finally {
group.shutdownGracefully();
}
}
}
控制台输出
io.netty.channel.ConnectTimeoutException: connection timed out: /127.0.0.1:8080
CONNECT_TIMEOUT_MILLIS设置为5秒超时
option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class Test02ConnectionTimeout {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.channel(NioSocketChannel.class)
.handler(new LoggingHandler());
ChannelFuture future = bootstrap.connect("127.0.0.1", 8080);
ChannelFuture channelFuture = future.sync();//当1秒内没有连接成功,则判断为连接超时所以抛出异常
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
log.debug("timeout");
} finally {
group.shutdownGracefully();
}
}
}
控制台输出
io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8080
Caused by: java.net.ConnectException: Connection refused: no further information
观察发现ChannelOption.CONNECT_TIMEOUT_MILLIS超时时间设置1秒和5秒时报错信息不同,原因是:
- 设置为5秒是因为在没有启动服务器端时,客户端两秒后判断出服务器端没启动,所以根本连不上。所以直接报错。
- 设置为1秒是因为还不能判断服务器端是否启动就已经超时了,所以报错连接超时。
注意事项
- 合理设置超时时间:根据应用场景和网络状况合理设置超时时间。如果网络条件较差,可以适当增加超时时间;如果对响应时间有严格要求,则可以缩短超时时间。
- 异常处理:在代码中处理 ConnectTimeoutException,可以记录日志,并根据业务需求决定是否重试连接等。
- 重试机制:可以实现一个重试机制,在首次连接失败后尝试多次连接,直到成功或达到最大重试次数。
- 监控和报警:设置监控来检测连接超时的发生,并在必要时触发报警,以便及时处理潜在的问题。
二、SO_BACKLOG
在Netty中,SO_BACKLOG 是一个与 TCP 服务器端套接字相关的选项,用于设置在服务器的连接请求队列中可以排队的最大连接数。当客户端尝试连接到服务器时,如果服务器正忙于处理其他连接请求,新的连接请求会被放入这个队列中等待处理。SO_BACKLOG 的值决定了这个队列的最大长度。
Netty中SO_BACKLOG属于 ServerSocketChannal 参数
- 第一次握手,client 发送 SYN 到 server,状态修改为 SYN_SEND,server 收到,状态改变为 SYN_REVD,并将该请求放入 sync queue 队列(半连接队列)
- 第二次握手,server 回复 SYN + ACK 给 client,client 收到,状态改变为 ESTABLISHED,并发送 ACK 给 server
- 第三次握手,server 收到 ACK,状态改变为 ESTABLISHED,将该请求从 sync queue 放入 accept queue(全连接队列)
其中
- 在 linux 2.2 之前,backlog 大小包括了两个队列的大小,在 2.2 之后,分别用下面两个参数来控制
- sync queue - 半连接队列
- 大小通过 /proc/sys/net/ipv4/tcp_max_syn_backlog 指定,在
syncookies 启用的情况下,逻辑上没有最大值限制,这个设置便被忽略 - accept queue - 全连接队列
- 其大小通过 /proc/sys/net/core/somaxconn 指定,在使用 listen 函数时,内核会根据传入的 backlog 参数与系统参数,取二者的较小值
- 如果 accpet queue 队列满了,server 将发送一个拒绝连接的错误信息到 client
在netty中可以通过 option(ChannelOption.SO_BACKLOG, 值) 来设置大小
代码示例
public class TestBacklogServer {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup())
.option(ChannelOption.SO_BACKLOG, 2)
// 验证全队列满了,客户端在进行连接会报错ConnectException: Connection refused: no further information
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new LoggingHandler());
}
}).bind(8080);
}
}
@Slf4j
public class TestBacklogClient {
public static void main(String[] args) {
NioEventLoopGroup worker = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(worker);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(ctx.alloc().buffer().writeBytes("hello!".getBytes()));
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
log.error("client error", e);
} finally {
worker.shutdownGracefully();
}
}
}
第三个客户端连接时报错:
Connected to the target VM, address: '127.0.0.1:43839', transport: 'socket'
19:58:44 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x95677ae0] REGISTERED
19:58:44 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x95677ae0] CONNECT: /127.0.0.1:8080
19:58:46 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [id: 0x95677ae0] CLOSE
Exception in thread "main" io.netty.channel.AbstractChannel$AnnotatedConnectException: Connection refused: no further information: /127.0.0.1:8080
Caused by: java.net.ConnectException: Connection refused: no further information
注意事项
- 操作系统限制:SO_BACKLOG 的实际效果受到操作系统的限制。在 Linux 上,SO_BACKLOG 的值通常会被限制在 somaxconn 系统参数范围内。可以通过查看当前linux系统的 somaxconn 值,并通过 sysctl 命令进行调整。
# 查看 somaxconn 的值
cat /proc/sys/net/core/somaxconn
# 设置 somaxconn 的值
sysctl net.core.somaxconn=1024
- 队列满后的处理:如果 SO_BACKLOG 队列满了,新的连接请求将会被拒绝。在这种情况下,客户端会收到一个连接失败的错误。因此,合理设置 SO_BACKLOG 的值很重要,以确保不会因为队列满而导致连接请求被拒绝。
- 性能影响:虽然设置较高的 SO_BACKLOG 值可以容纳更多的连接请求,但也可能占用更多的系统资源。因此,需要根据实际的并发需求和系统性能来权衡 SO_BACKLOG 的值。
三、ulimit -n(文件描述符)
文件描述符是操作系统分配给进程用于表示打开的文件、网络连接等资源的整数标识符。在 Linux 和 Unix-like 系统中,每个进程都有一个文件描述符的上限,这个上限由 ulimit -n 命令来查看和设置。
文件描述符是操作系统用来跟踪进程所打开的文件和其他 I/O 资源(如网络连接、管道等)的内部索引。每个打开的文件或 I/O 资源都有一个唯一的文件描述符与之关联。
- ulimit -n 属于操作系统参数
作用:显示一个线程同时打开最大文件描述符(FD)的数量,当FD到达上限在打开文件会报错。如果服务器要支持高并发,支持大量的连接就需要调整ulimit参数。
设置文件描述符限制
# 查看当前用户的软限制
ulimit -n
# 设置新的软限制
ulimit -n 65536
# 查看新的软限制
ulimit -n
在注意事项
- 安全性:增加文件描述符限制可能会增加系统资源的消耗,因此需要根据实际需求和系统能力来设置合适的限制值。
- 性能:增加文件描述符限制可能会导致系统开销增加,特别是在高并发场景下,因此需要权衡利弊。
- 系统限制:有些系统对文件描述符的总数有严格的限制,即使你是 root 用户也无法无限制地增加文件描述符的数量。
四、TCP_NODELAY
在Netty中,TCP_NODELAY 是一个与TCP协议相关的选项,用于控制是否禁用Nagle算法。Nagle算法是为了减少小数据包在网络中的传输次数,以减少网络带宽的消耗和网络拥塞。然而,在某些需要低延迟的应用场景中,禁用Nagle算法可以提高实时性能。
- TCP_NODELAY 属于 SocketChannal 参数
- 默认false开启了nagle算法(沾包问题数据攒到一起发送),但是一般都需要消息可以及时的发送出去。所以设置为true
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
public class Client {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true) // 设置 TCP_NODELAY 为 true
.handler(new MyClientInitializer());
// 连接到服务器
ChannelFuture future = bootstrap.connect("127.0.0.1", 8080).sync();
if (!future.isSuccess()) {
System.err.println("Failed to connect: " + future.cause());
}
} finally {
group.shutdownGracefully();
}
}
}
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1); // 主线程组,用于接受新的连接
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 工作线程组,用于处理连接上的数据
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.TCP_NODELAY, true) // 设置 TCP_NODELAY 为 true
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new LoggingHandler());
}
});
// 绑定端口并开始监听
ChannelFuture future = bootstrap.bind(8080).sync();
future.channel().closeFuture().sync(); // 等待服务器 socket 关闭
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
使用 TCP_NODELAY 的场景
- 实时应用
在实时应用(如在线游戏、实时视频会议等)中,通常需要尽可能低的延迟。在这种情况下,禁用Nagle算法可以提高实时性能,因为数据包会立即发送,而不会被累积。 - 高吞吐量应用
在需要高吞吐量的应用中,累积数据包可以减少网络带宽的消耗。因此,在这类应用中,可以保留Nagle算法的默认设置。
注意事项
性能影响:禁用Nagle算法会增加网络带宽的消耗,尤其是在发送小数据包的情况下。因此,在设置 TCP_NODELAY 之前,需要评估其对性能的影响。
兼容性:确保客户端和服务器端都设置了相同的 TCP_NODELAY 值,以避免由于配置不一致而导致的问题。
五、SO_SNDBUF & SO_RCVBUF
在Netty中,SO_SNDBUF 和 SO_RCVBUF 是两个与套接字缓冲区相关的选项,它们分别用于控制发送缓冲区和接收缓冲区的大小。这两个选项可以帮助优化网络通信性能,特别是在高并发或大数据量传输的场景下。
- SO_SNDBUF 属于 SocketChannal 参数
- SO_RCVBUF 既可用于 SocketChannal 参数,也可以用于 ServerSocketChannal 参数(建议设置到 ServerSocketChannal 上)
这两个参数决定了滑动窗口的上限。一般不建议手动添加,因为操作系统一般会根据通信双方的网络通信能力来调整这两个参数。
SO_SNDBUF(发送缓冲区)
SO_SNDBUF 选项用于设置发送缓冲区的大小。发送缓冲区是操作系统用来暂时存储待发送的数据的一个区域。当应用程序向网络发送数据时,数据首先被写入发送缓冲区,然后由操作系统负责将数据发送到网络上。
作用
提高发送速度:较大的发送缓冲区可以让应用程序更快地将数据写入缓冲区,而不必等待数据实际发送完毕。
减少阻塞:如果发送缓冲区太小,当网络拥塞或接收方处理较慢时,应用程序可能会因为缓冲区满而被阻塞。
SO_RCVBUF(接收缓冲区)
SO_RCVBUF 选项用于设置接收缓冲区的大小。接收缓冲区是操作系统用来暂时存储从网络接收的数据的一个区域。当数据从网络到达时,首先被写入接收缓冲区,然后由应用程序负责从缓冲区中读取数据。
作用
提高接收速度:较大的接收缓冲区可以让操作系统更快地接收数据,而不必等待应用程序处理完毕。
减少丢包:如果接收缓冲区太小,当网络流量较大或应用程序处理较慢时,可能会导致数据包丢失。
六、ALLOCATOR
- 属于 SocketChannal 参数
- 用来分配 ByteBuf, ctx.alloc()
ByteBuf默认使用PooledUnsafeDirectByteBuf
@Slf4j
public class Test04ByteBuf {
public static void main(String[] args) {
new ServerBootstrap()
.group(new NioEventLoopGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new LoggingHandler());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = ctx.alloc().buffer();
log.debug("alloc buf {}", buf);
//alloc buf PooledUnsafeDirectByteBuf(ridx: 0, widx: 6, cap: 256)
//在设置中VM options配置-Dio.netty.allocator.type=unpooled
//alloc buf UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeNoCleanerDirectByteBuf(ridx: 0, widx: 0, cap: 256)
//在设置中VM options配置-Dio.netty.allocator.type=unpooled -Dio.netty.noPreferDirect=true
//alloc buf UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf(ridx: 0, widx: 0, cap: 256)
// log.debug("receive buf {}", msg);
// System.out.println("");
}
});
}
}).bind(8080);
}
}
七、RCVBUF_ALLOCATOR
在Netty中,RCVBUF_ALLOCATOR 是一个与接收缓冲区大小相关的配置选项,它用于控制接收缓冲区的初始大小和最大大小。这个配置选项可以帮助优化网络通信性能,特别是在高并发或大数据量传输的场景下。
RCVBUF_ALLOCATOR 用于控制接收缓冲区的分配策略。接收缓冲区是操作系统用来暂时存储从网络接收的数据的一个区域。当数据从网络到达时,首先被写入接收缓冲区,然后由应用程序负责从缓冲区中读取数据。
- 属于 SocketChannal 参数
- 控制 netty 接收缓冲区大小
- 负责入站数据的分配,决定入站缓冲区的大小(并可动态调整),统一采用 direct 直接内存,具体池化还是非池化由 allocator 决定
全部文章:
Netty笔记01-Netty的基本概念与用法
Netty笔记02-组件EventLoop
Netty笔记03-组件Channel
Netty笔记04-组件Future & Promise
Netty笔记05-组件Handler & Pipeline
Netty笔记06-组件ByteBuf
Netty笔记07-粘包与半包(上)
Netty笔记08-粘包与半包(下)
Netty笔记09-网络协议设计与解析
Netty笔记10-Netty参数调优