第2章 第一款Netty应用程序

第2章 第一款Netty应用程序

    1. ChannelHandler用于构建应用业务逻辑。往往封装了为响应特定事件而编写的回调函数
    1. 本节主要讲解一个超级简单的Netty应用程序,回显服务: 客户端建立连接后,发送一个或多个消息。服务端收到后,将消息返回。

2.3 编写Echo服务器

Netty服务端至少需要两个部分: 一个ChannelHandler + 引导(Bootstrap)

2.3.1 ChannelHandler和业务逻辑

继承ChannelInboundHandlerAdapter类,感兴趣的入站方法:

    1. channelRead() - 对于每个传入的消息都要调用
    1. channelReadComplete() - 当前批量读取中的最后一条数据
    1. exceptionCaught() - 读取操作期间,有异常抛出时调用
代码:

/**
 * 这里对于所有的客户端连接来说,都会使用同一个 EchoServerHandler,因为其被标注为@Sharable,
 * 这将在后面的章节中讲到
 */
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter{

    /**
     * 每次传入的消息都要调用
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf in = (ByteBuf) msg;
        System.out.println(
                "Server received: " + in.toString(CharsetUtil.UTF_8));
        ctx.write(in);
    }

    /**
     * 读完当前批量中的最后一条数据后,触发channelReadComplete(...)方法
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx)
            throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * 异常捕获
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,
                                Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

解释:

    1. channelRead和channelReadComplete理解:当批量消息后最后一条数据被channelRead(...)后触发channelReadComplete事件。
    1. ctx.write(...)只是将消息暂时存放在ChannelOutboundBuffer中,等待flush(...)操作
    1. @Sharable注解:本质是声明该ChannelHandler全局单例。可被多个Channel安全的共享。标注了@Sharable注解的ChannelHandler请注意不能有对应的状态

2.3.2 引导服务器

    1. 引导服务器主要打开Netty的Channel。并分配对应的EventLoop和ChannelPipeline。
    1. 一个Channel只有一个ChannelPipeline。ChannelPipeline是由一组ChannelHandler组成的责任链。
代码:
public class EchoServer {
    private final int port;

    public EchoServer(int port) {
        this.port = port;
    }

    /**
     *
     *   EchoServerHandler 实现了业务逻辑;
     *   main()方法引导了服务器;
     *
     * 引导过程中所需要的步骤如下:
     *  创建一个 ServerBootstrap 的实例以引导和绑定服务器;
     *  创建并分配一个 NioEventLoopGroup 实例以进行事件的处理,如接受新连接以及读/写数据;
     *  指定服务器绑定的本地的 InetSocketAddress;
     *  使用一个 EchoServerHandler 的实例初始化每一个新的 Channel;
     *  调用 ServerBootstrap.bind()方法以绑定服务器。
     */
    public static void main(String[] args)
            throws Exception {

        int port = 9080;
        new EchoServer(port).start();
    }

    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
                    .channel(NioServerSocketChannel.class)
                    //使用指定的端口设置套接字地址
                    .localAddress(new InetSocketAddress(port))
                    //添加一个EchoServerHandler 到子Channel的 ChannelPipeline
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    });
            // 实际项目中,b.bind().sync()可以省略
            //异步地绑定服务器调用 sync()方法阻塞等待直到绑定完成
            ChannelFuture f = b.bind().sync();
            System.out.println(EchoServer.class.getName() +
                    " started and listening for connections on " + f.channel().localAddress());
            // 实际项目中,f.channel().closeFuture().sync()可以省略
            //获取 Channel 的CloseFuture,并且阻塞当前线程直到它完成
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }
}

2.4 编写Echo客户端

客户端将会:

    1. 建立连接
    1. 发送消息
    1. 关闭连接

2.4.1 ChannelHandler客户端逻辑

    1. Java是通过GC可达性分析来实现垃圾回收。对于Netty传输中的ByteBuf,使用的是引用计数算法。也就是说:如果你使用了Netty,需要你亲自考虑是否需要手动释放对象。判断方法,后文将会给出
    1. 扩展SimpleChannelInboundHandler类处理任务的Handler,无需手动释放对象。SimpleChannelInboundHandler.java中方法channelRead()中会负责释放引用。
    1. 客户端发送消息条数和服务端接收的消息条数是不对应的。除非处理了TCP的粘包黏包。
代码:
// SimpleChannelInboundHandler<T>中channelRead方法负责释放对象msg引用
public abstract class SimpleChannelInboundHandler<I> ...{
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        boolean release = true;
        try {
        // ...
        } finally {
            if (autoRelease && release) {
            	  // 减少对象msg引用计数
                ReferenceCountUtil.release(msg);
            }
        }
    }
}    
问:ChannelHandler中何时需要主动释放引用?
    1. 扩展的类不是: SimpleChannelInboundHandler,且该对象msg不会传给下一个ChannelHandler
    1. 扩展的类不是: SimpleChannelInboundHandler,且该对象msg不会被ctx.write(...)

2.4.2 引导客户端

引导客户端关键代码

代码:
public class EchoServer {
    private final int port;

    public EchoServer(int port) {
        this.port = port;
    }

    /**
     *
     *   EchoServerHandler 实现了业务逻辑;
     *   main()方法引导了服务器;
     *
     * 引导过程中所需要的步骤如下:
     *  创建一个 ServerBootstrap 的实例以引导和绑定服务器;
     *  创建并分配一个 NioEventLoopGroup 实例以进行事件的处理,如接受新连接以及读/写数据;
     *  指定服务器绑定的本地的 InetSocketAddress;
     *  使用一个 EchoServerHandler 的实例初始化每一个新的 Channel;
     *  调用 ServerBootstrap.bind()方法以绑定服务器。
     */
    public static void main(String[] args)
            throws Exception {

        int port = 9080;
        new EchoServer(port).start();
    }

    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
                    .channel(NioServerSocketChannel.class)
                    //使用指定的端口设置套接字地址
                    .localAddress(new InetSocketAddress(port))
                    //添加一个EchoServerHandler 到子Channel的 ChannelPipeline
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoServerHandler());
                        }
                    });
            // 实际项目中,b.bind().sync()可以省略
            //异步地绑定服务器调用 sync()方法阻塞等待直到绑定完成
            ChannelFuture f = b.bind().sync();
            System.out.println(EchoServer.class.getName() +
                    " started and listening for connections on " + f.channel().localAddress());
            // 实际项目中,f.channel().closeFuture().sync()可以省略
            //获取 Channel 的CloseFuture,并且阻塞当前线程直到它完成
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully().sync();
        }
    }
}
/**
 * 这里对于所有的客户端连接来说,都会使用同一个 EchoServerHandler,因为其被标注为@Sharable,
 * 这将在后面的章节中讲到
 */
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter{

    /**
     * 每次传入的消息都要调用
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf in = (ByteBuf) msg;
        System.out.println(
                "Server received: " + in.toString(CharsetUtil.UTF_8));
        ctx.write(in);
    }

    /**
     * 读完当前批量中的最后一条数据后,触发channelReadComplete(...)方法
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx)
            throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
                .addListener(ChannelFutureListener.CLOSE);
    }

    /**
     * 异常捕获
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx,
                                Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

posted @   economies  阅读(33)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!
点击右上角即可分享
微信分享提示