Netty In Action中文版 - 第九章:引导Netty应用程序
本章介绍
- 引导客户端和服务器
- 从Channel引导客户端
- 添加多个ChannelHandler
- 使用通道选项和属性
上一章学习了编写自己的ChannelHandler和编解码器并将它们添加到Channel的ChannelPipeline中。本章将讲解如何将它们结合在一起使用。
Netty提供了简单统一的方法来引导服务器和客户端。引导是配置Netty服务器和客户端程序的一个过程,Bootstrap允许这些应用程序很容易的重复使用。Netty程序的客户端和服务器都可以使用Bootstrap,其目的是简化编码过程,Bootstrap还提供了一个机制就是让一些组件(channels,pipeline,handlers等等)都可以在后台工作。本章将具体结合以下部分一起使用开发Netty程序:
- EventLoopGroup
- Channel
- 设置ChannelOption
- Channel被注册后将调用ChannelHandler
- 添加指定的属性到Channel
- 设置本地和远程地址
- 绑定、连接(取决于类型)
知道如何使用各个Bootstrap后就可以使用它们配置服务器和客户端了。本章还将学习在什么会后可以共享一个Bootstrap以及为什么这样做,结合我们之前学习的知识点来编写Netty程序。
9.1 不同的引导类型
9.2 引导客户端和无连接协议
9.2.1 引导客户端的方法
- group(...),设置EventLoopGroup,EventLoopGroup用来处理所有通道的IO事件
- channel(...),设置通道类型
- channelFactory(...),使用ChannelFactory来设置通道类型
- localAddress(...),设置本地地址,也可以通过bind(...)或connect(...)
- option(ChannelOption<T>, T),设置通道选项,若使用null,则删除上一个设置的ChannelOption
- attr(AttributeKey<T>, T),设置属性到Channel,若值为null,则指定键的属性被删除
- handler(ChannelHandler),设置ChannelHandler用于处理请求事件
- clone(),深度复制Bootstrap,Bootstrap的配置相同
- remoteAddress(...),设置连接地址
- connect(...),连接远程通道
- bind(...),创建一个新的Channel并绑定
9.2.2 怎么引导客户端
- package netty.in.action;
- import io.netty.bootstrap.Bootstrap;
- import io.netty.buffer.ByteBuf;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelFutureListener;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.SimpleChannelInboundHandler;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.nio.NioSocketChannel;
- /**
- * 引导配置客户端
- *
- * @author c.k
- *
- */
- public class BootstrapingClient {
- public static void main(String[] args) throws Exception {
- EventLoopGroup group = new NioEventLoopGroup();
- Bootstrap b = new Bootstrap();
- b.group(group).channel(NioSocketChannel.class).handler(new SimpleChannelInboundHandler<ByteBuf>() {
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
- System.out.println("Received data");
- msg.clear();
- }
- });
- ChannelFuture f = b.connect("127.0.0.1", 2048);
- f.addListener(new ChannelFutureListener() {
- @Override
- public void operationComplete(ChannelFuture future) throws Exception {
- if (future.isSuccess()) {
- System.out.println("connection finished");
- } else {
- System.out.println("connection failed");
- future.cause().printStackTrace();
- }
- }
- });
- }
- }
9.2.3 选择兼容通道实现
9.3 使用ServerBootstrap引导服务器
9.3.1 引导服务器的方法
- group(...),设置EventLoopGroup事件循环组
- channel(...),设置通道类型
- channelFactory(...),使用ChannelFactory来设置通道类型
- localAddress(...),设置本地地址,也可以通过bind(...)或connect(...)
- option(ChannelOption<T>, T),设置通道选项,若使用null,则删除上一个设置的ChannelOption
- childOption(ChannelOption<T>, T),设置子通道选项
- attr(AttributeKey<T>, T),设置属性到Channel,若值为null,则指定键的属性被删除
- childAttr(AttributeKey<T>, T),设置子通道属性
- handler(ChannelHandler),设置ChannelHandler用于处理请求事件
- childHandler(ChannelHandler),设置子ChannelHandler
- clone(),深度复制ServerBootstrap,且配置相同
- bind(...),创建一个新的Channel并绑定
9.3.2 怎么引导服务器
- package netty.in.action;
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.buffer.ByteBuf;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelFutureListener;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.SimpleChannelInboundHandler;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
- /**
- * 引导服务器配置
- * @author c.k
- *
- */
- public class BootstrapingServer {
- public static void main(String[] args) throws Exception {
- EventLoopGroup bossGroup = new NioEventLoopGroup(1);
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- ServerBootstrap b = new ServerBootstrap();
- b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
- .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
- System.out.println("Received data");
- msg.clear();
- }
- });
- ChannelFuture f = b.bind(2048);
- f.addListener(new ChannelFutureListener() {
- @Override
- public void operationComplete(ChannelFuture future) throws Exception {
- if (future.isSuccess()) {
- System.out.println("Server bound");
- } else {
- System.err.println("bound fail");
- future.cause().printStackTrace();
- }
- }
- });
- }
- }
9.4 从Channel引导客户端
有时候需要从另一个Channel引导客户端,例如写一个代理或需要从其他系统检索数据。从其他系统获取数据时比较常见的,有很多Netty应用程序必须要和企业现有的系统集成,如Netty程序与内部系统进行身份验证,查询数据库等。
- package netty.in.action;
- import java.net.InetSocketAddress;
- import io.netty.bootstrap.Bootstrap;
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.buffer.ByteBuf;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelFutureListener;
- import io.netty.channel.ChannelHandlerContext;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.SimpleChannelInboundHandler;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
- import io.netty.channel.socket.nio.NioSocketChannel;
- /**
- * 从Channel引导客户端
- *
- * @author c.k
- *
- */
- public class BootstrapingFromChannel {
- public static void main(String[] args) throws Exception {
- EventLoopGroup bossGroup = new NioEventLoopGroup(1);
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- ServerBootstrap b = new ServerBootstrap();
- b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
- .childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
- ChannelFuture connectFuture;
- @Override
- public void channelActive(ChannelHandlerContext ctx) throws Exception {
- Bootstrap b = new Bootstrap();
- b.channel(NioSocketChannel.class).handler(
- new SimpleChannelInboundHandler<ByteBuf>() {
- @Override
- protected void channelRead0(ChannelHandlerContext ctx,
- ByteBuf msg) throws Exception {
- System.out.println("Received data");
- msg.clear();
- }
- });
- b.group(ctx.channel().eventLoop());
- connectFuture = b.connect(new InetSocketAddress("127.0.0.1", 2048));
- }
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg)
- throws Exception {
- if (connectFuture.isDone()) {
- // do something with the data
- }
- }
- });
- ChannelFuture f = b.bind(2048);
- f.addListener(new ChannelFutureListener() {
- @Override
- public void operationComplete(ChannelFuture future) throws Exception {
- if (future.isSuccess()) {
- System.out.println("Server bound");
- } else {
- System.err.println("bound fail");
- future.cause().printStackTrace();
- }
- }
- });
- }
- }
9.5 添加多个ChannelHandler
在所有的例子代码中,我们在引导过程中通过handler(...)或childHandler(...)都只添加了一个ChannelHandler实例,对于简单的程序可能足够,但是对于复杂的程序则无法满足需求。例如,某个程序必须支持多个协议,如HTTP、WebSocket。若在一个ChannelHandler中处理这些协议将导致一个庞大而复杂的ChannelHandler。Netty通过添加多个ChannelHandler,从而使每个ChannelHandler分工明确,结构清晰。
- package netty.in.action;
- import io.netty.bootstrap.ServerBootstrap;
- import io.netty.channel.Channel;
- import io.netty.channel.ChannelFuture;
- import io.netty.channel.ChannelInitializer;
- import io.netty.channel.EventLoopGroup;
- import io.netty.channel.nio.NioEventLoopGroup;
- import io.netty.channel.socket.nio.NioServerSocketChannel;
- import io.netty.handler.codec.http.HttpClientCodec;
- import io.netty.handler.codec.http.HttpObjectAggregator;
- /**
- * 使用ChannelInitializer初始化ChannelHandler
- * @author c.k
- *
- */
- public class InitChannelExample {
- public static void main(String[] args) throws Exception {
- EventLoopGroup bossGroup = new NioEventLoopGroup(1);
- EventLoopGroup workerGroup = new NioEventLoopGroup();
- ServerBootstrap b = new ServerBootstrap();
- b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
- .childHandler(new ChannelInitializerImpl());
- ChannelFuture f = b.bind(2048).sync();
- f.channel().closeFuture().sync();
- }
- static final class ChannelInitializerImpl extends ChannelInitializer<Channel>{
- @Override
- protected void initChannel(Channel ch) throws Exception {
- ch.pipeline().addLast(new HttpClientCodec())
- .addLast(new HttpObjectAggregator(Integer.MAX_VALUE));
- }
- }
- }
9.6 使用通道选项和属性
比较麻烦的是创建通道后不得不手动配置每个通道,为了避免这种情况,Netty提供了ChannelOption来帮助引导配置。这些选项会自动应用到引导创建的所有通道,可用的各种选项可以配置底层连接的详细信息,如通道“keep-alive(保持活跃)”或“timeout(超时)”的特性。
- public static void main(String[] args) {
- //创建属性键对象
- final AttributeKey<Integer> id = AttributeKey.valueOf("ID");
- //客户端引导对象
- Bootstrap b = new Bootstrap();
- //设置EventLoop,设置通道类型
- b.group(new NioEventLoopGroup()).channel(NioSocketChannel.class)
- //设置ChannelHandler
- .handler(new SimpleChannelInboundHandler<ByteBuf>() {
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg)
- throws Exception {
- System.out.println("Reveived data");
- msg.clear();
- }
- @Override
- public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
- //通道注册后执行,获取属性值
- Integer idValue = ctx.channel().attr(id).get();
- System.out.println(idValue);
- //do something with the idValue
- }
- });
- //设置通道选项,在通道注册后或被创建后设置
- b.option(ChannelOption.SO_KEEPALIVE, true).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000);
- //设置通道属性
- b.attr(id, 123456);
- ChannelFuture f = b.connect("www.manning.com",80);
- f.syncUninterruptibly();
- }
前面都是引导基于TCP的SocketChannel,引导也可以用于无连接的传输协议如UDP,Netty提供了DatagramChannel,唯一的区别是不会connecte(...),只能bind(...)。看下面代码:
- public static void main(String[] args) {
- Bootstrap b = new Bootstrap();
- b.group(new OioEventLoopGroup()).channel(OioDatagramChannel.class)
- .handler(new SimpleChannelInboundHandler<DatagramPacket>() {
- @Override
- protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket msg)
- throws Exception {
- // do something with the packet
- }
- });
- ChannelFuture f = b.bind(new InetSocketAddress(0));
- f.addListener(new ChannelFutureListener() {
- @Override
- public void operationComplete(ChannelFuture future) throws Exception {
- if (future.isSuccess()) {
- System.out.println("Channel bound");
- } else {
- System.err.println("Bound attempt failed");
- future.cause().printStackTrace();
- }
- }
- });
- }
Netty有默认的配置设置,多数情况下,我们不需要改变这些配置,但是在需要时,我们可以细粒度的控制如何工作及处理数据。
9.7 Summary
In this chapter you learned how to bootstrap your Netty-based server and client implementation. You learned how you can specify configuration options that affect the and how you can use attributes to attach information to a channel and use it later. You also learned how to bootstrap connectionless protocol-based applications and how they are different from connection-based ones. The next chapters will focus on Netty in Action by using it to implement real-world applications. This will help you extract all interesting pieces for reuse in your next application. At this point you should be able to start coding!