《Netty实战》Netty In Action中文版 第1章——Netty——异步和事件驱动(一)
《Netty实战》样章由人民邮电出版社授权并发编程网发布,本书的中文版已经由人民邮电出版社引进并出版。
京东预售链接(优先发货):《Netty实战》([美]诺曼·毛瑞尔(Norman Maurer),马文·艾伦·沃尔夫泰尔(Marvin Allen Wolfthal))
第一部分 Netty的概念及体系结构
Netty是一款用于创建高性能网络应用程序的高级框架。在第一部分,我们将深入地探究它的能力,并且在3个主要的方面进行示例:
- 使用Netty构建应用程序,你不必是一名网络编程专家;
- 使用Netty比直接使用底层的Java API容易得多;
- Netty推崇良好的设计实践,例如,将你的应用程序逻辑和网络层解耦。
在第1章中,我们将首先小结Java网络编程的演化过程。在我们回顾了异步通信和事件驱动的处理的基本概念之后,我们将首先看一看Netty的核心组件。在第2章中,你将能够构建自己的第一款基于Netty的应用程序!在第3章中,你将开启对于Netty的细致探究之旅,从它的核心网络协议(第4章)以及数据处理层(第5章和第6章)到它的并发模型(第7章)。
我们将把所有的这些细节组合在一起,对第一部分进行总结。你将看到:如何在运行时配置基于Netty的应用程序的各个组件,以使它们协同工作(第8章),Netty是如何帮助你测试你的应用程序的(第9章)。
第1章 Netty——异步和事件驱动
本章主要内容
- Java网络编程
- Netty简介
- Netty的核心组件
假设你正在为一个重要的大型公司开发一款全新的任务关键型的应用程序。在第一次会议上,你得知该系统必须要能够扩展到支撑150 000名并发用户,并且不能有任何的性能损失,这时所有的目光都投向了你。你会怎么说呢?
如果你可以自信地说:“当然,没问题。”那么大家都会向你脱帽致敬。但是,我们大多数人可能会采取一个更加谨慎的立场,例如:“听上去是可行的。”然后,一回到计算机旁,我们便开始搜索“high performance Java networking”(高性能Java网络编程)。
如果你现在搜索它,在第一页结果中,你将会看到下面的内容:
Netty: Home
netty.io/
Netty是一款异步的事件驱动的网络应用程序框架,支持快速地开发可维护的高性能的面向协议的服务器和客户端。
如果你和大多数人一样,通过这样的方式发现了Netty,那么你的下一步多半是:浏览该网站,下载源代码,仔细阅读Javadoc和一些相关的博客,然后写点儿代码试试。如果你已经有了扎实的网络编程经验,那么可能进展还不错,不然则可能是一头雾水。
这是为什么呢?因为像我们例子中那样的高性能系统不仅要求超一流的编程技巧,还需要几个复杂领域(网络编程、多线程处理和并发)的专业知识。Netty优雅地处理了这些领域的知识,使得即使是网络编程新手也能使用。但到目前为止,由于还缺乏一本全面的指南,使得对它的学习过程比实际需要的艰涩得多——因此便有了这本书。
我们编写这本书的主要目的是:使得Netty能够尽可能多地被更加广泛的开发者采用。这也包括那些拥有创新的内容或者服务,却没有时间或者兴趣成为网络编程专家的人。如果这适用于你,我们相信你将会非常惊讶自己这么快便可以开始创建你的第一款基于Netty的应用程序了。当然在另一个层面上讲,我们也需要支持那些正在寻找工具来创建他们自己的网络协议的高级从业人员。
Netty确实提供了极为丰富的网络编程工具集,我们将花大部分的时间来探究它的能力。但是,Netty终究是一个框架,它的架构方法和设计原则是:每个小点都和它的技术性内容一样重要,穷其精妙。因此,我们也将探讨很多其他方面的内容,例如:
- 关注点分离——业务和网络逻辑解耦;
- 模块化和可复用性;
- 可测试性作为首要的要求。
在这第1章中,我们将从一些与高性能网络编程相关的背景知识开始铺陈,特别是它在Java开发工具包(JDK)中的实现。有了这些背景知识后,我们将介绍Netty,它的核心概念以及构建块。在本章结束之后,你就能够编写你的第一款基于Netty的客户端和服务器应用程序了。
1.1 Java网络编程
早期的网络编程开发人员,需要花费大量的时间去学习复杂的C语言套接字库,去处理它们在不同的操作系统上出现的古怪问题。虽然最早的Java(1995—2002)引入了足够多的面向对象façade(门面)来隐藏一些棘手的细节问题,但是创建一个复杂的客户端/服务器协议仍然需要大量的样板代码(以及相当多的底层研究才能使它整个流畅地运行起来)。
那些最早期的Java API(java.net
)只支持由本地系统套接字库提供的所谓的阻塞函数。代码清单1-1展示了一个使用了这些函数调用的服务器代码的普通示例。
代码清单1-1 阻塞I/O示例
ServerSocket serverSocket = new ServerSocket(portNumber); ⇽ -- 创建一个新的ServerSocket,用以监听指定端口上的连接请求
Socket clientSocket = serverSocket.accept(); ⇽ -- 对accept()方法的调用将被阻塞,直到一个连接建立
BufferedReader in = new BufferedReader(
new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out =
new PrintWriter(clientSocket.getOutputStream(), true); ⇽ -- 这些流对象都派生于该套接字的流对象
String request, response;
while ((request = in.readLine()) != null) { ⇽ -- 处理循环开始
if ("Done".equals(request)) {
break; ⇽ -- 如果客户端发送了“Done”,则退出处理循环
}
response = processRequest(request); ⇽ -- 请求被传递给服
务器的处理方法
out.println(response); ⇽ -- 服务器的响应被发送给了客户端
} ⇽ -- 继续执行处理循环
44代码清单1-1实现了Socket
API的基本模式之一。以下是最重要的几点。
-
ServerSocket
上的accept()
方法将会一直阻塞到一个连接建立,随后返回一个新的Socket
用于客户端和服务器之间的通信。该ServerSocket
将继续监听传入的连接。 -
BufferedReader
和PrintWriter
都衍生自Socket
的输入输出流。前者从一个字符输入流中读取文本,后者打印对象的格式化的表示到文本输出流。 -
readLine()
方法将会阻塞,直到在处一个由换行符或者回车符结尾的字符串被读取。 - 客户端的请求已经被处理。
这段代码片段将只能同时处理一个连接,要管理多个并发客户端,需要为每个新的客户端Socket
创建一个新的Thread
,如图1-1所示。
图1-1 使用阻塞I/O处理多个连接
让我们考虑一下这种方案的影响。第一,在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费。第二,需要为每个线程的调用栈都分配内存,其默认值大小区间为64 KB到1 MB,具体取决于操作系统。第三,即使Java虚拟机(JVM)在物理上可以支持非常大数量的线程,但是远在到达该极限之前,上下文切换所带来的开销就会带来麻烦,例如,在达到10 000个连接的时候。
虽然这种并发方案对于支撑中小数量的客户端来说还算可以接受,但是为了支撑100 000或者更多的并发连接所需要的资源使得它很不理想。幸运的是,还有一种方案。
1.1.1 Java NIO
除了代码清单1-1中代码底层的阻塞系统调用之外,本地套接字库很早就提供了非阻塞调用,其为网络资源的利用率提供了相当多的控制:
- 可以使用
setsockopt()
方法配置套接字
,以便读/写调用在没有数据的时候立即返回,也就是说,如果是一个阻塞调用应该已经被阻塞了[1]; - 可以使用操作系统的事件通知API[2]注册一组非阻塞套接字,以确定它们中是否有任何的套接字已经有数据可供读写。
Java对于非阻塞I/O的支持是在2002年引入的,位于JDK 1.4的java.nio
包中。
新的还是非阻塞的
NIO最开始是新的输入/输出(New Input/Output)的英文缩写,但是,该Java API已经出现足够长的时间了,不再是“新的”了,因此,如今大多数的用户认为NIO代表非阻塞I/O(Non-blocking I/O),而阻塞I/O(blocking I/O)是旧的输入/输出(old input/output,OIO)。你也可能遇到它被称为普通I/O(plain I/O)的时候。
1.1.2 选择器
图1-2展示了一个非阻塞设计,其实际上消除了上一节中所描述的那些弊端。
图1-2 使用Selector
的非阻塞I/O
class java.nio.channels.Selector
是Java的非阻塞I/O实现的关键。它使用了事件通知API以确定在一组非阻塞套接字
中有哪些已经就绪能够进行I/O相关的操作。因为可以在任何的时间检查任意的读操作或者写操作的完成状态,所以如图1-2所示,一个单一的线程便可以处理多个并发的连接。
总体来看,与阻塞I/O模型相比,这种模型提供了更好的资源管理:
- 使用较少的线程便可以处理许多连接,因此也减少了内存管理和上下文切换所带来开销;
- 当没有I/O操作需要处理的时候,线程也可以被用于其他任务。
尽管已经有许多直接使用Java NIO API的应用程序被构建了,但是要做到如此正确和安全并不容易。特别是,在高负载下可靠和高效地处理和调度I/O操作是一项繁琐而且容易出错的任务,最好留给高性能的网络编程专家——Netty。
最后更新:2017-05-18 20:36:41