Read book Netty in action(Chapter III)--My first Netty application

序言

我们前面简简单单的聊了一下Netty的核心。俗话说:光说不练假把式。这把我们来玩一下Netty。再深入一点点。

development environment

IDEA + MAVEN + JDK8

Netty客户端/服务端

我们有一个服务器端 + 多个客户端。虽然Netty是一个很牛逼的框架,但是理论上,还是受限于系统资源的。Netty的客户端和服务器之间的交互比较简单,在客户端建立一个连接够,它会向服务器发送一个/多个 消息。服务器会把消息会送给客户端。

My first Echo Server

所有的Netty服务器都需要两个部分:
至少一个ChannelHandler - 实现了服务器对从客户端接受的数据的处理,即它的业务逻辑。
配置服务器的启动代码,将服务器绑定到他要监听的连接请求的端口上。

ChannelHandler和业务逻辑

之前曾经介绍过Future和回调,并且阐述了它们在事件驱动设计中的应用。ChannelHandler是一个借口,他的实现负责接收和响应事件通知。在Netty应用中,所有的数据处理逻辑都在这里。
因为Echo服务器会响应传入的消息,所以他至少需要实现ChannelInboundHandler接口,用于定义响应入站的事件的方法。这个简单的应用程序只需少量的方法。所以继承ChannelInboundHandlerAdapter类足够,里面有默认实现。看看代码:

public interface ChannelInboundHandler extends ChannelHandler {
    void channelRegistered(ChannelHandlerContext var1) throws Exception;

    void channelUnregistered(ChannelHandlerContext var1) throws Exception;

    void channelActive(ChannelHandlerContext var1) throws Exception;

    void channelInactive(ChannelHandlerContext var1) throws Exception;

    void channelRead(ChannelHandlerContext var1, Object var2) throws Exception;

    void channelReadComplete(ChannelHandlerContext var1) throws Exception;

    void userEventTriggered(ChannelHandlerContext var1, Object var2) throws Exception;

    void channelWritabilityChanged(ChannelHandlerContext var1) throws Exception;

    void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception;
}

定义了一些事件

public class ChannelInboundHandlerAdapter extends ChannelHandlerAdapter implements ChannelInboundHandler {
// do something
}

每个传入的消息都要调用它

  @Skip
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ctx.fireChannelRead(msg);
    }

通知ChannelInboundHandler最后一次对ChannelRead()的调用时当前批量读取中的最后一条消息。

    @Skip
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelReadComplete();
    }

在读取操作期间,有异常抛出的时候会调用。

 @Skip
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.fireExceptionCaught(cause);
    }

该Echo服务器的ChannelHandler实现是EchoServerHandler.

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);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //将未决消息冲刷到远程节点,并且关闭该 Channel
        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 打印异常跟踪栈
        cause.printStackTrace();
        // 关闭该通道
        ctx.close();
    }
}

先重写这个,其实不止这个,还有很多,后面慢慢看,不过记住:
针对不同类型的事件来调用ChannelHandler。
应用程序通过实现ChannlerHandler挂钩到生命周期,且提供应用程序的逻辑。
在架构上,ChannlerHandler能够保持业务逻辑和网络处理代码的分离,这简化了并发过程。

引导服务器

我们实现了ChannelInboundHandlerAdapter 的核心逻辑之后,可以讨论引导服务器的本身了,具体设计以下内容:
绑定到服务器将在其上监听并接受传入连接请求的端口。
配置Channel,将入站消息通知给EchoServerHandler。


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

    public static void main(String[] args)
            throws Exception {
        if (args.length != 1) {
            System.err.println("Usage: " + EchoServer.class.getSimpleName() +
                    " <port>"
            );
            return;
        }
        //设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException)
        int port = Integer.parseInt(args[0]);
        //调用服务器的 start()方法
        new EchoServer(port).start();
    }

    public void start() throws Exception {
        final EchoServerHandler serverHandler = new EchoServerHandler();
        //(1) 创建EventLoopGroup
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            //(2) 创建ServerBootstrap
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)
                    //(3) 指定所使用的 NIO 传输 Channel
                    .channel(NioServerSocketChannel.class)
                    //(4) 使用指定的端口设置套接字地址
                    .localAddress(new InetSocketAddress(port))
                    //(5) 添加一个EchoServerHandler到于Channel的 ChannelPipeline
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            //EchoServerHandler 被标注为@Shareable,所以我们可以总是使用同样的实例
                            //这里对于所有的客户端连接来说,都会使用同一个 EchoServerHandler,因为其被标注为@Sharable,
                            ch.pipeline().addLast(serverHandler);
                        }
                    });
            //(6) 异步地绑定服务器;调用 sync()方法阻塞等待直到绑定完成
            ChannelFuture f = b.bind().sync();
            System.out.println(EchoServer.class.getName() +
                    " started and listening for connections on " + f.channel().localAddress());
            //(7) 获取 Channel 的CloseFuture,并且阻塞当前线程直到它完成
            f.channel().closeFuture().sync();
        } finally {
            //(8) 关闭 EventLoopGroup,释放所有的资源
            group.shutdownGracefully().sync();
        }
    }
}

ServerBootstrap b = new ServerBootstrap();创建了一个实例,因为使用的是NIO传输,所以指定了EventLoopGroup 来接受和处理新的连接,并且设置channel类为NioServerSocketChannel。将本地地址设置为一个具有指定端口的InetSocketAddress。服务器绑定到这个端口可以监听新的连接请求。使用ChannelInitializer。当有了一个新连接,将会创建一个新的Channel,它会把我们EchoServerHandler 加入到channel的pipelineLine中。这个handler将会收到又换入站消息的通知。接下来绑定了服务器ChannelFuture f = b.bind().sync();并且设置为sync,就是将当前线程阻塞直到服务器绑定完成为止,随后就关闭了EventLoopGroup 并且释放所有资源。

Echo客户端

echo客户端会先连接到客户端,然后发送消息,对于消息等待并且接受然后从服务端发回来,最后不要忘记关闭连接。
客户端也拥有一个channelInboundHandler,我们至少需要重写 channelActive(),channelRead0()和exceptionCaught()。他们分别是在服务器的连接已经建立之后被调用。当从服务器接受到一条消息被调用和在处理过程中引发异常被调用。

public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
        System.out.println(" i get " + msg.toString(CharsetUtil.UTF_8));
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("Herche Jane", CharsetUtil.UTF_8));
    }
}

很简单,我们第一个先是服务器开启的时候,调用了exceptionCaught方法,每当接受数据的时候,调用了channelRead0方法,当发生异常的时候,调用了exceptionCaught方法,此时,channel被关闭,连接断掉了。

引导客户端


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() {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host, port))
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });
            ChannelFuture future = bootstrap.connect().sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                group.shutdownGracefully().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }


    public static void main(String[] args) {
        final EchoClient localhost = new EchoClient("localhost", 9999);
        localhost.start();
    }

然后运行就会发现客户端连接上了服务器就有了一条消息,然后服务器收到了,写回来一条。

结束语

一个如此简单的例子,让我对netty有了基本的认识,但是我还是不能很好的运用它,接下来的日子,我会学习更多的关于netty的知识。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值