Netty作为一个高性能IO框架,基本上所有使用JAVA技术栈的大厂,底层的IO通信框架都是通过Netty实现的。例如 dubbo,Spring gateway等等。所以不管是已经工作的还是在校学生。学会Netty,在你面试大厂的时候,无疑都是加分项。是所有从事JAVA工作的必备技能。完整介绍Netty的书籍不是很多,主要有华为大牛李林峰的《Netty权威指南》,和上年刚出的《Netty进阶:跟着案例学Netty》,以及国外的译本《Netty实战》。初学者建议选择《Netty权威指南》或者《Netty实战》。等学会之后在看《Netty进阶》。
中间一个月比较忙,鸽了三天,今天开始更新Netty相关文章.
一、Netty 简介
1、Netty是什么?
以下资料摘自百度百科,总结的已经比较好了。
Netty是由Jboss提供的一个Java开源框架,现为 Github上的独立项目。Netty提供异步的、时间驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。
2、为什么要用Netty?
1、虽然JAVA NIO框架提供了 多路复用IO的支持,但是并没有提供上层“信息格式”的良好封装。例如前两者并没有提供针对 Protocol Buffer、JSON这些信息格式的封装,但是Netty框架提供了这些数据格式封装(基于责任链模式的编码和解码功能);
2、NIO的类库和API相当复杂,使用它来开发,需要非常熟练地掌握Selector、ByteBuffer、ServerSocketChannel、SocketChannel等,需要很多额外的编程技能来辅助使用NIO,例如,因为NIO涉及了Reactor线程模型,所以必须必须对多线程和网络编程非常熟悉才能写出高质量的NIO程序
3、要编写一个可靠的、易维护的、高性能的NIO服务器应用。除了框架本身要兼容实现各类操作系统的实现外。更重要的是它应该还要处理很多上层特有服务,例如:客户端的权限、还有上面提到的信息格式封装、简单的数据读取,断连重连,半包读写,心跳等等,这些Netty框架都提供了响应的支持。
4、高性能,拥有比核心 Java API 更好的吞吐量,较低的延时,并且资源消耗更少,这个得益于共享池和重用,减少内存拷贝,实现0拷贝。
4、JAVA NIO框架存在一个poll/epoll bug:Selector doesn’t block on Selector.select(timeout),不能block意味着CPU的使用率会变成100%(这是底层JNI的问题,上层要处理这个异常实际上也好办)。当然这个bug只有在Linux内核上才能重现。
这个问题在JDK 1.7版本中还没有被完全解决,但是Netty已经将这个bug进行了处理。
这个Bug与操作系统机制有关系的,JDK虽然仅仅是一个兼容各个操作系统平台的软件,但在JDK5和JDK6最初的版本中(严格意义上来将,JDK部分版本都是),这个问题并没有解决,而将这个帽子抛给了操作系统方,这也就是这个bug最终一直到2013年才最终修复的原因(JDK7和JDK8之间)。
3、为什么Netty使用NIO而不是AIO?
Netty不看重Windows上的使用,在Linux系统上,AIO的底层实现仍使用EPOLL,没有很好实现AIO,因此在性能上没有明显的优势,而且被JDK封装了一层不容易深度优化。
AIO还有个缺点是接收数据需要预先分配缓存, 而不是NIO那种需要接收时才需要分配缓存, 所以对连接数量非常大但流量小的情况, 内存浪费很多。
据说Linux上AIO不够成熟,处理回调结果速度跟不上处理需求,有点像外卖员太少,顾客太多,供不应求,造成处理速度有瓶颈。
作者原话:
Not faster than NIO (epoll) on unix systems (which is true)
There is no daragram suppport
Unnecessary threading model (too much abstraction without usage)
4、为什么不用Netty5
-
netty5 中使用了 ForkJoinPool,增加了代码的复杂度,但是对性能的改善却不明显
-
多个分支的代码同步工作量很大
-
作者觉得当下还不到发布一个新版本的时候
-
在发布版本之前,还有更多问题需要调查一下,比如是否应该废弃 exceptionCaught, 是否暴露EventExecutorChooser等等。
二、Hello Netty!
接下来我们就开始从一个简单地demo,进入netty的世界吧。
该demo实现了创建一个Netty服务器和一个netty客户端,服务端接收到客户端请求的时候便打印相应的信息。
1、NettyServer
/**
* 作者:DarkKing
* 创建日期:2019/10/02
* 类说明:netty服务端
*
*/
public class NettyServer {
private final int port;
public NettyServer(int port) {
this.port = port;
}
public static void main(String[] args) throws InterruptedException {
int port = 9999;
NettyServer echoServer = new NettyServer(port);
System.out.println("服务器启动");
echoServer.start();
System.out.println("服务器关闭");
}
public void start() throws InterruptedException {
final NettyServerHandler serverHandler = new NettyServerHandler();
/*线程组*/
EventLoopGroup group = new NioEventLoopGroup();
try {
/*服务端启动必须*/
ServerBootstrap b = new ServerBootstrap();
b.group(group)/*将线程组传入*/
.channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/
.localAddress(new InetSocketAddress(port))/*指定服务器监听端口*/
/*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel,
所以下面这段代码的作用就是为这个子channel增加handle*/
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel ch) throws Exception {
/*添加到该子channel的pipeline的尾部*/
ch.pipeline().addLast(serverHandler);
}
});
ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/
f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/
} finally {
group.shutdownGracefully().sync();/*优雅关闭线程组*/
}
}
}
2、NettyServerHandler
/**
* 作者:DarkKing
* 创建日期:2019/10/02
* 类说明:netty服务端处理handler
*
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/*客户端读到数据以后,就会执行*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf in = (ByteBuf)msg;
System.out.println("Server accept"+in.toString(CharsetUtil.UTF_8));
ctx.write(in);
}
/*** 服务端读取完成网络数据后的处理*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE);
}
/*** 发生异常后的处理*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
3、NettyClient
public class NettyClient {
private final int port;
private final String host;
public NettyClient(int port, String host) {
this.port = port;
this.host = host;
}
public void start() throws InterruptedException {
/*线程组*/
EventLoopGroup group = new NioEventLoopGroup();
try{
/*客户端启动必备*/
Bootstrap b = new Bootstrap();
b.group(group)/*把线程组传入*/
/*指定使用NIO进行网络传输*/
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host,port))
.handler(new NettyClientHandler());
/*连接到远程节点,阻塞直到连接完成*/
ChannelFuture f = b.connect().sync();
/*阻塞程序,直到Channel发生了关闭*/
f.channel().closeFuture().sync();
}finally {
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws InterruptedException {
new NettyClient(9999,"127.0.0.1").start();
}
}
4、NettyClientHandler
/**
* 作者:DarkKing
* 创建日期:2019/10/02
* 类说明:netty客户端处理器
*
*/
public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
/*客户端读到数据以后,就会执行*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg)
throws Exception {
System.out.println("client acccept:"+msg.toString(CharsetUtil.UTF_8));
}
/*连接建立以后*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer(
"Hello Netty",CharsetUtil.UTF_8));
//ctx.fireChannelActive();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
super.userEventTriggered(ctx, evt);
}
}
5、程序演示
1、启动NettyServer,打印服务器启动
2、启动客户端,客户端连接服务器端成功,并发送Hello,Netty!并收到服务器端返回的消息,Hello,Netty!
3、服务器端打印 Server accept:Hello Netty
6、总结
使用Netty大大简化了我们的开发工作量,并将原生JDK复杂的Selector、ByteBuffer、ServerSocketChannel、SocketChannel等组件进行封装。使我们开发者不需要关系具体底层的运行原理和机制。进行模板化的开发。提高开发的效率、以及容错率。
本章主要简单介绍了下Netty的入门。代码已放到github上,github.com/379685397/n…。下章对Netty的组件进行介绍。