一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联各个组件,而客户端和服务端这两种应用程序间通用的引导步骤有AbstractBootstrap处理,Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类;
引导类的层次结构
为何引导类要实现Cloneable接口?
有时可能会需要创建多个具有类似配置或者完全相同配置的Channel(如链式调用那里添加配置);为了支持这种模式而又不需要为每个Channel都创建并配置一个新的引导类实例,AbstractBootstrap被标记为Cloneable的引导类实例;
注意,这种方式只会创建引导类实例的EventLoopGroup的一个浅拷贝,所以被浅拷贝的EventLoopGroup将在所有克隆的Channel实例之间共享;这是可以接受的,因为通常这些克隆的Channel的生命周期都很短暂;如创建一个Channel以进行一次HTTP请求;
对于AbstractBootstrap的子类型 B 是其父类型的一个类型参数,因此可以返回到运行时实例的引用以支持方法的链式调用(也就是所谓的流式语法);
引导客户端
Bootstrap 类负责为客户端和使用无连接协议的应用程序创建 Channel;
使用如下:
public class NettyClient { public static void main(String[] args) { final int port = 19000; final String local = "127.0.0.1"; EventLoopGroup group = new NioEventLoopGroup(); try { // 创建一个BootStrap类的实例以创建和连接新的Channel Bootstrap bootstrap = new Bootstrap(); // 链式写法 // 设置EventLoopGroup,提供用于处理Channel事件的EventLoop bootstrap.group(group) // 指定要使用的Channel实现 .channel(NioSocketChannel.class) // 设置用于用于处理Channel事件的Handler .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new NettyClientHandler()); } }); // 连接服务端 ChannelFuture channelFuture = bootstrap.connect(local, port).sync(); channelFuture.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } }
除了 connect()方法以外, BootStrap的其他方法将通过每次方法调用所返回Bootstrap 实例的引用;
在引导的过程中,在调用 bind()或者 connect()方法之前,必须调用以下方法来设置所需的组件(如果不这样做, 则将会导致 IllegalStateException):
- group();
- channel()或者 channelFactory();
- handler();需要通过该方法配置 ChannelPipeline;
引导服务器
ServerBootstrap 是服务端启动引导类;
ServerBootstrap 在 bind()方法被调用时创建了一个 ServerChannel,并且该 ServerChannel 管理了多个子 Channel;
ServerChannel的实现负责创建子 Channel,这些子Channel 代表了已被接受的连接(已连接的客户端);
负责引导 ServerChannel 的 ServerBootstrap 提供了的方法,以简化将设置应用到已被接受的子Channel的ChannelConfig 的任务;
使用如下:
public class NettyServer { private final static Logger logger = LoggerFactory.getLogger(NettyServer.class); public static void main(String[] args) { // 创建两个线程组bossGroup和workerGroup,含有子线程NioEventLoop的个数默认为cpu核数的两倍 // bossGroup只处理连接请求,业务处理交由wokerGroup EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); final int port = 19000; try { // 创建服务的启动对象 ServerBootstrap bootstrap = new ServerBootstrap(); // 设置两个线程组 bootstrap.group(bossGroup, workerGroup) // 指定使用NioServerSocketChannel作为服务器的通道实现 .channel(NioServerSocketChannel.class) // 初始化服务器连接队列大小,服务端处理客户端连接请求是顺序处理的,所以同一时间只能处理一个客户端连接 // 多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理 .option(ChannelOption.SO_BACKLOG, 1024) .childHandler(new ChannelInitializer<SocketChannel>() { //创建通道初始化对象,设置初始化参数 @Override protected void initChannel(SocketChannel ch) throws Exception { //对workerGroup的SocketChannel设置处理器codec ch.pipeline().addLast(new NettyServerHandler()); } }); logger.info("netty server start..."); // 通过配置好ServerBootStrap的实例绑定该Channel // 启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕,这里会阻塞住 ChannelFuture channelFuture = bootstrap.bind(port).sync(); // 添加监听器 channelFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { logger.info("监听端口" + port + "成功"); } else { logger.info("监听失败..."); } } }); channelFuture.channel().closeFuture().sync(); } catch (Exception e) { logger.error("异常..", e); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
如果服务器正在处理一个客户端的请求,这个请求需要它充当第三方系统的客户端;
当一个应用程序(如一个代理服务器)必须要和组织现有的系统(如 Web 服务或者数据库)集成时,就可能发生这种情况; 在这种情况下,将需要从已经被接受的子 Channel 中引导一个客户端 Channel;
如果创建新的BootStrap实例,并且每个新创建的客户端 Channel 定义另一个 EventLoop,这会产生额外的线程,以及在已被接受的子 Channel 和客户端 Channel 之间交换数据时不可避免的上下文切换;
通过将已被接受的子 Channel(已连接的客户端) 的 EventLoop 传递给 Bootstrap的 group()方法来共享该 EventLoop;因为分配给 EventLoop 的所有 Channel 都使用同一个线程,所以这避免了额外的线程创建和Channel之间数据交换时的上下文切换;如下图;
写法如下: