netty基础08_引导类
引导类的作用是对程序进行配置;
也就是将ChannelPipeline、 ChannelHandler 、 EventLoop等组件组织起来,成为一个可实际运行的应用程序
netty的引导类两种,都继承自 AbstractBootstrap:
Bootstrap ->用于引导客户端和无连接协议
ServerBootstrap ->用于引导服务器
服务端致力于使用一个父 Channel 来接受来自客户端的连接, 并创建子 Channel 以用于它们之间的通信;
客户端将最可能只需要一个单独的、 没有父 Channel 的 Channel 来用于所有的网络交互。
两种应用程序类型之间通用的引导步骤由 AbstractBootstrap 处理;
特定于客户端或者服务端的引导步骤分别由 Bootstrap 或 ServerBootstrap 处理。
1.引导客户端
Bootstrap 类被用于客户端或者使用了无连接协议的应用程序中。
api:
引导过程:
EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); //新建一个Bootstrap实例用来引导客户端 bootstrap.group(group) //设置 EventLoopGroup,提供用于处理 Channel事件的 EventLoop;这里用的是非阻塞流nio .channel(NioSocketChannel.class) //指定要使用的channel实现;与EventLoopGroup匹配这里也要用nio的; .handler(new SimpleChannelInboundHandler<ByteBuf>() { //设置由于处理入站事件的处理器 @Override protected void channeRead0( ChannelHandlerContext channelHandlerContext,ByteBuf byteBuf) 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(); } } } );
2. 引导服务端
ServerBootstrap用来引导服务端;
api
引导过程:
ServerChannel 的实现负责创建子 Channel,这些子 Channel 代表了已被接受的连接。 NioEventLoopGroup group = new NioEventLoopGroup(); ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(group) .channel(NioServerSocketChannel.class) .childHandler(new SimpleChannelInboundHandler<ByteBuf>() { @Override protected void channelRead0(ChannelHandlerContext ctx,ByteBuf byteBuf) throws Exception { System.out.println("Received 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("Bound attempt failed"); channelFuture.cause().printStackTrace(); } } } );
3. Channel 和 EventLoopGroup 的兼容性
netty可以使用阻塞流Oio和非阻塞流Nio;
Oio和Nio分别有其对应的Channel和EventLoopGroup实现;
可以通过前缀区分;
注意不能混用,例如:不能将Nio的EventLoopGroup配合Oio的Channel使用,否则会报 IllegalStateException;
4. 从 Channel 引导客户端
假设你的服务器正在处理一个客户端的请求, 这个请求需要它充当第三方系统的客户端。(例如代理服务器)
在这种情况下,需要从已经被接受的子 Channel 中引导一个客户端 Channel。
解决方案: 创建新的 Bootstrap 实例
缺点:
必需为每个新创建的客户端 Channel 定义另一个 EventLoop,会产生额外的线程;
被接受的子 Channel 和客户端 Channel 之间交换数据时不可避免的上下文切换 ;
另一种解决方案: 将已被接受的子 Channel 的 EventLoop 传递给 Bootstrap的 group()方法来共享该 EventLoop。
好处:因为分配给 EventLoop 的所有 Channel 都使用同一个线程,所以这避免了额外的线程创建,以及前面所提到的相关的上下文切换。
原则: 尽可能地重用 EventLoop,以减少线程创建所带来的开销;
代码实现:
ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .childHandler(new SimpleChannelInboundHandler<ByteBuf>() { //配置用来处理子channel的处理器 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"); } } ); bootstrap.group(ctx.channel().eventLoop()); //使用与分配给已被接受的子Channel相同的EventLoop connectFuture = bootstrap.connect(new InetSocketAddress("www.baidu.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));
5. 在引导过程中添加多个 ChannelHandler
引导类的引导过程中,通过 handler()或者 childHandler()方法来添加Channelhandeler,但只能添加一个;
有时需要添加多个Channelhandeler;
Netty 提供了一个特殊的 ChannelInboundHandlerAdapter 子类:ChannelInitializer
public abstract class ChannelInitializer<C extends Channel>extends ChannelInboundHandlerAdapter
ChannelInitializer的抽象方法:
protected abstract void initChannel(C ch) throws Exception;
可以实现这个方法,将多个Channelhandeler添加到 ChannelPipeline;
作用机制:
在引导时,通过 handler()或者 childHandler()添加 ChannelInitializer的实例;
一旦 Channel 被注册到了它的 EventLoop 之后,就会调用ChannelInitializer的initChannel()方法;
根据initChannel()方法的实现,可以在管道中添加多个Channelhandeler;
在initChannel()方法返回之后, ChannelInitializer 的实例将会从 ChannelPipeline 中移除它自己。
代码实现:
erverBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup()) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializerImpl()); //用自己的初始化器作为消息处理器 ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080)); future.sync(); //自定义的初始化器,本质上是一个消息处理器,作用是在管道中添加多个消息处理器 final class ChannelInitializerImpl extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); //得到管道对象 pipeline.addLast(new HttpClientCodec()); //在管道中添加第一个消息处理器-http客户端解码器,用来按http协议解码消息 pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE)); //在管道中添加第二个消息处理器-http消息聚合器,用来将一条完整的http消息聚合在一起后交给下一步处理 } }
6. 使用 Netty 的 ChannelOption 和属性
1)ChannelOption
ChannelOption用来保存Channel的配置;
常用的配置: 如keep-alive、超时属性以及缓冲区设置等;
可以使用引导类的option()方法来将 ChannelOption属性应用到引导;
添加到引导中的ChannelOption属性会自动应用到引导所创建的每个Channel中;
2)属性
有时需要在Channel中传递额外的属性;
Netty为此提供了 AttributeMap 抽象(一个由 Channel 和引导类提供的集合) 以及 AttributeKey<T>(一个用于插入和获取属性值的泛型类)。
3)代码实现
final AttributeKey<Integer> id = new AttributeKey<Integer>("ID"); //使用AttributeKey给Channel添加额外属性 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(); // 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()方法给Channel添加ChannelOption中的属性 .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000); bootstrap.attr(id, 123456); //调用attr()方法给额外属性赋值 ChannelFuture future = bootstrap.connect( new InetSocketAddress("www.baidu.com", 80)); future.syncUninterruptibly();
7.引导无连接协议
前面的引导代码示例使用的都是基于 TCP 协议的 SocketChannel;
Bootstrap 类也可以被用于无连接的协议;
为此,Netty 提供了各种 DatagramChannel 的实现。
唯一区别就是,不再调用 connect()方法,而是只调用 bind()方法
Bootstrap bootstrap = new Bootstrap(); bootstrap.group(new OioEventLoopGroup()) .channel(OioDatagramChannel.class) .handler(new SimpleChannelInboundHandler<DatagramPacket>(){ @Override public void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception { // Do something with the packet } } ); ChannelFuture future = bootstrap.bind(new InetSocketAddress(0)); //调用 bind()方法,因为该协议是无连接的
8.关闭
关闭netty程序时,需要关闭 EventLoopGroup;
关闭 EventLoopGroup时需要干净地释放资源: 处理任何挂起的事件和任务,并且随后释放所有活动的线程;
EventLoopGroup的shutdownGracefully()方法用来做这一工作;
shutdownGracefully()方法是一个异步操作,返回一个Future;
可以给返回的Future注册一个监听器以在关闭完成时获得通知;
EventLoopGroup group = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap(); bootstrap.group(group) .channel(NioSocketChannel.class); ... Future<?> future = group.shutdownGracefully(); //优雅关闭EventLoopGroup // block until the group has shutdown future.syncUninterruptibly();