Netty入门应用

使用Netty编写一个时间服务器

服务端程序

public class TimeServer {
    public void bind(int port) throws InterruptedException {

        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            //backlog是阻塞队列
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).
                    option(ChannelOption.SO_BACKLOG, 1024).childHandler(new ChildChannelHandler());
            ChannelFuture f = bootstrap.bind(port).sync();
            //绑定端口,同步等待成功
            f.channel().closeFuture().sync();
        }finally {
            //优雅退出
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel socketChannel) throws Exception {
            socketChannel.pipeline().addLast(new TimeServerHandler());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                System.out.println("use  default port");
            }
        }
        new TimeServer().bind(port);
    }
}

服务端程序从bind()开始,使用 NioEventLoopGroup()创建了两个实例,NioEventLoopGroup是个线程组,专门用于处理网络事件,本质上就是Reactor线程组。这里的两个线程组,一个用于接收客户端的连接,一个用于网络数据的读写。

ServerBootstrap是Netty中用于NIO服务端启动的辅助配置类,降低了服务端开发的复杂度。group方式设置NIO处理线程组,channel方法设置Channel类型,option方法配置各种参数,childHandler用于绑定IO事件的处理类。

childHandler继承ChannelInitializer类,实现了initChannel方法,在Channel创建完成后,进行初始化时,将它的handle加入到pipeline中,用来处理网络事件。

ServerBootstrap辅助类配置完成后,使用bind方法绑定端口,然后调用sync等待绑定完成。

public class TimeServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        byte[] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);
        String body = new String(bytes, "UTF-8");
        System.out.println("The Time server receive order: " + body);
        String cuurentTime = "QUERY TIME".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD QUERY";
        ByteBuf resp = Unpooled.copiedBuffer(cuurentTime.getBytes());
        //不是写到channel,而是写到buffer,通过下面的flush真正写到SocketChannel
        ctx.write(resp);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

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

TimeServerHandler用于处理网络数据读写,这里我们主要看channelRead方法。

首先对msg进行类型转换,这里的ByteBuf跟JDK NIO中的ByteBuffer很像,在原有基础上提供了更多的功能。通过buf.readableBytes()获取可读取的字节,然后通过readBytes将缓冲区的字节复制到新的自己数组中,最后通过new String获取请求信息。判断如果是正确的指令则通过ChannelHandlerContext的write将应答数据写到 缓存区

客户端程序

public class TimeClient {
    public void connect(int port, String host) throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new TimeClientHandler());
                    }
                });
            ChannelFuture future = bootstrap.connect(host, port).sync();
            future.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                System.out.println("use  default port");
            }
        }
        new TimeClient().connect(port, "127.0.0.1");
    }
}

客户端的逻辑:首先也是先进行启动辅助类Bootstrap的配置,客户端的事件处理类直接使用匿名内部类。配置完成后,调用connect方法发起到服务端程序的异步连接,然后调用sync方法等待连接成功。

当客户端连接关闭时,客户端主函数退出,释放所有NIO资源。

public class TimeClientHandler extends ChannelInboundHandlerAdapter {
    private final ByteBuf firstMessage;


    public TimeClientHandler() {
        byte[] req = "QUERY TIME".getBytes();
        firstMessage = Unpooled.buffer(req.length);
        firstMessage.writeBytes(req);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //链路建立的时候发送第一条消息
        ctx.writeAndFlush(firstMessage);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf) msg;
        byte[] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);
        String body = new String(bytes, "UTF-8");
        System.out.println("Now is :" + body);
    }

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

}

这里我们主要看channelActive和channelRead。

当客户端和服务端的链接建立成功后,Netty的NIO线程会调用channelActive方法,这个方法的实现中主要就是使用ChannelHandlerContext的writeAndFlush方法发送请求数据。

当服务端返回应答数据后,Netty会调用channelRead方法,这个方法就是把应答数据从ByteBuf中读取出来并打印。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值