netty demo

本文代码git地址,可点击拉取,本地运行

https://gitee.com/lhy102/first-netty/tree/master/nettyDemo

项目结构

netty demo_第1张图片

服务端handler

//1@Sharable 标识这类的实例之间可以在 channel 里面共享
@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));        //2
        ctx.write(in);                            //3将所接收的消息返回给发送者。注意,这还没有冲刷数据
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)//4冲刷所有待审消息到远程节点。关闭通道后,操作完成
                .addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();                //5
        ctx.close();                            //6关闭通道
    }
}

@Sharable 标识这类的实例之间可以在 channel 里面共享

覆盖 channelRead因为我们需要处理所有接收到的数据。

覆盖 exceptionCaught 使我们能够应对任何 Throwable 的子类型
每个 Channel 都有一个关联的 ChannelPipeline,它代表了 ChannelHandler 实例的链。适配器处理的实现只是将一个处理方法调用转发到链中的下一个处理器。因此,如果一个 Netty 应用程序不覆盖exceptionCaught ,那么这些错误将最终到达 ChannelPipeline,并且结束警告将被记录。出于这个原因,你应该提供至少一个 实现 exceptionCaught 的 ChannelHandler。

关键点要牢记:
ChannelHandler 是给不同类型的事件调用
应用程序实现或扩展 ChannelHandler 挂接到事件生命周期和提供自定义应用逻辑。

服务端代码:

public class EchoServer {

    private final int port;

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

    public void start() throws Exception {
        NioEventLoopGroup group = new NioEventLoopGroup(); //3创建 EventLoopGroup
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)                                //4创建 ServerBootstrap
                    .channel(NioServerSocketChannel.class)        //5指定使用 NIO 的传输 Channel
                    .localAddress(new InetSocketAddress(port))    //6设置 socket 地址使用所选的端口
                    //7添加 EchoServerHandler 到 Channel 的 ChannelPipeline
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch)
                                throws Exception {
                            ch.pipeline().addLast(
                                    new EchoServerHandler());
                        }
                    });

            ChannelFuture f = b.bind().sync();            //8绑定的服务器;sync 等待服务器关闭
            System.out.println(EchoServer.class.getName() + " 服务端开始监听started and listen on " + f.channel().localAddress());
            f.channel().closeFuture().sync();            //9关闭 channel 和 块,直到它被关闭
        } finally {
            group.shutdownGracefully().sync();            //10关闭 EventLoopGroup,释放所有资源。
        }
    }

    public static void main(String[] args) throws Exception {
        new EchoServer(8080).start();
    }

}

第7步是关键:在这里我们使用一个特殊的类,ChannelInitializer 。当一个新的连接被接受,一个新的子 Channel 将被创建, ChannelInitializer 会添加我们EchoServerHandler 的实例到 Channel 的 ChannelPipeline。正如我们如前所述,如果有入站信息,这个处理器将被通知。

客户端Handler

//1 @Sharable标记这个类的实例可以在 channel 里共享
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        //2 当被通知该 channel 是活动的时候就发送信息
        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
    }

    @Override
    public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
        //3 记录接收到的消息
        System.out.println("客户端消息——Client received: " + in.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        //4记录日志错误并关闭 channel
        cause.printStackTrace();
        ctx.close();
    }
}

channelActive() - 服务器的连接被建立后调用
channelRead0() - 数据后从服务器接收到调用
exceptionCaught() - 捕获一个异常时调用

建立连接后该 channelActive() 方法被调用一次。 一旦建立了连接,字节序列被发送到服务器

channelRead0() 接收到数据时被调用
注意,由服务器所发送的消息可以以块的形式被接收。即,当服务器发送 5 个字节是不是保证所有的 5 个字节会立刻收到 - 即使是只有 5 个字节,channelRead0() 方法可被调用两次,第一次用一个ByteBuf(Netty的字节容器)装载3个字节和第二次一个 ByteBuf 装载 2 个字节。唯一要保证的是,该字节将按照它们发送的顺序分别被接收。

SimpleChannelInboundHandler vs. ChannelInboundHandler
何时用这两个要看具体业务的需要。在客户端,当 channelRead0() 完成,我们已经拿到的入站的信息。当方法返回时,SimpleChannelInboundHandler 会小心的释放对 ByteBuf(保存信息) 的引用。而在 EchoServerHandler,我们需要将入站的信息返回给发送者,由于 write() 是异步的,在 channelRead() 返回时,可能还没有完成。所以,我们使用 ChannelInboundHandlerAdapter,无需释放信息。最后在 channelReadComplete() 我们调用 ctxWriteAndFlush() 来释放信息。

客户端代码

public class EchoClient {

    private final String host;
    private final int port;

    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public void start() throws Exception {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //1 创建 Bootstrap
            Bootstrap b = new Bootstrap();
            //2 34行含义: 指定EventLoopGroup 来处理客户端事件。由于我们使用 NIO 传输,所以用到了 NioEventLoopGroup 的实现
            b.group(group)
                    .channel(NioSocketChannel.class)            //3 使用的 channel 类型是一个用于 NIO 传输
                    .remoteAddress(new InetSocketAddress(host, port))    //4 设置服务器的 InetSocketAddress
                    //5 当建立一个连接和一个新的通道时,创建添加到 EchoClientHandler 实例 到 channel pipeline
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch)
                                throws Exception {
                            ch.pipeline().addLast(
                                    new EchoClientHandler());
                        }
                    });

            ChannelFuture f = b.connect().sync();        //6 连接到远程;等待连接完成

            f.channel().closeFuture().sync();            //7 阻塞直到 Channel 关闭
        } finally {
            group.shutdownGracefully().sync();            //8 调用 shutdownGracefully() 来关闭线程池和释放所有资源
        }
    }

    public static void main(String[] args) throws Exception {
        new EchoClient("127.0.0.1", 8080).start();
    }
}

服务端运行

netty demo_第2张图片

客户端运行

netty demo_第3张图片
netty demo_第4张图片

如果先启动客户端,会报异常:

客户端尝试连接服务器,但服务器是关闭的,所以引发了一个 java.net.ConnectException ,这个异常被 EchoClientHandler 的 exceptionCaught() 触发,打印出异常信息,并关闭 channel

关于@ChannelHandler.Sharable 与 @Sharable

netty demo_第5张图片
netty demo_第6张图片

参考文档:
netty实战

你可能感兴趣的:(netty)