目录javascript
Netty Bootstrap(图解)
疯狂创客圈 Java 分布式聊天室【 亿级流量】实战系列之18 【 博客园 总入口 】css
源码工程
源码IDEA工程获取连接:Java 聊天室 实战 源码html
写在前面
你们好,我是做者尼恩。目前和几个小伙伴一块儿,组织了一个高并发的实战社群【疯狂创客圈】。正在开始 高并发、亿级流程的 IM 聊天程序 学习和实战,此文是是百万级流量 Netty 聊天器 打造的系列文章的第18篇,这是一个基础篇,介绍Bootstrap。前端
力争以图文并茂的形式,作到很是的易懂。java
图解几个重要概念
下面的几个概念,很是重要。react
以前没有认真介绍,下面图解说明一下。linux
父子 channel
在 Netty 中, Channel 是一个 Socket 链接的抽象, 它为用户提供了关于底层 Socket 状态(是不是链接仍是断开) 以及对 Socket 的读写等操做。面试
每当 Netty 创建了一个链接后, 都会有一个对应的 Channel 实例。算法
而且,有父子channel 的概念。 服务器链接监听的channel ,也叫 parent channel。 对应于每个 Socket 链接的channel,也叫 child channel。bootstrap
EventLoop 线程与线程组
在看本文以前,若是不明白 reactor 线程和reactor模式,请 查看 疯狂创客圈的专门文章:Reactor模式 。
在Netty 中,每个 channel 绑定了一个thread 线程。
一个 thread 线程,封装到一个 EventLoop , 多个EventLoop ,组成一个线程组 EventLoopGroup。
反过来讲,EventLoop 这个至关于一个处理线程,是Netty接收请求和处理IO请求的线程。 EventLoopGroup 能够理解为将多个EventLoop进行分组管理的一个类,是EventLoop的一个组。
他们的对应关系,大体以下:
通道与Reactor线程组
这里主要是涉及的是服务器端。
服务器端,通常有设置两个线程组,监听链接的 parent channel 工做在一个独立的线程组,这里名称为boss线程组(有点像负责招人的包工头)。
链接成功后,负责客户端链接读写的 child channel 工做在另外一个线程组,这里名称为 worker 线程组,专门负责搬数据(有点儿像搬砖)。
Channel 通道的类型
除了 TCP 协议之外, Netty 还支持不少其余的链接协议, 而且每种协议还有 NIO(异步 IO) 和 OIO(Old-IO, 即传统的阻塞 IO) 版本的区别。
不一样协议不一样的阻塞类型的链接都有不一样的 Channel 类型与之对应,下面是一些经常使用的 Channel 类型:
- NioSocketChannel, 表明异步的客户端 TCP Socket 链接.
- NioServerSocketChannel, 异步的服务器端 TCP Socket 链接.
- NioDatagramChannel, 异步的 UDP 链接
- NioSctpChannel, 异步的客户端 Sctp 链接.
- NioSctpServerChannel, 异步的 Sctp 服务器端链接.
- OioSocketChannel, 同步的客户端 TCP Socket 链接.
- OioServerSocketChannel, 同步的服务器端 TCP Socket 链接.
- OioDatagramChannel, 同步的 UDP 链接
- OioSctpChannel, 同步的 Sctp 服务器端链接.
- OioSctpServerChannel, 同步的客户端 TCP Socket 链接.
启动器初步介绍
Bootstrap 是 Netty 提供的一个便利的工厂类,能够经过它来完成 Netty 的客户端或服务器端的 Netty 初始化。
固然,Netty 的官方解释说,能够不用这个启动器。
可是,一点点去手动建立channel 而且完成一些的设置和启动,会很是麻烦。仍是使用这个便利的工具类,会比较好。
有两个启动器,分别应用在服务器和客户端。
以下图:
两个启动器大体的配置,都是相同的。
下面以服务器serverBootstrap 启动类为主要的介绍对象。
图解 Bootstrap执行流程
首先,建立了一个引导器 ServerBootstrap 实例,这个专门用于引导服务端的启动工做,直接new 建立便可。(客户端的引导器差很少,不过是建立Bootstrap 实例)
// 启动引导器 private static ServerBootstrap b = new ServerBootstrap();
启动一个Bootstrap,大体有8步,以下图:
代码以下:
try { //1 设置reactor 线程 b.group(bossLoopGroup, workerLoopGroup); //2 设置nio类型的channel b.channel(NioServerSocketChannel.class); //3 设置监听端口 b.localAddress(new InetSocketAddress(port)); //4 设置通道选项 b.option(ChannelOption.SO_KEEPALIVE, true); b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); //5 装配流水线 b.childHandler(new ChannelInitializer<SocketChannel>() { //有链接到达时会建立一个channel protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ProtobufDecoder()); ch.pipeline().addLast(new ProtobufEncoder()); // pipeline管理channel中的Handler // 在channel队列中添加一个handler来处理业务 ch.pipeline().addLast("serverHandler", serverHandler); } }); // 6 开始绑定server // 经过调用sync同步方法阻塞直到绑定成功 ChannelFuture channelFuture = b.bind().sync(); LOGGER.info(ChatServer.class.getName() + " started and listen on " + channelFuture.channel().localAddress()); // 7 监听通道关闭事件 // 应用程序会一直等待,直到channel关闭 ChannelFuture closeFuture= channelFuture.channel().closeFuture(); closeFuture.sync(); } catch (Exception e) { e.printStackTrace(); } finally { // 8 优雅关闭EventLoopGroup, // 释放掉全部资源包括建立的线程 workerLoopGroup.shutdownGracefully(); bossLoopGroup.shutdownGracefully(); }
接下来就是精彩的8个步骤。
1:设置reactor 线程组
在设置 reactor 反应器线程组以前,建立了两个 NioEventLoopGroup 线程组:
-
bossLoopGroup 表示服务器链接监听线程组,专门接受 accept 新的客户端client 链接
-
workerGroup 表示处理每一条链接的数据收发的线程组
在线程组和启动器都建立完成后,就能够开始设置线程组:经过 b.group(bossGroup, workerGroup) 方法,给引导器配置两大线程组。
配置完成以后,整个引导类的 reactor 线程正式肯定。这里肯定的工做模式,为父子线程的模型。
也能够不设置两个线程组,只设置一个线程组。
若是只设置一个线程组,具体的方法为 —— b.group( workerGroup) 。
配置完成一个线程组,则全部的 channel ,包括服务监听通道父亲channel 和全部的子channel ,都工做在同一个线程组中。
说明一下,一个线程组,可不止一条线程哈。
2 :设置通道的IO类型
Netty 不止支持 Java NIO ,也支持阻塞式的 BIO (在Netty 中 叫作OIO)。
这里配置的是NIO,方法以下。
//2 设置nio类型的channel b.channel(NioServerSocketChannel.class);
若是想指定 IO 模型为 BIO,那么这里配置上Netty的 OioServerSocketChannel.class 类型便可。因为NIO 的优点巨大,一般不会在Netty中使用BIO。
3:设置监听端口
//3 设置监听端口 b.localAddress(new InetSocketAddress(port));
这是最为简单的一步操做。
4:设置通道参数
-
childOption() 方法
给每条child channel 链接设置一些TCP底层相关的属性,好比上面,咱们设置了两种TCP属性,其中 ChannelOption.SO_KEEPALIVE表示是否开启TCP底层心跳机制,true为开
-
option() 方法
对于server bootstrap而言,这个方法,是给parent channel 链接设置一些TCP底层相关的属性。
TCP链接的参数详细介绍以下。
option设置的参数:
SO_RCVBUF ,SO_SNDBUF
这两个选项就是来设置TCP链接的两个buffer尺寸的。
每一个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区,TCP的全双工的工做模式以及TCP的滑动窗口即是依赖于这两个独立的buffer以及此buffer的填充状态。
SO_SNDBUF
Socket参数,TCP数据发送缓冲区大小。该缓冲区即TCP发送滑动窗口,linux操做系统可以使用命令:cat /proc/sys/net/ipv4/tcp_smem 查询其大小。
TCP_NODELAY
TCP参数,当即发送数据,默认值为Ture(Netty默认为True而操做系统默认为False)。该值设置Nagle算法的启用,改算法将小的碎片数据链接成更大的报文来最小化所发送的报文的数量,若是须要发送一些较小的报文,则须要禁用该算法。Netty默认禁用该算法,从而最小化报文传输延时。
这个参数,与是否开启Nagle算法是反着来的,true表示关闭,false表示开启。通俗地说,若是要求高实时性,有数据发送时就立刻发送,就关闭,若是须要减小发送次数减小网络交互,就开启。
SO_KEEPALIVE
底层TCP协议的心跳机制。Socket参数,链接保活,默认值为False。启用该功能时,TCP会主动探测空闲链接的有效性。能够将此功能视为TCP的心跳机制,须要注意的是:默认的心跳间隔是7200s即2小时。Netty默认关闭该功能。
SO_REUSEADDR
Socket参数,地址复用,默认值False。有四种状况可使用:
(1).当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时,而你但愿启动的程序的socket2要占用该地址和端口,好比重启服务且保持先前端口。
(2).有多块网卡或用IP Alias技术的机器在同一端口启动多个进程,但每一个进程绑定的本地IP地址不能相同。
(3).单个进程绑定相同的端口到多个socket上,但每一个socket绑定的ip地址不一样。(4).彻底相同的地址和端口的重复绑定。但这只用于UDP的多播,不用于TCP。
SO_LINGER
Socket参数,关闭Socket的延迟时间,默认值为-1,表示禁用该功能。-1表示socket.close()方法当即返回,但OS底层会将发送缓冲区所有发送到对端。0表示socket.close()方法当即返回,OS放弃发送缓冲区的数据直接向对端发送RST包,对端收到复位错误。非0整数值表示调用socket.close()方法的线程被阻塞直到延迟时间到或发送缓冲区中的数据发送完毕,若超时,则对端会收到复位错误。
SO_BACKLOG
Socket参数,服务端接受链接的队列长度,若是队列已满,客户端链接将被拒绝。默认值,Windows为200,其余为128。
b.option(ChannelOption.SO_BACKLOG, 1024)
表示系统用于临时存放已完成三次握手的请求的队列的最大长度,若是链接创建频繁,服务器处理建立新链接较慢,能够适当调大这个参数.
SO_BROADCAST
Socket参数,设置广播模式。
5: 装配流水线
ChannelPipeline 这是Netty处理请求的责任链,这是一个ChannelHandler的链表,而ChannelHandler就是用来处理网络请求的内容的。
每个channel ,都有一个处理器流水线。
装配 child channel 流水线,调用 childHandler()方法,传递一个ChannelInitializer 的实例。
在 child channel 建立成功,开始通道初始化的时候,在bootstrap启动器中配置的 ChannelInitializer 实例就会被调用。
这个时候,才真正的执行去执行 initChannel 初始化方法,开始通道流水线装配。
流水线装配,主要是在流水线pipeline 的后面,增长负责数据读写、处理业务逻辑的handler。
b.childHandler(new ChannelInitializer<SocketChannel>() { //有链接到达时会建立一个channel protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new ProtobufDecoder()); ch.pipeline().addLast(new ProtobufEncoder()); // pipeline管理channel中的Handler // 在channel队列中添加一个handler来处理业务 ch.pipeline().addLast("serverHandler", serverHandler); } });
说明一下,ChannelInitializer这个类中,有一个泛型参数 SocketChannel,这里的类型,须要和前面的Channel类型对应上。
顺便说一下处理器。
处理器 ChannelHandler 用来处理网络请求内容,有ChannelInboundHandler和ChannelOutboundHandler两种,ChannlPipeline会从头至尾顺序调用ChannelInboundHandler处理网络请求内容,从尾到头调用ChannelOutboundHandler 处理网络请求内容。
pipeline 流水线的图,大体以下:
如何装配parent 通道呢?
使用serverBootstrap.handler() 方法 。 handler()方法,能够和前面分析的childHandler()方法对应起来。childHandler()用于指定处理新链接数据的读写处理逻辑。 handler()方法装配parent 通道。
比方说:
serverBootstrap.handler(new ChannelInitializer() { protected void initChannel(NioServerSocketChannel ch) { System.out.println("服务端启动中"); } } )
handler()用于指定在服务端启动过程当中的一些逻辑,一般状况下呢,咱们用不着这个方法。
6: 开始绑定server
// 经过调用sync同步方法阻塞直到绑定成功 ChannelFuture channelFuture = b.bind().sync(); LOGGER.info(ChatServer.class.getName() + " started and listen on " + channelFuture.channel().localAddress());
这个也很简单。
7: ChannelFuture
ChannelFuture 在Netty中的全部的I/O操做都是异步执行的,这就意味着任何一个I/O操做会马上返回,不保证在调用结束的时候操做会执行完成。所以,会返回一个ChannelFuture的实例,经过这个实例能够获取当前I/O操做的状态。
// 7 监听通道关闭事件 // 应用程序会一直等待,直到channel关闭 ChannelFuture closeFuture= channelFuture.channel().closeFuture(); closeFuture.sync();
对于客户端来讲,Bootstrap是开发netty客户端的基础,经过Bootstrap的connect方法来链接服务器端。该方法返回的也是ChannelFuture。
8 优雅关闭EventLoopGroup
// 8 优雅关闭EventLoopGroup, // 释放掉全部资源包括建立的线程 workerLoopGroup.shutdownGracefully(); bossLoopGroup.shutdownGracefully();
这个,会关闭全部的child channel,这是很是重要的。
关闭以后,会释放掉底层的资源,如TCP Socket 文件描述符,等等。
疯狂创客圈 Java 死磕系列
-
Java (Netty) 聊天程序【 亿级流量】实战 开源项目实战
- Netty 源码、原理、JAVA NIO 原理
- Java 面试题 一网打尽