文章目录
- Option配置参数
- ①CONNECT_TIMEOUT_MILLIS(连接超时设定)
- ②SO_BACKLOG(全连接队列中存储连接个数)
- ③tcp_nodelay(一次尽可能发出数据,而非缓冲区缓存;禁用nagle算法)
- ④so_sendbuf & so_rcvbuf(发送缓冲区和接收缓冲区)
- ⑤allocator(配置ctx.alloc()分配的类型和开辟内存空间位置)
- ⑥rcvbuf_allocator(控制netty的接收缓冲区大小。就是在readchannel事件中读到的ByteBuf。)
netty笔记汇总:Netty学习指南(资料、文章汇总)
根据黑马程序员netty视频教程学习所做笔记,部分内容图例来源黑马笔记
笔记demo案例仓库地址: Github-【netty-learn】
Option配置参数
new ServerBootstrap().option() //是给ServerSocketChannel配置参数
new ServerBootstrap().childOption() //是给SocketChannel配置参数
①CONNECT_TIMEOUT_MILLIS(连接超时设定)
参数说明+代码示例
参数说明
来源:属于 SocketChannal 参数。
效果:用在客户端建立连接时,如果在指定毫秒内无法连接,会抛出 timeout 异常。
注意:对于SO_TIMEOUT参数主要用在阻塞 IO,阻塞 IO 中 accept,read 等都是无限等待的,如果不希望永远阻塞,使用它调整超时时间。(一句话控制阻塞的超时时间,到达一定时间直接停止阻塞向下执行)
代码示例
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;
/**
* @ClassName Client
* @Author ChangLu
* @Date 2022/1/15 17:33
* @Description ChannelOption.CONNECT_TIMEOUT_MILLIS:连接超时参数设置,到达一定时间依旧没有连接就会抛出超时异常。
*/
@Slf4j
public class Client {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap()
.group(group)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 1)
.channel(NioSocketChannel.class)
.handler(new LoggingHandler());
ChannelFuture future = bootstrap.connect("localhost", 8081);
future.sync().channel().closeFuture().sync(); // 断点1
} catch (Exception e) {
e.printStackTrace();
log.debug("timeout");
} finally {
group.shutdownGracefully();
}
}
}
运行结果:若是在进行连接过程中指定时间内没有返回响应,那么就会抛出一个连接超时异常;相反若是服务器没有启动,客户端来尝试连接,就会出现服务被拒绝的异常(其与设置连接超时时间无关)
源码分析
定位:可通过报错异常指定的类。
连接超时异常就是一个定时任务,若是在指定秒数中任务执行则表示连接失败,若是没有执行则说明并没有出现超时连接异常(在时间范围内连接成功并且取消了该定时任务)
定时任务执行情况:在EventLoop中线程定时任务执行时,会调用connectPromise.tryFailure(cause)
,其就会唤醒sync(),接着捕获异常打印信息,失败了就会进入catch(),成功会继续向后执行channel()。
注意:在线程之间通信使用的是promise!
②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(全连接队列)
- 最终当进行accept()时,就会从队列中取出来连接!
该参数与三次握手相关,若是三次握手成功,则会从全连接队列中取出来!
- 队列存在意义:在accept()实际处理前会在队列中堆积。可用于减轻服务端accept()的压力,用于临时保存一些连接状态,三次握手是发生在accept()之前的。
backlog:指的就是半连接、全连接队列的数量,全连接队列大小决定了你有多少个客户端能够在队列中临时存放,若是客户端的连接大于队列的个数,也就是处理连接能力达到上限了,就会出现拒绝连接的错误!
实际使用方式:
-
可通过linux系统配置,若是windows环境直接程序配置即可!
在 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
-
程序控制方式。对于NIO中
ServerSocketChannel
其其通过bind(8080,backlog)来设置;对于netty通过配置参数如option(ChannelOption.SO_BACKLOG,1024)
。
程序默认值说明:若是我们不进行手动配置,其也会有默认参数值,对于nio,可通过bind()方法,借助IDEA的useage快捷键,顺着调用方法向下找,最终能够在NetUtil中的静态代码块找到赋值操作;对于windows平台会设置为200,非windows为128,接着会去尝试读取linux的配置文件属性值!
测试程序方式:通过断点debug,让其阻塞在read()方法中,那么连接的客户端就会被放入到队列里,此时就能够测试出连接拒绝异常的效果了!
调参建议:若是设置其过小,那么在高峰期间大量客户端连接就会出现连接拒绝的问题!应该尽量对backlog设置大一点,让队列不容易被填满,这样客户端连接才不会被大量拒绝!
代码示例
对于netty设置参数的例子:是对serversocketchannel的设置的参数
public class Server {
public static void main(String[] args) throws InterruptedException {
new ServerBootstrap()
.group(new NioEventLoopGroup(),new NioEventLoopGroup())
.option(ChannelOption.SO_BACKLOG, 2) //设置全连接队列个数为2
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
}
}).bind(8081).sync();
System.out.println("服务器已启动!");
}
}
源码分析
①参数设置的效果演示
对netty进行调试的话,我们需要在NioEventLoop
类里的processSelectedKey
方法中的unsafe.read()打上断点,该方法的含义就是从全连接队列中取出连接请求:
②backlog的默认值:若是不设置其默认值?
从NIO中ServerSocketChannel的bind(地址,backlog)入手
查看其实现:
在其父类DefaultServerSocketChannelConfig中有getBacklog的具体实现:
最终在NetUtil的静态代码块中可以找到其赋值操作:
backlog其他参数
ulimit -n 数字
来源:属于操作系统参数。
限制你一个线程能够同时打开的最大文件描述符的数量(可以是socket连接),linux中无论socket、文件都是使用的一个文件描述符表示,简称FD,当文件描述符达到上限,再想打开就会报一个错误,其上限是为了保护你的系统打开的socket数不要太多。
建议:若是你的服务器想要应对高并发,支持大量的连接连我们服务器,就一定要调整这一个参数。这个参数属于一个临时类调整,建议把它放在启动脚本里。
③tcp_nodelay(一次尽可能发出数据,而非缓冲区缓存;禁用nagle算法)
来源:属于 SocketChannal 参数。
说明:为了尽可能发送大块数据,避免网络中充斥着许多小数据块。根据某个算法会先对要发的数据进行攒一批之后一次发送出去。默认设置的是false(开启的nagle算法),实际希望消息尽快发出去,所以要设置其为true。(对于想要测试出效果,目前没有找到合适的例子)
.option(ChannelOption.TCP_NODELAY,true)
④so_sendbuf & so_rcvbuf(发送缓冲区和接收缓冲区)
来源:SO_SNDBUF 属于 SocketChannal 参数;SO_RCVBUF 既可用于 SocketChannal 参数,也可以用于 ServerSocketChannal 参数(建议设置到 ServerSocketChannal 上)
目的:设置了滑动窗口的上限。建议不要调整该参数,早期应该根据参数来进行调整,但现在的操作系统比较智能,会自动的根据通信双方的网络能力调整sendbuf和rcvbuf。并不是调的越大越好,若是调的太大可能会对系统内存的占用会比较高!
⑤allocator(配置ctx.alloc()分配的类型和开辟内存空间位置)
参数说明
来源:属于 SocketChannal 参数。
主要目的:配置ctx.alloc()来创建的类型(池化、非池化)与开辟位置(直接、堆)。
默认配置说明:默认是池化直接内存。实际上是根据一个配置类的中配置类型来进行设置的。
配置方式:通过配置虚拟机参数,io.netty.allocator.type=pooled|unpooled来设置池化、非池化。对于直接或堆内依旧使用一个系统变量io.netty.noPrefferDirect=true|false。
测试
直接进行虚拟机参数配置即可:
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("ctx.alloc() => {}", ctx.alloc().buffer());
}
});
源码调试
起点:ChannelConfig
配置池化、非池化参数:io.netty.allocator.type=unpooled|pooled
配置直接、堆内存:io.netty.noPreferDirect=false|true
⑥rcvbuf_allocator(控制netty的接收缓冲区大小。就是在readchannel事件中读到的ByteBuf。)
参数说明+代码示例
来源:属于 SocketChannal 参数。
效果:控制netty的接收缓冲区大小。就是在readchannel事件中读到的ByteBuf。
核心点及默认配置:控制netty的接收缓冲区大小,指的就是最初readchannel中读到的ByteBuf。默认容量为1024,之后实际开辟空间大小跟后面几次传输的实际大小相关,最大为65536,最小为64。
- 关于类型与开辟空间位置:类型依旧根据根据虚拟机参数
io.netty.allocator.type=pooled|unpooled
来配置!分配空间位置默认是直接内存,这是固定的不能够更改。(netty认为使用直接内存效率更高)
代码示例
//设置指定的接收ByteBuf大小为100字节
.childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(100))
测试:
源码分析
切入点:通过设置断点,来根据调用栈找到
其控制池化、非池化:
接着看allocate分配空间方法:
说明:实际由一个分配器来进行创建的ByteBuf。该分配器只能用于配置是池化还是非池化。分配的时候调用的就是ioBuffer,指定就是直接内存,对于容量大小调用的是guess()方法其是猜测出来的,其会动态的根据你几次的数据量来对创建的ByteBuf进行调整,最开始默认是1024,之后若是数据量增多会进行调整,最大不会超过65536。若是穿过来的数据量很小,那么也会进行减少,最小为64。
对于分配的细节,我们需要来查看allocHandle
接着依次查看调用方,确定源头:对应三个参数值对应上面的说明
整理者:长路 时间:2021.1.15-1.16
我是长路,感谢你的耐心阅读。如有问题请指出,我会积极采纳!
欢迎关注我的公众号【长路Java】,分享Java学习文章及相关资料
Q群:851968786 我们可以一起探讨学习
注明:转载可,需要附带上文章链接