Bootstrap类
引导类的层次结构包括一个抽象的父类和两个具体的引导子类:(ctrl+shift+alt+u)
服务器致力于使用一个父Channel来接受来自客户端的连接,并创建子Channel以用于它们之间的通信;而客户端将最可能只需要一个单独的、没有父Channel的Channel来用于所有的网络交互。两种应用程序类型之间通用的引导步骤由AbstractBootstrap处理,而特定于客户端或者服务器的引导步骤则分别由Bootstrap或ServerBootstrap处理。
AbstractBootstrap类的完整声明是:
public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable
在这个签名中,子类型B是其父类型的一个类型参数,因此可以返回到运行时实例的引用以支持方法的链式调用(也就是所谓的流式语法)。
public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel>public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel>
引导客户端和无连接协议
Bootstrap类被用于客户端或者使用了无连接协议的应用程序中。
名称 | 描述 |
Bootstrap group(EventLoopGroup) | 设置用于处理Channel所有事件的EventLoopGroup |
Bootstrap channel(Class<? extends C>) Bootstrap channelFactory(ChannelFactory<? extends C>) |
channel()方法指定了Channel的实现类。如果该实现类没提供默认的构造函数,可以通过调用channelFactory()方法来指定一个工厂类,它将会被bind()方法调用 |
Bootstrap localAddress(SocketAddres) | 指定Channel应该绑定到的本地地址。如果没有指定,则将由操作系统创建一个随机的地址。或者,也可以通过bind()或者connect()方法指定localAddress |
<T> Bootstrap option(ChannelOption<T> option,T value) | 设置ChannelOption,其将被应用到每个新创建的Channel的ChannelConfig。这些选项将会通过bind()或者connect()方法设置到Channel,不管哪个先被调用。这个方法在Channel已经被创建后再调用将不会有任何的效果。支持的ChannelOption取决于使用的Channel类型。 |
<T> Bootstrap attr(Attribute<T> key, T value) | 指定新创建的Channel的属性值。这些属性值是通过bind()或者connect()方法设置到Channel的,具体取决于谁最先被调用。这个方法在Channel被创建后将不会有任何的效果。 |
Bootstraphandler(ChannelHandler) | 设置将被添加到ChannelPipeline以接收事件通知的ChannelHandler |
Bootstrap clone() | 创建一个当前Bootstrap的克隆,其具有和原始的Bootstrap相同的设置信息 |
Bootstrap remoteAddress(SocketAddress) | 设置远程地址。或者,也可以通过connect()方法来指定它 |
ChannelFuture connect() | 连接到远程节点并返回一个ChannelFuture,其将 会在连接操作完成后接收到通知 |
ChannelFuture bind() | 绑定Channel并返回一个ChannelFuture,其将 会在绑定操作完成后接收到通知,在那之后必须调用Channel.connect()方法来建立连接 |
引导客户端
Bootstrap类负责为客户端和使用无连接协议的应用程序创建Channel
在引导的过程中,在调用bind()或者connect()方法之前,必须调用以下方法来设置所需的组件:
- group();
- channel()或者channelFactory();
- handler()。
如果不这样做,则将会导致IllegalStateException。对handler()方法的调用尤其重要,因为它需要配置好ChannelPipeline。
不能混用具有不同前缀的组件,如NioEventLoopGroup和OioSocketChannel。
引导服务器
ServerBootstrap类
名称 | 描述 |
group | 设置ServerBootstrap要用的EventLoopGroup。这个EventLoopGroup将用于ServerChannel和被接受的子Channel的I/O处理 |
channel | 设置将要被实例化的ServerChannel类 |
channelFactory | 如果不能通过默认的构造函数创建Channel,那么可以提供一个Channel-Factory |
localAddress | 指定ServerChannel应该绑定到的本地地址。如果没有指定,则将由操作系统使用一个随机地址。或者,可以通过bind()方法来指定该localAddress |
option | 指定要应用到新创建的ServerChannel的ChannelConfig的ChannelOption。这些选项将会通过bind()方法设置到Channel。在bind()方法被调用之后,设置或者改变ChannelOption都不会有任何的效果。所支持的ChannelOption取决于所使用的Channel类型。 |
childOption | 指定当子Channel被接受时,应用到子Channel的ChannelConfig的ChannelOption。所支持的ChannelOption取决于所使用的Channel的类型。 |
attr | 指定ServerChannel上的属性,属性将会通过bind()方法设置给Channel。在调用bind()方法之后改变它们将不会有任何的效果 |
childAttr | 将属性设置给已经被接受的子Channel。接下来的调用将不会有任何的效果 |
handler | 设置被添加到ServerChannel的ChannelPipeline中的ChannelHandler。更加常用的方法参见childHandler() |
childHandler | 设置将 被添加到 已被接受 的子Channel的ChannelPipeline中的Channel-Handler。handler()方法和childHandler()方法之间的区别是:前者所添加的ChannelHandler由接受子Channel的ServerChannel处理,而childHandler()方法所添加的ChannelHandler将由已被接受的子Channel处理,其代表一个绑定到远程节点的套接字 |
clone | 克隆一个设置和原始的ServerBootstrap相同的ServerBootstrap |
bind | 绑定ServerChannel并且返回一个ChannelFuture,其将会在绑定操作完成后收到通知(带着成功或者失败的结果) |
引导服务器
childHandler()、childAttr()和childOption(),这些调用支持特别用于服务器应用程序的操作。具体来说,ServerChannel的实现负责创建子Channel,这些子Channel代表了已被接受的连接。因此,负责引导ServerChannel的ServerBootstrap提供了这些方法,以简化将设置应用到已被接受的子Channel的ChannelConfig的任务。
从Channel引导客户端
假设你的服务器正在处理一个客户端的请求,这个请求需要它充当第三方系统的客户端。当一个应用程序(如一个代理服务器)必须要和组织现有的系统(如Web服务或者数据库)集成时,就可能发生这种情况。在这种情况下,将需要从已经被接受的子Channel中引导一个客户端Channel。
通过将已被接受的子Channel的EventLoop传递给Bootstrap的group()方法来共享该EventLoop。因为分配给EventLoop的所有Channel都使用同一个线程,所以这避免了额外的线程创建,以及前面所提到的相关的上下文切换。
其核心是通过ChannelHandlerContext获取当前channel的eventLoop。
尽可能地重用EventLoop,以减少线程创建所带来的开销。
在引导过程中添加多个ChannelHandler
只需要简单地向Bootstrap或ServerBootstrap的实例提供你的ChannelInitializer实现即可,并且一旦Channel被注册到了它的EventLoop之后,就会调用你的initChannel()版本。在该方法返回之后,ChannelInitializer的实例将会从ChannelPipeline中移除它自己。
使用Netty的ChannelOption和属性
可以使用option()方法来将ChannelOption应用到引导。你所提供的值将会被自动应用到引导所创建的所有Channel。可用的ChannelOption包括了底层连接的详细信息,如keep-alive或者超时属性以及缓冲区设置。
像Channel这样的组件可能甚至会在正常的Netty生命周期之外被使用。在某些常用的属性和数据不可用时,Netty提供了AttributeMap抽象(一个由Channel和引导类提供的集合)以及AttributeKey<T>(一个用于插入和获取属性值的泛型类)。使用这些工具,便可以安全地将任何类型的数据项与客户端和服务器Channel(包含ServerChannel的子Channel)相关联了。
package netty.in.action;import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.CompositeByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.AttributeKey;
import io.netty.util.ByteProcessor;
import io.netty.util.concurrent.ScheduledFuture;import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Date;
import java.util.concurrent.TimeUnit;public class EchoClient {final ByteBuf buf = Unpooled.copiedBuffer("Hello,王宝强", Charset.forName("UTF-8"));final AttributeKey<Integer> id = AttributeKey.newInstance("ID");//创建一个AttributeKey以标识该属性public void connect(int port, String host) throws Exception {// 配置客户端NIO线程组EventLoopGroup group = new NioEventLoopGroup();try {Bootstrap b = new Bootstrap();b.group(group).channel(NioSocketChannel.class)//设置ChannelOption,其将在connect()或者bind()方法被调用时被设置到已经创建的Channel上.option(ChannelOption.SO_KEEPALIVE, true).option(ChannelOption.CONNECT_TIMEOUT_MILLIS,5000).attr(id, 123456)//设置属性.handler(new ChildChannelHandler2());// 发起异步连接操作ChannelFuture f = b.connect(host, port).sync();// 等待客户端链路关闭f.channel().closeFuture().sync();} finally {// 优雅退出,释放NIO线程组group.shutdownGracefully();}}private class ChildChannelHandler2 extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {System.out.println("客户端启动……");ch.pipeline().addLast("text", new ChannelInboundHandlerAdapter() {public void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println(ctx.channel().attr(id).get());//获取属性ctx.writeAndFlush(buf);}public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {ByteBuf buf = (ByteBuf) msg;byte[] req = new byte[buf.readableBytes()];buf.readBytes(req);String body = new String(req, "UTF-8");System.out.println(body);ScheduledFuture<?> future = ctx.channel().eventLoop().schedule(new Runnable() {@Overridepublic void run() {System.out.println("开启异步线程");}}, 1, TimeUnit.SECONDS);
// future.cancel(false);ctx.channel().eventLoop().scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {ctx.writeAndFlush(Unpooled.copiedBuffer("发送心跳" + new Date(), Charset.forName("UTF-8")));}}, 1, 5, TimeUnit.SECONDS);}});}}private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {protected void initChannel(SocketChannel ch) throws Exception {System.out.println("客户端启动……");ByteBuf bufs = Unpooled.copiedBuffer("pipeline发送的数据->", Charset.forName("UTF-8"));ch.pipeline().write(bufs);//通过调用ChannelPipeline的write方法将数据写入通道,但是不刷新ch.pipeline().addLast("text", new ChannelInboundHandlerAdapter() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {ctx.channel().write(Unpooled.copiedBuffer("通过ChannelHandlerContext获取的channel发送的消息->",Charset.forName("UTF-8")));//通过ChannelHandlerContext获取的channel发送的消息->CompositeByteBuf messageBuf = Unpooled.compositeBuffer();ByteBuf headerBuf = buf;ByteBuf bodyBuf = buf;messageBuf.addComponent(bodyBuf);//将ByteBuf实例追加到CompositeByteBufmessageBuf.addComponent(headerBuf);for (ByteBuf buf : messageBuf) {//遍历所有ByteBufSystem.out.println(buf);byte[] req = new byte[buf.readableBytes()];buf.readBytes(req);String body = new String(req, "UTF-8");System.out.println("复合缓冲区:" + body);}ctx.writeAndFlush(buf);}public void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {ByteBuf buf = (ByteBuf) msg;ByteBuf copyBuf = ((ByteBuf) msg).copy();
// System.out.println(buf.refCnt());//返回此对象的引用计数。如果为0,则表示此对象已被释放。
// buf.release();//释放引用计数对象for (int i = 0; i < buf.capacity(); i++) {byte b = buf.getByte(i);if ((char) b >= 'a' && (char) b <= 'z' || (char) b >= 'A' && (char) b <= 'Z' || (char) b == ',')System.out.println("i=" + (char) b);}int i = buf.forEachByte(new ByteProcessor() {@Overridepublic boolean process(byte value) throws Exception {byte[] b = ",".getBytes();if (b[0] != value)return true;elsereturn false;}});System.out.println("i=" + i + " value=" + (char) buf.getByte(i));ByteBuf sliced = buf.slice(0, 2);sliced.setByte(0, (byte) 'h');byte[] req = new byte[buf.readableBytes()];buf.readBytes(req);String body = new String(req, "UTF-8");System.out.println(body);ctx.fireChannelRead(copyBuf);}});ch.pipeline().addLast("text2", new ChannelInboundHandlerAdapter() {public void channelRead(ChannelHandlerContext ctx, Object msg)throws Exception {ByteBuf buf = (ByteBuf) msg;byte[] req = new byte[buf.readableBytes()];buf.readBytes(req);String body = new String(req, "UTF-8");System.out.println("text2:" + body);ByteBuf bufs = Unpooled.copiedBuffer("test2发送的数据", Charset.forName("UTF-8"));ctx.writeAndFlush(bufs);ctx.close();}});
// ch.pipeline().remove("text2");}}public static void main(String[] args) throws Exception {int port = 8080;new EchoClient().connect(port, "127.0.0.1");}
}
引导DatagramChannel
前面的引导代码示例使用的都是基于TCP协议的SocketChannel,但是Bootstrap类也可以被用于无连接的协议。为此,Netty提供了各种DatagramChannel的实现。唯一区别就是,不再调用connect()方法,而是只调用bind()方法:
Bootstrap b = new Bootstrap();b.group(new OioEventLoopGroup()).channel(OioDatagramChannel.class).handler(new SimpleChannelInboundHandler<DatagramPacket>() {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("打印Active");}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {System.out.println("打印");}});// 发起异步连接操作ChannelFuture f = b.bind(new InetSocketAddress(port));//调用bind()方法,因为该协议是无连接的f.addListener(new ChannelFutureListener() {@Overridepublic voidoperationComplete(ChannelFuture channelFuture) throws Exception {if (channelFuture.isSuccess()) {System.out.println("通道绑定");} else {System.err.println("绑定请求失败");channelFuture.cause().printStackTrace();}}});// 等待客户端链路关闭f.channel().closeFuture().sync();
关闭
最重要的是,你需要关闭EventLoopGroup,它将处理任何挂起的事件和任务,并且随后释放所有活动的线程。这就是调用EventLoopGroup.shutdownGracefully()方法的作用。这个方法调用将会返回一个Future,这个Future将在关闭完成时接收到通知。需要注意的是,shutdownGracefully()方法也是一个异步的操作,所以你需要阻塞等待直到它完成,或者向所返回的Future注册一个监听器以在关闭完成时获得通知。
参考《Netty实战》