netty概览
Netty是由JBOSS提供的一个java开源框架。
Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.
Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。
netty版本
目前为止netty的最新版本是netty4,在2019-08-13发布的4.1.39.Final。
为什么netty5被废弃了。
作者的声明与讨论
After talking with @Scottmitch and also with @nmittler I would like to propose "dropping" current master and so not release any new 5.0 version for now.
The major change of using a ForkJoinPool increases complexity and has not
demonstrated a clear performance benefit. Also keeping all the branches in sync is quite some work without a real need for it as there is nothin in current master which I think justifies a new major release.Things that we should investigate to prepare for this change:
- Deprecate
exceptionCaught
inChannelHandler
, only expose it inChannelInboundHandler
- Expose
EventExecutorChooser
fromMultithreadEventExecutorGroup
to allow the user more flexibility to choose nextEventLoop
- Add another method to be able to send user events both ways in the pipeline. (#4378)
Please chime in here and let me / us hear any concerns.
- netty5中使用了ForkJoinPool,增加了代码的复杂度,但是对性能的改善却不明显。
- 多个分支的代码同步的工作量是很大的。
- 作者觉得当下还不到发布一个新版本的时候。
- 在发布版本之前,还有更多的问题需要调查一下。
hello world
-
创建一个java工程,引入netty最新的jar包,本人使用gradle创建。
dependencies { compile( "io.netty:netty-all:4.1.39.Final" ) }
-
创建testServer,bossGroup获取连接,并把连接分发给workerGroup
public class TestServer { public static void main(String[] args) throws InterruptedException { // bossGroup 获取连接 把连接分发给workerGroup NioEventLoopGroup bossGroup = new NioEventLoopGroup(); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); try { //启动服务 ServerBootstrap serverBootstrap = new ServerBootstrap(); //TestServerinitializer 自定义子处理器 serverBootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new TestServerinitializer()); ChannelFuture channelFuture = serverBootstrap.bind(8899).sync(); channelFuture.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
-
创建 TestServerinitializer
public class TestServerinitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("httpServerCoder", new HttpServerCodec()); pipeline.addLast("testHttpServerHandler",new TestHttpServerHandler()); } }
-
创建 testHttpServerHandler 负责读取客户端内容并设置返回报文
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> { @Override protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8); DefaultHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()); ctx.writeAndFlush(response); } }
-
运行testServer,用curl调用。
curl "http://localhost:8899"
返回结果
hello world
最简单的hello world 运行结束。但里面有一些问题。
- 运行报错
警告: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
java.io.IOException: Connection reset by peer
at java.base/sun.nio.ch.FileDispatcherImpl.read0(Native Method)
at java.base/sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:39)
at java.base/sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:276)
at java.base/sun.nio.ch.IOUtil.read(IOUtil.java:233)
at java.base/sun.nio.ch.IOUtil.read(IOUtil.java:223)
at java.base/sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:358)
修改一下 testHttpServerHandler
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception{
if (msg instanceof HttpRequest){
//... 代码省略
}
}
-
用谷歌浏览器会打印两次hello world
由于我们监听8899端口,并不关心uri,谷歌浏览器每次请求会额外请求一次图标favicon.ico,所以会打印两次hello world,testHttpServerHandler 可以改造如下
public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> { @Override protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { if (msg instanceof HttpRequest) { HttpRequest httpRequest = (HttpRequest) msg; System.out.println(String.format("请求方法名:%s", httpRequest.method().name())); URI uri = new URI(httpRequest.uri()); if ("/favicon.ico".equals(uri.getPath())) { System.out.println("请求favicon.ico"); return; } ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8); DefaultHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content); response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain"); response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()); ctx.writeAndFlush(response); } } }
-
TestHttpServerHandler 中我们继承了SimpleChannelInboundHandler 。
public abstract class SimpleChannelInboundHandler<I> extends ChannelInboundHandlerAdapter {
SimpleChannelInboundHandler继承了ChannelInboundHandlerAdapter ,我们重写里面的方法看看调用顺序,在TestHttpServerHandler中加入这些方法。
@Override public void channelActive(ChannelHandlerContext ctx) throws Exception { System.out.println("channelActive..."); super.channelActive(ctx); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { System.out.println("channelInactive..."); super.channelInactive(ctx); } @Override public void channelRegistered(ChannelHandlerContext ctx) throws Exception { System.out.println("channelRegistered..."); super.channelRegistered(ctx); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { System.out.println("channelUnregistered..."); super.channelUnregistered(ctx); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { System.out.println("handlerAdded..."); super.handlerAdded(ctx); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { System.out.println("handlerRemoved..."); super.handlerRemoved(ctx); }
用 curl "http://localhost:8899" 调用,打印结果
handlerAdded...
channelRegistered...
channelActive...
channelInactive...
channelUnregistered...
handlerRemoved...用浏览器调用
handlerAdded...
channelRegistered...
channelActive...因为浏览器本身也是一个程序,我们用的是http1.1并不会马上关闭。