又见“Hello World”,第一个Netty示例!

Posted 点滴积累相伴成长

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了又见“Hello World”,第一个Netty示例!相关的知识,希望对你有一定的参考价值。

众所周知,Netty是一个异步的基于事件驱动的高性能网络通信框架,从我们日常开的发角度来看,Netty的使用的场景可以分为处理Http请求,基于Socket的网络通信(例如我们熟悉的阿里的Dubbo这款RPC框架底层的网络通信就是基于Netty),还有就Netty提供了对WebSocket协议的支持。我将结合自身的学习过程,通过文章的形式在保证内容清晰准确,表达简单易懂的前提下将Netty如何使用,各个组件之间协作关系,线程模型,Java的NIO、编解码以及源码分析等内容介绍给大家,希望通过我的文章可以帮助大家理解并在项目实际开发中应用Netty。下面我们就通过这样一个简单示例来了解Netty作为服务端时,是如何处理Http请求的——当浏览器请求Netty服务端时,将“Hello World”返回给浏览器。


首先,在IDE中创建一个maven工程,并在pom.xml文件中增加对Netty的依赖:


<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.15.Final</version>
</dependency>
</dependencies>


接下来我们定义一个ChannelHandler接口的实现类,用于处理来自浏览器的请求,将它命名为HttpServerHandler,目前我们可以先将它理解为类似于一种过滤器或拦截器,而Handler我们见名知义,应该是用来处理我们实际业务的一个组件,具体代码如下图:



仔细观察发现,我们定义的HttpServerHandler继承了SimpleChannelInbountHandler抽象类,并实现了其channelRead0这个方法,此方法是一个回调方法,还记得文章最开始提到的“事件驱动”吗,响应事件的方法往往就是一个回调方法,因此对于这个方法不应该感到不解。此方法的作用是读取浏览器传入的数据,并将响应结果返回给浏览器。所以接收信息并构造响应对象的逻辑就需要在在channelRead0中实现。此方法的第一个参数是上下文对象,在此示例中的作用是将响应结果写入到IO线程并经由经网络返回至浏览器,第二个参数封装了Http请求的相关内容。这里需要强调的一点是,Netty并没有实现Servlet规范,只是提供了最基础的底层API,更不要提消息路由了,此示例只简单介绍Netty作为Http服务端时处理请求和响应的基本流程,从而对Netty的执行流程产生一个简单的认识。


我们的目的是要把“Hello World”返回到浏览器页面,因此我们需要构造内容对象,由于任何消息都是以字节的形式在网络中进行传输的,因此我们需要将返回内容转换成Netty提供的字节缓冲对象,如上图中的第35行代码:

ByteBuf content = Unpooled.copiedBuffer("Hello World", CharsetUtil.UTF_8);

ByteBuf是Netty提供的重要组件,后面的文章中会详细讨论,然后我们使用Netty提供的API来构造Http响应对象FullHttpResponse response,指定Http协议版本为Http1.1,返回状态码为200,并将返回内容content保存到此对象中,再指定好Http头信息(上图代码37-40行),最后通过调用ctx.writeAndFlush(response);将内容写入到网络返回到浏览器;


实际上用Netty实现Hello World并不是那么简单的,工作远未结束,我们需要将自己编写的Handler处理器存放在Netty提供的初始化器ChannelInitializer中。现在我们再来定义一个HttpServerInitializer类,代码如下图:


又见“Hello World”,第一个Netty示例!


此类继承了ChannelInitializer抽象类并实现其initChannel方法,此方法也是一个回调方法,它的调用时机目前暂且理解为服务器启动后,有请求链接进入到服务端,Channel通道被注册时HttpServerInitializer实例被创建,initChannel方法被调用。第13行的ChannelPipeline可以抽象的理解为一个管道,我们自己编写的Handler处理以及Netty提供的各种内置的处理器可以链式的形式添加到这个处理器链当中,它的数据结构是一个包含头尾节点的链表。上图中的第15行中的HttpServerCodec是Netty提供的Http协议的编解码器,用于将请求和响应消息进行编解码,此时不禁想到我们之前在编写Servlet时并没有手工的对请求响应消息进行编解码,原来Servlet容器默默的为我们做了很多工作,大大的简化了开发,让我们专注于业务逻辑的实现上。


写到这里您是不是认为开发任务已经完成了呢,稍安勿躁,服务端的启动代码还没有编写。具体代码如下图所示,我将对图片中关于Netty启动流程的代码进行一个简要的说明:



首先声明两个事件循环组EventLoopGroup对象,分别是bossGroup和workerGroup,我们暂且把它们理解为两个死循环,bossGroup线程用于接受请求,然后把具体处理流程传递给workerGroup线程(此处需要再一次指出Netty的异步和事件驱动特性,我们从Netty框架所提供的类的命名规则就能感性的认识到这一点)。


上图中ServerBootStrap类是Netty提供的一个简化服务端启动的工具类,它的group方法接受bossGroup和workerGroup,channel方法接收指定类型的Channel的Class对象,此时的Channel类型为NioserverSocketChannel,Channel我们暂且把它理解为网络连接,childHandler方法接受我们之前定义的初始化器HttpServerInitializer对象。至此,我们已经将服务端的启动工作,初始化器创建以及自定义的处理器HttpServerHandler有机的结合在了一起。


然后如上图第22行代码所示,将服务启动绑定在8080端口上,然后调用sync同步方法,等待请求消息。此方法返回ChannelFuture对象,看见Future不禁又想到了异步操作,现在是不是对Netty声称的异步、事件驱动有一个感性的认识了呢?


最后从ChannelFuture中可以获取到channel对象,调用channel的closeFuture方法并执行sync同步操作对服务关闭进行监听。另外,Netty提供了优雅关闭机制,当服务关闭时,final块中的事件循环组bossGroup和workerGroup执行shutDownGranfully方法释放线程资源和IO资源。至此,示例代码全部编写完成,下面通过启动main方法来开启Netty的Http服务端,并在浏览器中访问http://localhost:8080,请求我们的Netty服务,观察结果:



一切正常的话将在浏览器页面中正确显示“Hello World”!


补充说明一下,实际上Netty框架的设计非常精巧,同时涉及到了很多概念和相关组件,在一篇文章中有很多类的作用和细节没有理清楚很正常,我们在接下来的文章中将对Netty的部分重要的概念和组件的细节详细解读。


感谢阅读!













以上是关于又见“Hello World”,第一个Netty示例!的主要内容,如果未能解决你的问题,请参考以下文章

Netty 中文教程 Hello World !详解

java 使用netty搭建tcp服务器(hello world)

HttpServer性能比较

第一个python hello world

使用VS2013调试C语言时出错,连简单的Hello World都报错,调试时显示无法启动程序,无法访问

Python 1-3Python的第一个程序 Hello World