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数不要太多。
建议:若是你的服务器想要应对高并发,支持大量的连接连我们服务器,就一定要调整这一个参数。这个参数属于一个临时类调整,建议把它放在启动脚本里。