【Netty4】Netty入门官方例子解析(二)时间服务器

文章目录

官网文档

系列文章:
《Netty入门官方例子解析(一)丢弃服务器》
《Netty入门官方例子解析(二)Time Server时间服务器》
《Netty入门官方例子解析(三)处理一个基于流的传输》

1. 版本

netty4

2. 代码

原文这个章节,是为了构建一个模拟时间服务器的代码,可以供其他客户端查询时间,可以通过rdate(后文有解释)命令来验证。

import io.netty.bootstrap.ServerBootstrap;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
    
/**
 * Discards any incoming data.
 */
public class DiscardServer {
    
    private int port;
    
    public DiscardServer(int port) {
        this.port = port;
    }
    
    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap(); // (2)
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class) // (3)
             .childHandler(new ChannelInitializer<SocketChannel>() { // (4)
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ch.pipeline().addLast(new DiscardServerHandler());
                 }
             })
             .option(ChannelOption.SO_BACKLOG, 128)          // (5)
             .childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
    
            // Bind and start to accept incoming connections.
            ChannelFuture f = b.bind(port).sync(); // (7)
    
            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
    
    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        }

        new DiscardServer(port).run();
    }
}
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

public class TimeServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(final ChannelHandlerContext ctx) { // (1)
        final ByteBuf time = ctx.alloc().buffer(4); // (2)
        time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));

        final ChannelFuture f = ctx.writeAndFlush(time); // (3)
        f.addListener(new ChannelFutureListener() {
            public void operationComplete(ChannelFuture future) {
                assert f == future;
                ctx.close();
            }
        }); // (4)
    }

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

当建立连接并准备通信时,将调用channelActive()方法。让我们写一个表示该方法当前时间的32位整数。

要发送新消息,我们需要分配一个包含消息的新缓冲区。我们将要写一个32位整数,因此我们需要一个容量至少为4个字节的ByteBuf。通过ChannelHandlerContext.alloc()获取当前的ByteBufAllocator并分配一个新的缓冲区

和往常一样,我们编写构造的消息。

但是,等等,flip在哪里?在NIO中发送消息之前,我们不是曾经调用过java.nio.ByteBuffer.flip()吗? ByteBuf没有这种方法,因为它有两个指针。一个用于读取操作,另一个用于写入操作。当您将某些内容写入ByteBuf时,写入器索引会增加,而读取器索引不会改变。读索引和写索引分别表示消息的开始和结束位置。

相反,NIO缓冲区没有提供一种明确的方法来确定消息内容的开始和结束位置,而无需调用flip方法。当您忘记flip缓冲区时会遇到麻烦,因为将不会发送任何内容或发送不正确的数据。在Netty中不会发生这样的错误,因为我们对不同的操作类型有不同的指针。您会发现它使您适应它的生活变得轻松多了-无需flip的生活!

需要注意的另一点是ChannelHandlerContext.write()(和writeAndFlush())方法返回ChannelFuture。 ChannelFuture表示尚未发生的I / O操作。这意味着,由于Netty中的所有操作都是异步的,因此可能尚未执行任何请求的操作。例如,以下代码甚至在发送消息之前就可能关闭连接

Channel ch = ...;
ch.writeAndFlush(message);
ch.close();

因此,您需要在ChannelFuture完成之后调用close()方法,该方法由write()方法返回,并在完成写操作后通知其侦听器。 请注意,close()也可能不会立即关闭连接,它会返回ChannelFuture。

原文中的例子可以用linuxrdate命令访问,记住不是cmd命令行,否则显示乱码。rdate是一个查询日期并支持同步至本地日期的工具,下面的语法取决于版本,我安装的版本并不支持port参数,也因此没有验证成功。不过没关系,下文中有java实现的客户端

客户端
我们java实现一个客户端,来模拟rdate命令一样的效果。

$ rdate -o <port> -p <host>
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class TimeClient {
    public static void main(String[] args) throws Exception {

        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap(); // (1)
            b.group(workerGroup); // (2)
            b.channel(NioSocketChannel.class); // (3)
            b.option(ChannelOption.SO_KEEPALIVE, true); // (4)
            b.handler(new ChannelInitializer<SocketChannel>() {
                public void initChannel(SocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new TimeClientHandler());
                }
            });

            // Start the client.
            ChannelFuture f = b.connect("localhost", 8088).sync(); // (5)

            // Wait until the connection is closed.
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
        }
    }
}

1.Bootstrap与ServerBootstrap相似,除了它用于非服务器通道,例如客户端或无连接通道。
2.如果仅指定一个EventLoopGroup,则它将同时用作boss 组和worker 组。 尽管,boss worker并不用于客户端。
3.代替NioServerSocketChannel,NioSocketChannel被用来创建客户端通道
4.请注意,由于客户端SocketChannel没有父级,因此此处不像使用ServerBootstrap那样使用childOption()
5.我们应该调用connect()方法而不是bind()方法。

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.Date;

public class TimeClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf m = (ByteBuf) msg; // (1)
        try {
            long currentTimeMillis = (m.readUnsignedInt() - 2208988800L) * 1000L;
            System.out.println(new Date(currentTimeMillis));
            ctx.close();
        } finally {
            m.release();
        }
    }

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

我们运行下看看效果:
记得先运行DiscardServer ,再运行TimeClient

Thu Jan 16 10:37:34 CST 2020
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值