Netty是一款用于创建高性能网络应用程序的高级框架。通俗地讲,其实就是远程通信框架,即rpc框架。正因为netty强大的性能,它也因此被用于很多流行框架或组件的底层通信机制。比如常见的Spring Cloud Gateway,Dubbo。它们的底层通信机制就是Netty。
由于JDK中用于操作nio的api太偏底层,使用这些底层api较为复杂。而Netty用较简单的抽象隐藏了底层实现的复杂性,封装了更为简单易用的api。
通道,作为NIO的三大组件之一。可参考 BIO/伪异步IO/NIO/AIO四种IO模型的演变 一文。
一个回调其实就是一个方法,一个指向已经被提供给另外一个方法的方法的引用。这使得后者可以在适当的时候调用前者。 Netty在内部使用回调来处理事件。
Future 提供了另一种在操作完成时通知应用程序的方式。这个对象可以看作是一个异步操作的结果的占位符;它将在未来的某个时刻完成,并提供对其结果的访问。
(笔者感觉这段描述十分到位,因为是异步,所以我们无法同步等待获取结果,而在操作线程池的时候,执行ExecutorService.submit()接口的返回值就是Future,之前笔者一直以为这个返回值还是阻塞等待获取到的。现在看到这段描述,如醍醐灌顶。)
JDK的JUC下的Future,只允许手动检查对应的操作是否完成,或一直阻塞直到它完成,十分繁琐。因此Netty提供了自己的实现——ChannelFuture,用于在执行异步操作的时候使用。
Netty 使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作。
io.netty
netty-all
5.0.0.Alpha2
所有的 Netty 服务器都需要以下两部分。
// 标示一个ChannelHandler可以被多个Channel安全地共享
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelHandlerAdapter {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 打印异常栈跟踪
cause.printStackTrace();
// 关闭该Channel
ctx.close();
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
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);
}
}
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() + " ");
}
// 设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException)
int port = Integer.parseInt(args[0]);
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)
.channel(NioServerSocketChannel.class) // 3.指定所使用的NIO传输Channel
.localAddress(new InetSocketAddress(port)) // 4.使用指定的端口设置套接字地址
.childHandler(new ChannelInitializer() { //5.添加一个EchoServerHandler到子Channel的ChannelPipeline
protected void initChannel(SocketChannel ch) throws Exception {
// EchoServerHandler被标注为@Shareable,所以我们可以总是使用同样的实例
ch.pipeline().addLast(serverHandler);
}
});
ChannelFuture f = b.bind().sync(); // 6.异步地绑定服务器;调用sync()方法阻塞等待直到绑定完成
f.channel().closeFuture().sync(); // 7.获取Channel的CloseFuture,并且阻塞当前线程直到它完成
} finally {
// 8.关闭EventLoopGroup,释放所有的资源
group.shutdownGracefully().sync();
}
}
}
同服务端的编写类似,客户端也要通过 ChannelHandler 来实现其逻辑。只不过客户端一般会使用某个ChannelHandler的具体实现类。
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler {
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
}
protected void messageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
}
}