Netty in action—Bootstraping
2017-09-13 22:18
295 查看
启动(bootstrapping)一个应用是配置并运行它的过程。
与其应用架构的方法一致,Netty将通过你的应用与网络层分离的方式(不需要关心网络层的实现细节)来处理启动。
我们前面介绍过的几个Netty组件参与了启动过程,其中一些客户端和服务端都可以使用。这两种类型的共同启动步骤由AbstractBootstrap处理,而专用于客户端或服务端的是Bootstrap或ServerBootstrap。下面开始介绍Bootstrap。
为什么bootstrap类是Clonealbe的? 你有时需要创建多个channel,它们有相似或相同的配置。为了支持这种设计同时不需要一个新的bootstrap实例,AbstractBootstrap类被设计为Clonealbe的。在一个已经配置好的bootstrap实例调用clone()方法会返回另一个开箱即用的bootstrap实例;注意这只会创建bootstrap的EventLoopGroup的一个浅克隆,因此它会被所有克隆的channel共享。这是可接受的,因为被克隆的channel通常是生命周期很短的,一个典型的应用是HTTP请求。
AbstractBootstrap类的完整声明如下:
在这个签名中,子类B是超类的一个类型参数,因此可以返回一个运行时实例的引用来支持方法链接(method chaining)
子类如下定义:
下面的代码展示了如何启动一个客户端:
这种兼容性必须维护,你不能混合不同前缀的组件,比如NioEventLoopGroup和OioSocketChannel。下面的代码显示了一个错误的示范:
这段代码会引起一个IllegalStateException
启动时,在你调用bind()或connect()之前,你必须调用下面的方法来设置必要的组件
* group()
* channel() 或 channnelFactory()
* handler()
如果没这么做也会导致一个IllegalStateException。handler()方法尤其重要,因为必须用它来配置ChannelPipeline。
下面的代码展示了这个过程:
实现EventLoop共享可以通过调用group()方法设置EventLoop来实现:
你可以部署很多个ChannelHandler到一个ChannelPipeline中。但是如果你只能设置一个ChannelHandler你该如何做呢?
Netty提供了一个ChannelInboundHandlerAdapter的特别的子类来满足这种情况。
它定义了如下的方法:
这个方法提供了一个简单的方式来增加多个ChannelHandler到一个ChannelPipeline中。你只需要提供你ChannelInitializer的实现,一旦Channel与它的EventLoop注册好了后,你实现的initChannel()方法就会被调用。当这个方法返回后,ChannelInitializer的实例就会把自己从ChannelPipeline中移除。
下面定义ChannelInitializerImpl类然后通过bootstrap的childHandler()来注册它:
与其应用架构的方法一致,Netty将通过你的应用与网络层分离的方式(不需要关心网络层的实现细节)来处理启动。
启动类
启动类的结构如上图。包含一个抽象父类和两个具体启动子类。与其将这两个具体子类看成server和client的启动类,不如将它们看成是支持的不同应用程序功能。也就是,一个server提供一个父channel来接收客户端的连接,然后创建子channel来进行会话,而一个client可能只需要一个非父类的channel来进行所有的网络交互(也有可能不需要channel,比如无连接传输协议UDP)。我们前面介绍过的几个Netty组件参与了启动过程,其中一些客户端和服务端都可以使用。这两种类型的共同启动步骤由AbstractBootstrap处理,而专用于客户端或服务端的是Bootstrap或ServerBootstrap。下面开始介绍Bootstrap。
为什么bootstrap类是Clonealbe的? 你有时需要创建多个channel,它们有相似或相同的配置。为了支持这种设计同时不需要一个新的bootstrap实例,AbstractBootstrap类被设计为Clonealbe的。在一个已经配置好的bootstrap实例调用clone()方法会返回另一个开箱即用的bootstrap实例;注意这只会创建bootstrap的EventLoopGroup的一个浅克隆,因此它会被所有克隆的channel共享。这是可接受的,因为被克隆的channel通常是生命周期很短的,一个典型的应用是HTTP请求。
AbstractBootstrap类的完整声明如下:
public abstract class AbstractBootstrap <B extends AbstractBootstrap<B, C>, C extends Channel>
在这个签名中,子类B是超类的一个类型参数,因此可以返回一个运行时实例的引用来支持方法链接(method chaining)
子类如下定义:
public class Bootstrap extends AbstractBootstrap<Bootstrap,Channel> public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap,ServerChannel>
在客户端和无连接协议中的启动
下表给出了Bootstrap方法的总览,有很多方法是继承自AbstractBootstrap的名称 | 描述 |
---|---|
Bootstrap group(EventLoopGroup) | 设置EventLoopGroup,这个EventLoopGroup会为channel处理所有的事件 |
Bootstrap channel(Class) Bootstrap channelFactory(ChannelFactory) | channel()指定Channel的实现类。如果这个实现类没有提供一个默认的构造函数,你可以调用channelFactory()来指定一个工厂类(被bind()方法调用) |
Bootstrap localAddress(SocketAddress) | 指定Channel要绑定的本地address。如果没有提供,OS会选择一个随机的地址(从你网卡中选择),另外,你可以通过bind()或connect()来指定address |
Bootstrap option(ChannelOption option,T value) | 设置一个ChannelOption应用到新创建Channel的ChannelConfig(Channel创建后再调用此方法无效) |
Bootstrap attr(Attribute key, T value) | 指定新创建的Channel的一个属性(Channel创建后再调用此方法无效) |
Bootstrap handler(ChannelHandler) | 设置加到ChannelPipeline中的ChannelHandler来接收事件通知 |
Bootstrap clone() | 创建当前Bootstrap的一个克隆,它们共享同一个配置 |
Bootstrap remoteAddress(SocketAddress) | 设置远程地址。或者你可以在调用connect()方法时指定 |
ChannelFuture connect() | 连接到远端机器然后返回一个ChannelFuture,当连接操作完成时可以通过ChannelFuture来得到通知 |
ChannelFuture bind() | 绑定Channel然后返回一个ChannelFutre,当连接操作完成时可以通过ChannelFuture来得到通知。在这个方法之后,必须调用Channel.connect()方法来建立连接 |
启动一个客户端
Bootstrap负责为客户端创建channel,如下图所示:下面的代码展示了如何启动一个客户端:
EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group)//设置EventLoopGroup提供处理Channel事件的EventLoop .channel(NioSocketChannel.class) .handler(new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { System.out.println("Received data"); } } ); ChannelFuture future = bootstrap.connect( new InetSocketAddress("www.baidu.com", 80)); future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { if (channelFuture.isSuccess()) { System.out.println("Connection established"); } else { System.err.println("Connection attempt failed"); channelFuture.cause().printStackTrace(); } } } );
Channel和EventLoopGroup的兼容性
下面列出了io.netty.channel包的树形结构,你可以从包名和类名前缀看出NIO和OIO传输服务都有相应的EventLoopGroup和Channel实现。channel ├───nio │ NioEventLoopGroup ├───oio │ OioEventLoopGroup └───socket ├───nio │ NioDatagramChannel │ NioServerSocketChannel │ NioSocketChannel └───oio OioDatagramChannel OioServerSocketChannel OioSocketChannel
这种兼容性必须维护,你不能混合不同前缀的组件,比如NioEventLoopGroup和OioSocketChannel。下面的代码显示了一个错误的示范:
EventLoopGroup group = new NioEventLoopGroup();//指定了一个NIO EventLoopGroup实现 Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(OioSocketChannel.class) //指定一个OIO Channel实现类 .handler(new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0( ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception { System.out.println("Received data"); } } ); ChannelFuture future = bootstrap.connect( new InetSocketAddress("www.manning.com", 80)); future.syncUninterruptibly();
这段代码会引起一个IllegalStateException
Exception in thread "main" java.lang.IllegalStateException: incompatible event loop type: io.netty.channel.nio.NioEventLoop at io.netty.channel.AbstractChannel$AbstractUnsafe.register(AbstractChannel.java:464) at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:80) at io.netty.channel.SingleThreadEventLoop.register(SingleThreadEventLoop.java:74) ...
启动时,在你调用bind()或connect()之前,你必须调用下面的方法来设置必要的组件
* group()
* channel() 或 channnelFactory()
* handler()
如果没这么做也会导致一个IllegalStateException。handler()方法尤其重要,因为必须用它来配置ChannelPipeline。
启动服务端
ServerBootstrap类
下表列出了ServerBootstrap的方法:名称 | 描述 |
---|---|
group | 设置ServerBootstrap使用的EventLoopGroup |
channel | 设置要实例化的ServerChannel类 |
channelFactory | 如果Channel不能通过默认的构造函数创建,你就可以指定一个channelFactory |
localAddress | 指定Channel要绑定的本地address。如果没有提供,OS会选择一个随机的地址(从你网卡中选择),另外,你可以通过bind()或connect()来指定address |
option | 设置一个ChannelOption应用到新创建ServerChannel的ChannelConfig |
childOption | 指定一个ChannelOption来应用到接收的Channel的ChannelConfig |
attr | 指定新创建的ServerChannel的一个属性,通过bind()方法将属性设置到channel上,调用bind()后再调用此方法无效 |
childAttr | 应用一个属性来接收Channel |
handler | 设置要加到ServerChannel的ChannelPipeline的ChannelHander |
childHandler | 设置要添加到接收的Channel的ChannelPipeline的ChannelHander,与上一个方法的区别是,这个方法为接收的Channel(代表与远端机器绑定的socket)增加handler而handler()为ServerChannel增加handler |
clone | 克隆ServerBootstrap来接收不同的远端机器连接。它的配置与源ServerBootstrap相同 |
bind | 绑定ServerChannel然后返回一个ChannelFutre,当连接操作完成时可以通过ChannelFuture来得到通知。 |
启动一个Server
childHandler(), childAttr()和childOption()方法支持server应用特有的操作。下图显示了一个ServerBootstrap通过bind()方法创建一个ServerChannel,然后ServerChannel管理了几个子Channel。下面的代码展示了这个过程:
NioEventLoopGroup group = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap();//创建一个ServerBootstrap bootstrap.group(group)//设置EventLoopGroup来提供处理Channel事件的EventLoop .channel(NioServerSocketChannel.class) .childHandler(new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception { System.out.println("Received data"); } } ); //通过配置好的bootstrap来绑定channel ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { if (channelFuture.isSuccess()) { System.out.println("Server bound"); } else { System.err.println("Bound attempt failed"); channelFuture.cause().printStackTrace(); } } } );
从Channel中启动客户端
通过将EventLoop传入Bootstrap的group()方法来共享Channel的EventLoop。 因为所有Channel都分配给一个使用相同的线程EventLoop,这避免了额外的线程创建和相关的上下文切换。 这个共享解决方案下图所示:实现EventLoop共享可以通过调用group()方法设置EventLoop来实现:
ServerBootstrap bootstrap = new ServerBootstrap();//用来创建SocketChannel bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .childHandler( new SimpleChannelInboundHandler<ByteBuf>() { ChannelFuture connectFuture; @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { Bootstrap bootstrap = new Bootstrap();//创建一个Bootstrap来与远端建立连接 bootstrap.channel(NioSocketChannel.class).handler( new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0( ChannelHandlerContext ctx, ByteBuf in) throws Exception { System.out.println("Received data"); } } ); //使用同样的Eventloop设置给接收的(accepted)channel bootstrap.group(ctx.channel().eventLoop()); connectFuture = bootstrap.connect( new InetSocketAddress("www.manning.com", 80)); } @Override protected void channelRead0( ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception { if (connectFuture.isDone()) { // do something with the data } } } ); ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { if (channelFuture.isSuccess()) { System.out.println("Server bound"); } else { System.err.println("Bind attempt failed"); channelFuture.cause().printStackTrace(); } } } );
在一个启动过程中增加多个ChannelHandler
当你的应用比较复杂时就需要增加多个handler了,比如需要支持多种传输协议的应用。你可以部署很多个ChannelHandler到一个ChannelPipeline中。但是如果你只能设置一个ChannelHandler你该如何做呢?
Netty提供了一个ChannelInboundHandlerAdapter的特别的子类来满足这种情况。
public abstract class ChannelInitializer<C extends Channel> extends ChannelInboundHandlerAdapter
它定义了如下的方法:
protected abstract void initChannel(C ch) throws Exception;
这个方法提供了一个简单的方式来增加多个ChannelHandler到一个ChannelPipeline中。你只需要提供你ChannelInitializer的实现,一旦Channel与它的EventLoop注册好了后,你实现的initChannel()方法就会被调用。当这个方法返回后,ChannelInitializer的实例就会把自己从ChannelPipeline中移除。
下面定义ChannelInitializerImpl类然后通过bootstrap的childHandler()来注册它:
ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializerImpl());//注册一个ChannelInitializerImpl的实例来设置ChannelPipeline ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); future.sync(); final class ChannelInitializerImpl extends ChannelInitializer<Channel> { //增加handler到ChannelPipeline @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); //下面增加了2个ChannelHandler到pipeline pipeline.addLast(new HttpClientCodec()); pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE)); } } //创建一个AttributeKey以指定一个属性 final AttributeKey<Integer> id = new AttributeKey<Integer>("ID"); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(new NioEventLoopGroup()) .channel(NioSocketChannel.class) .handler( new SimpleChannelInboundHandler<ByteBuf>() { @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { Integer idValue = ctx.channel().attr(id).get();//获取id的值 // do something with the idValue } @Override protected void channelRead0( ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception { System.out.println("Received data"); } } ); bootstrap.option(ChannelOption.SO_KEEPALIVE,true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); bootstrap.attr(id, 123456);//将123456保存到id ChannelFuture future = bootstrap.connect( new InetSocketAddress("www.manning.com", 80)); future.syncUninterruptibly();
启动DatagramChannels
下面展示无连接协议的使用(UDP)。Netty提供了不同的DatagramChannel的实现,它们唯一的区别是你不用调用connect(),只需要bind(),如下所示:Bootstrap bootstrap = new Bootstrap(); bootstrap.group(new OioEventLoopGroup()).channel( OioDatagramChannel.class).handler(//指定了OioDatagramChannel new SimpleChannelInboundHandler<DatagramPacket>() { @Override public void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { // Do something with the packet } } ); //因为是无连接的协议,所有调用bind ChannelFuture future = bootstrap.bind(new InetSocketAddress(0)); future.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { if (channelFuture.isSuccess()) { System.out.println("Channel bound"); } else { System.err.println("Bind attempt failed"); channelFuture.cause().printStackTrace(); } } });
关闭
如果你想优雅的关闭你的应用,指的是干净的释放资源。你只需要注意几点,首先,你需要关闭EventLoopGroup,它会处理队列中的事件和任务然后释放活跃的线程。这可以通过调用EventLoopGroup.shutdownGracefully()来实现。这个调用会返回一个Future,当关闭操作完成时可以得到通知。注意,shutdownGracefully()方法是一个异步方法(所以会返回Future)。下面的代码展示了这个过程:
EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class); ... Future<?> future = group.shutdownGracefully(); //调用syncUninterruptibly()来阻塞直到group关闭 future.syncUninterruptibly();
相关文章推荐
- 《Netty_in_Action》中文正式出版:阿里工程师耗时2年精心翻译(含试读PDF)
- Netty In Action中文版 - 第六章:ChannelHandler
- 《Netty in Action》中文版—第六章 ChannelHandler和ChannelPipeline
- 《Netty_in_Action》中文正式出版:阿里工程师耗时2年精心翻译(含试读PDF)
- Netty In Action 读书笔记 - 第四章 传输
- Netty in Action (八) 第四章节 第一部分 传输服务迁移案例
- Netty In Action中文版 - 第六章:ChannelHandler
- Netty in Action (二)第一章节 第一部分 java网络编程
- Netty In Action 读书笔记 - 第九章 引导启动Netty应用
- Netty In Action中文版 - 第六章:ChannelHandler
- Netty in action thrid chapter 心得总结
- 《Netty in Action》中文版 — 第一部分前言
- Netty In Action中文版 - 第六章:ChannelHandler
- 《Netty in action》目录修复版本分享
- netty in action第四章-Transports(传输)
- Netty In Action中文版 - 第六章:ChannelHandler
- Netty in Action (十六) 第六章节 第二部分 ChannelHandlerContext和异常处理
- netty in action sixth chapter summarize
- Netty in Action (五) 第二章节 第一部分 搭建第一个Netty应用前的准备
- Netty In Action中文版 - 第六章:ChannelHandler