456
汽车大全
《Netty 实战》Netty In Action中文版 第2章——你的第一款Netty应用程序(一)
第2章 你的第一款Netty应用程序
本章主要内容
- 设置开发环境
- 编写Echo服务器和客户端
- 构建并测试应用程序
在本章中,我们将展示如何构建一个基于Netty的客户端和服务器。应用程序很简单:客户端将消息发送给服务器,而服务器再将消息回送给客户端。但是这个练习很重要,原因有两个。
首先,它会提供一个测试台,用于设置和验证你的开发工具和环境,如果你打算通过对本书的示例代码的练习来为自己将来的开发工作做准备,那么它将是必不可少的。
其次,你将获得关于Netty的一个关键方面的实践经验,即在前一章中提到过的:通过ChannelHandler
来构建应用程序的逻辑。这能让你对在第3章中开始的对Netty API的深入学习做好准备。
2.1 设置开发环境
要编译和运行本书的示例,只需要JDK和Apache Maven这两样工具,它们都是可以免费下载的。
我们将假设,你想要捣鼓示例代码,并且想很快就开始编写自己的代码。虽然你可以使用纯文本编辑器,但是我们仍然强烈地建议你使用用于Java的集成开发环境(IDE)。
2.1.1 获取并安装Java开发工具包
你的操作系统可能已经安装了JDK。为了找到答案,可以在命令行输入:
javac -version
如果得到的是javac 1.7
……或者1.8
……,则说明已经设置好了并且可以略过此步[1]。
否则,请从https://java.com/en/download/manual.jsp处获取JDK第8版。请留心,需要下载的是JDK,而不是Java运行时环境(JRE),其只可以运行Java应用程序,但是不能够编译它们。该网站为每个平台都提供了可执行的安装程序。如果需要安装说明,可以在同一个网站上找到相关的信息。
建议执行以下操作:
- 将环境变量
JAVA_HOME
设置为你的JDK安装位置(在Windows上,默认值将类似于C:\Program Files\Java\jdk1.8.0_121); - 将
%JAVA_HOME%\bin
(在Linux上为${JAVA_HOME}/bin
)添加到你的执行路径。
2.1.2 下载并安装IDE
下面是使用最广泛的Java IDE,都可以免费获取:
- Eclipse—— www.eclipse.org;
- NetBeans—— www.netbeans.org;
- Intellij IDEA Community Edition—— www.jetbrains.com。
所有这3种对我们将使用的构建工具Apache Maven都拥有完整的支持。NetBeans和Intellij IDEA都通过可执行的安装程序进行分发。Eclipse通常使用Zip归档文件进行分发,当然也有一些自定义的版本包含了自安装程序。
2.1.3 下载和安装Apache Maven
即使你已经熟悉Maven了,我们仍然建议你至少大致浏览一下这一节。
Maven是一款广泛使用的由Apache软件基金会(ASF)开发的构建管理工具。Netty项目以及本书的示例都使用了它。构建和运行这些示例并不需要你成为一个Maven专家,但是如果你想要对其进行扩展,我们推荐你阅读附录中的Maven简介。
你需要安装Maven吗
Eclipse和NetBeans[2]自带了一个内置的Maven安装包,对于我们的目的来说开箱即可工作得良好。如果你将要在一个拥有它自己的Maven存储库的环境中工作,那么你的配置管理员可能就有一个预先配置好的能配合它使用的Maven安装包。
在本书中文版出版时,Maven 的最新版本是3.3.9。你可以从https://maven.apache.org/ download.cgi下载适用于你的操作系统的tar.gz或者zip归档文件[3]。安装很简单:将归档文件的所有内容解压到你所选择的任意的文件夹(我们将其称为<安装目录>)。这将创建目录<安装目录>\apache-maven-3.3.9。
和设置Java环境一样:
- 将环境变量
M2_HOME
设置为指向<安装目录>\apache-maven-3.3.9; - 将
%M2_HOME%\bin
(或者在Linux上为${M2_HOME}/bin
)添加到你的执行路径。
这将使得你可以通过在命令行执行mvn.bat
(或者mvn
)来运行Maven。
2.1.4 配置工具集
如果你已经按照推荐设置好了环境变量JAVA_HOME
和M2_HOME
,那么你可能会发现,当你启动自己的IDE时,它已经发现了你的Java和Maven的安装位置。如果你需要进行手动配置,我们所列举的所有的IDE版本在Preferences或者Settings下都有设置这些变量的菜单项。相关的细节请查阅文档。
这就完成了开发环境的配置。在接下来的各节中,我们将介绍你要构建的第一个Netty应用程序的详细信息,同时我们将更加深入地了解该框架的API。之后,你就能使用刚刚设置好的工具来构建和运行Echo服务器和客户端了。
2.2 Netty客户端/服务器概览
图2-1从高层次上展示了一个你将要编写的Echo客户端和服务器应用程序。虽然你的主要关注点可能是编写基于Web的用于被浏览器访问的应用程序,但是通过同时实现客户端和服务器,你一定能更加全面地理解Netty的API。
图2-1 Echo客户端和服务器
虽然我们已经谈及到了客户端,但是该图展示的是多个客户端同时连接到一台服务器。所能够支持的客户端数量,在理论上,仅受限于系统的可用资源(以及所使用的JDK版本可能会施加的限制)。
Echo客户端和服务器之间的交互是非常简单的;在客户端建立一个连接之后,它会向服务器发送一个或多个消息,反过来,服务器又会将每个消息回送给客户端。虽然它本身看起来好像用处不大,但它充分地体现了客户端/服务器系统中典型的请求-响应交互模式。
我们将从考察服务器端代码开始这个项目。
2.3 编写Echo服务器
所有的Netty服务器都需要以下两部分。
-
至少一个
ChannelHandler
——该组件实现了服务器对从客户端接收的数据的处理,即它的业务逻辑。 - 引导——这是配置服务器的启动代码。至少,它会将服务器绑定到它要监听连接请求的端口上。
在本小节的剩下部分,我们将描述Echo服务器的业务逻辑以及引导代码。
2.3.1 ChannelHandler和业务逻辑
在第1章中,我们介绍了Future
和回调,并且阐述了它们在事件驱动设计中的应用。我们还讨论了ChannelHandler
,它是一个接口族的父接口,它的实现负责接收并响应事件通知。在Netty应用程序中,所有的数据处理逻辑都包含在这些核心抽象的实现中。
因为你的Echo服务器会响应传入的消息,所以它需要实现ChannelInboundHandler
接口,用来定义响应入站事件的方法。这个简单的应用程序只需要用到少量的这些方法,所以继承Channel-InboundHandlerAdapter
类也就足够了,它提供了ChannelInboundHandler
的默认实现。
我们感兴趣的方法是:
-
channelRead()
——对于每个传入的消息都要调用; -
channelReadComplete()
——通知ChannelInboundHandler
最后一次对channel-Read()
的调用是当前批量读取中的最后一条消息; -
exceptionCaught()
——在读取操作期间,有异常抛出时会调用。
该Echo服务器的ChannelHandler
实现是EchoServerHandler
,如代码清单2-1所示。
代码清单2-1 EchoServerHandler
@Sharable ⇽--- 标示一个Channel- Handler可以被多个Channel安全地共享
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println(
"Server received: " + in.toString(CharsetUtil.UTF_8)); ⇽--- 将消息记录到控制台
ctx.write(in); ⇽--- 将接收到的消息写给发送者,而不冲刷出站消息
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
.addListener(ChannelFutureListener.CLOSE); ⇽--- 将未决消息冲刷到远程节点,并且关闭该Channel
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) {
cause.printStackTrace(); ⇽--- 打印异常栈跟踪
ctx.close(); ⇽--- 关闭该Channel
}
}
ChannelInboundHandlerAdapter
有一个直观的API,并且它的每个方法都可以被重写以挂钩到事件生命周期的恰当点上。因为需要处理所有接收到的数据,所以你重写了channelRead()
方法。在这个服务器应用程序中,你将数据简单地回送给了远程节点。
重写exceptionCaught()
方法允许你对Throwable
的任何子类型做出反应,在这里你记录了异常并关闭了连接。虽然一个更加完善的应用程序也许会尝试从异常中恢复,但在这个场景下,只是通过简单地关闭连接来通知远程节点发生了错误。
如果不捕获异常,会发生什么呢
每个
Channel
都拥有一个与之相关联的ChannelPipeline
,其持有一个ChannelHandler
的实例链。在默认的情况下,ChannelHandler
会把对它的方法的调用转发给链中的下一个Channel-Handler
。因此,如果exceptionCaught()
方法没有被该链中的某处实现,那么所接收的异常将会被传递到ChannelPipeline
的尾端并被记录。为此,你的应用程序应该提供至少有一个实现了exceptionCaught()
方法的ChannelHandler
。(6.4节详细地讨论了异常处理)。
除了ChannelInboundHandlerAdapter
之外,还有很多需要学习的ChannelHandler
的子类型和实现,我们将在第6章和第7章中对它们进行详细的阐述。目前,请记住下面这些关键点:
- 针对不同类型的事件来调用
ChannelHandler
; - 应用程序通过实现或者扩展
ChannelHandler
来挂钩到事件的生命周期,并且提供自定义的应用程序逻辑; - 在架构上,
ChannelHandler
有助于保持业务逻辑与网络处理代码的分离。这简化了开发过程,因为代码必须不断地演化以响应不断变化的需求。 -
转载自 并发编程网 - ifeve.com
最后更新:2017-05-18 20:36:18