关注微信公众号(瓠悠笑软件部落),大家一起学习,一起摸鱼。
Netty 是一个嘿洋格的高性能网络程序框架。让我给你好好摆哈儿他最拽的三个地方:
- 只有网络专家才可以用netty编程?你跟老子爬!要是那样?那我们这些小虾米玩个屁呀。
- 你说你拽得很,硬要直接用底层的Java API进行开发。我不拦到你,有摩托你不骑就算了,在后面撵嘛。
- Netty 把网络层的逻辑实现得巴巴适适的,你只需要翘起二郎退写你的业务代码就可以了。
在第一章,让我们先了解一下Java网络变成的前世今生。在我们摸清了 异步通信(asynchronous communications) 和 基于事件的驱动处理(event driven processing) 的概念后,再去看哈儿 netty 的核心组件。到那时候呀(第二章),你就可以自己捏出第一个 netty 程序出来了。接着呢(第三章),你就该学会如何调教你捏出来的娃儿,(第四章)摸哈儿他可以使用的核心网络协议,(第五章和第六章)看哈数据处理层。(第七章)最后就玩一下洋盘的并发模型了。
你以为你学满贯了,莫慌!上面说那些只是第一部分呢。后头还有更精彩的表演。你会看到Netty这个车车儿在正在跑的情况下,如果更改基于他的程序组建配置。而不翻车。最后呢(第九章),讲哈Netty咋个教你测试你写的程序。
Netty - asynchronous and event-driven
这篇讲三点
- java 里面的网络编程
- 介绍哈 Netty
- netty 的核心部件儿
想像哈儿你在一家嘿大,嘿强的公司里头开发一个嘿关键的系统。领导说: 今年我们业务发展得好,我们的系统必须给老子扎起!要同时支撑 15万个客户耍,还不能卡起卡起的。然后那些人都把你盯到起,等你接话。
你两个脚打闪闪儿,然后麻起胆子说:“莫得问题,包在我身上!”。 然后大家都拍巴巴掌了。但是哪些人私下在想: “这个细娃儿得行不哦?冒皮皮打飞机。等到看他龟儿的洋相!“。散会后你回到各人的位置上,偷偷摸摸地打开电脑搜:”high performance Java networking.”
搜出来的第一个结果就是Netty
Netty: Home
netty.io/
Netty is an asynchronous event-driven network application framework for rapid
development of maintainable high performance protocol servers & clients.
找是找到了,但是看不懂的嘛。咋办嘛?去官网上逛哈,把代码下下来,仔细阅读Javadocs和一些博客。然后开始干。如果你是一个熟悉网络编程的老师机,你可能会搞得起来。否则呀,弄不起的嘛。
为啥子呢?高性能的系统。不是说你会写几行if else就玩得转的,他还要求你会这样,会那样:网络编程,多线程,并发。Netty 把这些硬骨头打成粉粉儿,即使你是网络新手,也可以补钙。但是到目前为止,都不晓得咋个泡这些骨头粉粉儿,所以我才在这里批跨。
我们的主要目的就是让各位都泡得来netty这个牌子的骨头粉粉儿。喝了这些粉粉,你就会涨很多知识,还可以写很多服务。如果你莫得时间去学习网络编程,也不想花那么多时间去成为一个网络专家。哦,对头,要耍朋友得嘛,要买菜得嘛,哪来那么多时间嘛。嘿嘿!你算找对人了。只要你学会了如何用Netty. 要不到好久你就可以搞出你的网络程序开山之作了。同时呢,我们想让寻找工具的高手看得起Netty,用它去创建自己的工作协议。
Netty 这个筐筐头确实有很多网络工具包,我们要花很多时间去把玩它。但netty终究是一个框框。这个框框是咋个搭的呢?为啥子要这么搭呢?搞清这些问题和如果学会用哪些工具包一样重要。不然你连门都找不到,咋个入洞房嘛?所以呢,我们先聊一下这些点点:
- 各干各的(搞业务的搞业务,搞网络的搞网络,莫混到一起了)
- 分成一堆一堆的,哪个都可以用,想怎么用就怎么用(模块化和可重用性)
- 上赛道之前,随时都可以拿出来跑。(可测试性是最基本要求)
在这章里头,我们要讲哈儿高性能网络编程的背景,特别是实现了JDK提供的接口这种方式。这些讲了后,我们就将哈Netty, 他的核心概念是啥子? 他的构建模块是啥子? 最后呀,你就要准备开始捏一下你的第一个基于Netty的客户端和服务器程序了哦。
1.1 Networking in java
在网络变成的早些时候,那些先人们,不晓得花了好多时间去学习C语言套接字库的复杂性。还要处理不同操作系统上面稀缺古怪的问题。早期的Java版本(1995-2002)就遵循了面向对象编程的观念。把哪些吱吱哇哇的细节都藏起来了,但是要想创建一个复杂的 客户端/服务端 协议,仍然要写嘿多样板代码(有时候还要去看下那些细节,才晓得咋个回事,才能把它跑起来)。
那些头版 Java API(java.net)使用了系统自带的库,但只能提供阻塞的函数。你看嘛,那哈的代码咋个写的:
上面的纸飞飞儿实现了一个基本的Socket API模式。关键点是这些:
- accept() ServerSocket接收一个门牌号(端口), 就开始开门迎客了(accept). 它要一直等呀等,等到服务端和客户端的链接建立起开(Socket). 期间啥子都不能做。
- BufferedReader 负责从 Socket 里面拿数据(input streams), 看看别个发的到底是啥子消息。 PrintWriter 负责往 Socket 里面写数据(output streams), 把Java里面的对象按照格式弄成文本后,传给别个。
- readLine() readLine 从 BufferReader 里面捞数据, 直到遇到由换行符或回车符结束的字符串为止。在没有前就一直等到起。
- 然后客户端的请求才被处理了。
你看嘛,在同一个时间只能处理一个链接。要想同时处理多个客户端呀,就要为每个客服端淡出来一个Thread去创建新的 Socket链接,就像下面的图画的那样。
摸到脑壳想一下嘛,这么做要不得。你看嘛,哪些和客户端建立链接的线程,就想人一样,也要打瞌睡的。别个的任务就是处理输入数据和输出数据。客户端的数据紧到不送起来,或者你要给客户端的妹儿发的悄悄话紧到憋不出来。那别人有能去干其他事情,只有干等到起。这些 Thread 还把内存呀,磁盘呀,这些贵的资源占到起. 人家心头想: “老子没有用完,你敢跟老子强。爬开些!” 。结果大家都这样,好浪费嘛。尽是些占到毛坑耍手机等拉屎的龟儿子。再说,每个 Thread 会根据 不同的系统,分配 64KB 到 1MB 的内存。 再说, 即使Java虚拟机可以在物理上嘿多嘿多的线程数量, 但是CPU 有没有那么多嘛?一哈儿干这样,一哈儿干那样,跑过去跑过来的,还要想哈上下文。好鸡儿打脑壳。万一是数量多到撑不住了瑟,就要冒烟了哦。 不信呀?你开一万个链接看一哈儿嘛。
所以这种方法只能在村里头给娃娃摆咔咔架儿耍,一旦人多了,就玩不转了。幸运的是,还有其他选择。
1.1.1 Java NIO
上面之说以卡,是因为使用了系统提供的阻塞式方法。随着操作系统的发展。系统上原生的套接字库(socket libraries)也在升级。人家发现这样子玩下去要不得,就引入了非阻塞式调用(non-blicking calls).这哈就有意思了,我们可以想哈儿如何更高效地利用网络资源了。
- setsockopt() 使用 setsockopt 方法,你就可以配置 sockets. 这么起配置的 sockets 呀,当你读写数据的时候,一但没有数据。他们就马上返回了。你想哈之前呢?如果没有数据,会一直等到有数据。哎呀忽悠不下去了,原文让看哈这本书
- event notification API. 人家提供了事件注册API.你可以在里面注册一堆 非阻塞的 sockets. 只要那个有数据来了,或者想写数据给clients. 他就通知用哪个client。看哈这里嘛
Java 是在 2002年 支持 非阻塞 I/O 的,在JDK 1.4的 java.nio包里面。那哈你是不是还在和娃哈哈呢?
New or non-blocking?
NIO was originally an acronym for New Input/Output, but the Java API has been around
long enough that it is no longer new. Most users now think of NIO as signifying non-
blocking I/O, whereas blocking I/O is OIO or old input/output. You may also encoun-
ter references to plain I/O.
1.1.2 Selectors
下面这个纸片片画的是非阻塞式设计。它解决了上个设计聊到的缺点。
java.nio.channels.Selector类是 Java non-blocking I/O 关键点。有一群 no-blocking sockets 注册在他哪里,然后他通过 事件通知API (event notification API) 发号施令,喊那个准备好处理 I/O. 哪个就要准备好处理 I/O. 因为任何读取或写入操作都可以随时检查其完成状态,所以单个线程可以处理多个并发连接。
总的来说,这种模型比 blocking I/O model 提供了更好的资源管理:
- 可以让几个 Selector 的 线程负责处理许多 connections. 这样减少了内存的占用和不同线程之间来回的上下文切换。
- 当没有要处理的 I/O 时,可以将线程重定向到其他任务。
虽然很多程序是直接用 Java NIO API 构建起来的。但是要想做好,做安全,不是那么简单的。特别是在高负载时。这是一项繁琐且容易出错的任务。那咋个办?用Netty呗!他可是高性能网络专家。
1.2 Introducing Netty
想哈你在会上吹过的牛逼!支持15万个客户端并发链接。想想别人看你的眼神,透露出什么? ”这根本不可能嘛!"。今天,作为系统用户,我们认为这种能力是理所当然的,而作为开发人员,我们期望这样做走得更高。 我们知道总是需要更高的吞吐量和可扩展性 - 以更低的成本交付。
不要低估了以更低的成本交互这一点。直接使用底层的APIs,会把你虐惨。那些API太复杂了,你会发现本来想干一件很简单的事情,结果做的时候这也不懂,那也不懂,触及了知识的盲区。而不去搞懂那些知识又做不下去。因此,想想面向对象编程的基本概念:不要给我讲那么多细节。你做好了提供一个方法给我用就可以了,我管你怎么实现的。你只需要弄动我要你做的是什么就可以了。
这一原则促进了许多框架的发展,封装常见编程任务的解决方案,其中许多与分布式系统开发密切相关。我猜想所有专业的Java开发人员都熟悉这些公共jar包中的一个。如果你想不用第三方的Jar包搞一个工程呀,你的时间和技术怕是不够用哦。
在网络领域,Netty是Java的卓越框架。利用易于使用的API背后的Java高级API的强大功能,Netty让您自由专注于你真正感兴趣的东西 - 你的应用程序的独特价值。
在我们开始第一次仔细研究Netty之前,请先检查一下表格中的主要功能表。 有些是技术性的,有些则更具建筑性或哲学性。 在本书的过程中,我们将不止一次地重温它们。
1.2.1 Who uses Netty?
Netty拥有一个充满活力且不断发展的用户社区,其中包括大型公司,如 Apple,Twitter,Facebook,Google,Square和Instagram,以及流行开放 Infinispan,HornetQ,Vert.x,Apache Cassandra和Elastic-search等源代码项目,所有这些项目都在其核心中采用了强大的网络抽象码。 在创业公司中,Firebase和Urban Airship正在使用Netty,前者用于长期的HTTP连接,后者用于各种推送通知。
无论何时使用Twitter,您都在使用Finagle,它们基于Netty的框架系统间通信。 Facebook在Nifty中使用Netty,这是他们的Apache Thrift服务。
可扩展性和性能是两家公司的关键问题,两者都是Netty的常规贡献者。反过来,Netty从这些项目中受益,通过实施FTP,SMTP,HTTP 和 Web Socket 等协议实现,同时增强了它的领域和灵活性范围。
1.2.2 Asynchronous and event-driven
后面我们会把异步这个词挂在嘴边,到底异步是啥子哦?异步呀?就是异步啥。就是非同步嘛。你不是说废话吗?你想哈邮箱:当你给对方发送一封邮件后,你可能收得到回信,也可能收不到。也可能你正在给他发邮件的时候,收到了他给你发送的一封邮件。异步事件虽然是异步的,但有可能是有顺序关联的。如果你发邮件问了一个问题,通常你会收到对发发给你的解答。可能在等待他发邮件给你之前,你可以去做点其他事情。
在我们每天的生活中,异步事件随时随地都在发生,就莫想那么多嘛。但是要想让电脑也想那样子工作瑟,就可能初选一些典型的问题哟。 从本质上讲,对我们来说,同步和事件驱动的系统展示了一个特定的的行为,这是一种非常有价值的行为:它可以在任何时间和任何顺序对发生的事件作出反应。
此功能对于实现最高级别的可伸缩性至关重要,定义为“系统,网络或流程处理日益增长的工作的能力
有能力的方式或扩大能力以适应这种增长。“
异步和可伸缩性之间有什么联系?
- 非阻塞网络调用(non-blocking networks calls)使我们不必等待完成操作。 完全异步的I / O构建于此功能之上,并向其迈出一步:异步方法立即返回,当它完成时,直接或稍后通知用户。
- Selectors 允许我们用更少的 threads 为多个事件监视许多连接。
将这些特性放在一起,使用 No-blocking I/O,与Blocking I/O相比。 我们可以更快,更经济地处理非常大的事件的数量. 从网络的角度来看,这是我们想要构建这类系统的关键,正如您所看到的,它也是Netty从头开始设计的关键。
在下一节中,我们将首先了解Netty的核心组件。 目前,将它们视为域对象(domain objects)而不是具体的Java类。 随着时间的推移,我们会看到他们如何合作提供有关网络上发生的事件的通知,并使它们可供处理。
1.3 Netty’s core comments
在本节中,我们将讨论Netty的主要构建块:
- Channels
- Callbacks
- Futures
- Events and handlers
这些构建块代表不同类型的构造:资源(resources),逻辑(logic)和通知(notifications)。 您的应用程序将使用它们来访问网络和数据流。
对于每个组件,我们将提供一个基本定义,并在适当的情况下提供一个简单的代码示例来说明其用法。
1.3.1 Channels
Channel是Java NIO的基本构造。 它代表了
an open connection to an entity such as a hardware device, a file, a
network socket, or a program component that is capable of performing
one or more distinct I/O operations, for example reading or writing. 参考
现在,将 Channel 视为传入(inbound)和传出(outbound)数据的载体绑定。 因此,它可以是打开的或关闭的,连接的或断开的。
1.3.2 Callbacks
callback 是一个简单的方法。 把这个方法的引用作为钩子传递给另外一个方法。然后代码就可以做其他事情了,后面这个方法想啥时候调用前面这个方法,就可以调用。回调(callbacks)在编程开发中经常用到。它是一种经常用到的方法。通知注册方,你要我做的事情完成了。
Netty 在内部处理时间时用到了回调; 当触发了回调函数时。 接口 ChannelHandler的实现类就会去处理这个事件。下面就是一个例子: 当一个新的链接被建立起来后,就会调用 ChannelHandler 的 channelAcive() 方法。然后打印消息。
1.3.3 Futures
Future 提供了另外一种方法:当一个操作完成后,通知对应的application. 这个对象充当了异步操作结果的占位符。在未来的某个时间点他将完成,并提供方法访问执行的结果。
JDK 本身提供了 java.util.concurrent.Future 接口,但是提供的实现,只允许你手动检查这个操作有没有执行完,在没执行检查前是阻塞的。这样嘿不安逸。所以 Netty 就各人搞了一个实现, ChannelFuture. 当一个异步操作被执行的时候使用。
ChannelFuture 提供了额外的方法,让你注册一个或者多个 ChannelFutureListener 实例。这些监听类的 operationComplete() 方法, 当操作执行完的时候就会被调用。这时候监听类就可以检查到底是执行成功了呢,还是执行出错了。如果出错了,我们就可以看哈跑出来的异常。长话短说, ChannelFutureListener 提供了通知机制,就不需要一直跑去检查执行结果了。
每一个 Netty 的 I/O 操作都会返回一个 ChannelFuture; 也就是说,他们都是非阻塞的。就像前面说的那样,Netty就是基于此实现了异步和基于时间驱动。
下面的代码描述了 ChannelFuture 如何作为 I/O 操作的返回。注意哦! connect() 方法会直接返回,不会有任何阻塞,然后后台会去建立链接。这个链接什么时候建立,可能有很多原因。但是这个链接的建立代码逻辑,已经抽象出来了,从这里拿开了。因为这个Thread 等待这个操作完成是非阻塞的, 在等的时候它就可以去做其他事情了,所以可以更高效的使用资源了。
下面讲哈如何利用监听类: ChannelFutureListener. 首先你先链接到远端。然后将 ChannelFutureListener 注册到 connect() 方法返回的 ChannelFuture 类里面。当建立建立起来后,就会通知这个监听类。你可以检查这个状态,如果操作是成功的,你可以往 Channel 里面写数据了,如果失败了,你可以看看 ChannelFuture 抛出来的异常到底是啥子。
注意这里的错误如何处理完全取决于你,主题, 当然, 任何约束由手头的具体错误造成的。例如, 在连接失败的情况下,您可以尝试重新连接或建立到另一个远程对等方的连接。
如果你认为一个 ChannelFutureListener 是一个更复杂的 callback 版本, 你是对的。事实上, callbaks 和 Futures 是相辅相成, 结合起来用的, 它们构成了 Netty 本身的关键组成部分之一。
1.3.4 Events and handlers
Netty 使用不同的事件来通知我们有关状态或操作状态的更改。这使我们能够根据具体发生了什么,采取对应合适的行为。这类行动可能包括:
- Logging 记录日志
- Data transformation 数据传输
- Flow-control 流控制
- Application logic 应用程序逻辑
Netty 是一个网络框架, 因此事件按它的入站(inbound)或出站(outbound)数据流进行划分。
时间可能被 **inboound ** 数据 或 相关的状态更改触发,包括:
- Active or inactive connections
- Data reads
- User events
- Error events
一个 outbount event 一个 future 中operation 触发的结果,可能是这些:
- 打开或者关闭远端的一个链接
- 往 socket 里面写数据,或者刷新数据
可以将每个事件调度到处理程序类的用户实现的方法。这是一个很好的例子, 一个事件驱动的范式直接转换为应用程序积木。上面显示了如何通过这样的处理程序链处理事件。
Netty 的 ChannelHandler 提供了上面类似处理的基本抽象方法。 后面会讲很多ChannelHandler的内容,但现在你可以把每一个handler当作是 response中对各个特定时间的回调实例。
Netty 提供了很多额外的已经定义号的handlers。你直接拿来用就是了,包括中 HTTP 和 SSL/TLS 协议的handlers. 总的来说,ChannelHandlers 使用 events 和 futures, 把他们抽象起来,让你的程序可以拿来用。
1.3.5 Putting it all together
在本章中, 您已经介绍了 Netty 的高性能网络方法。工作及其实施的一些主要组成部分。让我们组装起来,以使我们对所讨论的问题有一个大局观。
FUTURES , CALLBACKS , AND HANDLERS
Netty 的异步编程模型是建立在 Futrues 和 callbacks, 与调度事件到处理程序的方法发生在更深的水平。总之, 这些元素提供了一个处理环境, 允许应用程序的逻辑, 以独立于任何与网络相关的问题而开发维护。这是 Netty 设计方法的一个关键目标。
动态拦截操作(Intercepting operations) 和 转换入站(inbound) 或出站(outbound)数据只要求您提供回调(callback), 或利用 operations 返回 Future 对象。这使得链接操作变得简单和高效, 并促进编写可重用的通用代码。
SELECTORS, EVENTS, AND EVENT LOOPS
Netty 通过触发事件将选择器从应用程序中抽象出来, 从而消除所有手写的调度代码, 否则将是必需的。在将事件循环分配给每个通道, 以处理所有事件, 包括:
- Registration of interesting events 注册感兴趣的事件
- Dispatching events to ChannelHandlers 将事件调度给对应的 Channelhandlers
- Scheduling futher actions. 为要发生的特定结果编写对应的处理逻辑
事件循环(EventLoop) 本身仅由一个线程驱动, 该线程处理一个通道(Channel), 并且在事件循环的生存期内不会更改。这个简单而强大的设计消除了任何关于同步的问题, 因此您可以专注于提供正确的逻辑来执行当有有趣的数据需要处理时。正如我们将看到的, 当我们探索 Netty 的线程在模型上, API 简单紧凑。
1.4 Summary
在本章中, 我们将介绍 Netty 框架的背景, 包括Java 网络 API 的演变, 阻塞和非以及异步 ito 在大容量下的优势,高性能网络。然后, 我们对 Netty 的功能、设计和优势进行了概述。这些包括 Netty 的异步模型的基础机制, 包括 callbacks, futures, 以及它们的结合使用。我们还谈到了 events 是如何生成的
以及如何拦截和处理它们。展望未来, 我们将更深入地探讨如何这个丰富的集合工具可用于满足您的应用程序的特定需求。在下一章中, 我们将深入研究 Netty api 和编程的基础知识模型, 您将编写您的第一个客户端和服务器。