系列文章:
《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