vertx高级指南(二)
集成Netty
Netty是Vert.x的依赖之一。事实上,Netty给予了Vert.x网络服务的能力,你可以使用Vert.x Core编写以下类型的基本网络服务:
- TCP
- HTTP
- UDP
- DNS
它们是基于 Netty 的各种组件构建的。 Netty 社区已经实现了广泛的组件,本章解释了如何在 Vert.x 中集成这些组件。
在本章中,我们将构建一个 TIME 协议客户端和服务器。 Netty 文档提供了这个简单协议的客户端/服务器实现,我们将重点关注这些组件的集成。
Netty集成点
本章的主要目的是解释 Vert.x 的一些内部接口,此类接口是暴露与 Netty 交互的底层方法的扩展,这些方法对于直接重用 Netty 的组件非常有用。
大多数用户不需要处理这些扩展,因此这些方法被隔离在扩展接口中
启动客户端
ContextInternal
继承了 io.vertx.core.Context
并暴露了一些 Netty 集成点,比如 VertxInternal
。
通常情况下,上下文是从 Vertx#getOrCreateContext()
方法获取的,该方法返回当前执行上下文,或者在必要时创建一个新的上下文:在 Verticle 中调用时,getOrCreateContext()
返回此 Verticle 的上下文,当在主线程或单元测试等非 Vert.x 线程中使用时,它会创建一个新上下文并返回它。
1 | Context context = vertx.getOrCreateContext(); |
上下文始终与 Netty 事件循环(event loop)相关联,因此使用此上下文可确保我们的组件重复使用相同的事件循环(如果之前存在)或使用新的事件循环。
ContextInternal#nettyEventLoop()
方法返回这个特定的事件循环,我们可以在 Bootstrap(对于客户端)或 ServerBoostrap(对于服务器)上使用它:
1 | ContextInternal contextInt = (ContextInternal) context; // 1 |
- 获取这个上下文关联的event loop
- 创建客户端的Bootstrap
启动服务端
VertxInternal
扩展了 io.vertx.core.Vertx
,其中 VertxInternal#getAcceptorEventLoopGroup()
返回一个 EventLoopGroup
用于接受服务器上的连接,它的常见用法是在 ServerBootstrap 上:
1 | ContextInternal contextInt = (ContextInternal) context; // 1 |
- 获取这个上下文关联的event loop
- 获取这个Vertx的Acceptor event loop组
- 创建服务端的Bootstrap
处理事件
现在我们对ContextInternal
更熟悉了,让我们看看如何使用它来处理Netty事件,如网络事件、channel生命周期等…
ContextInternal#emit
方法用于向应用程序发出事件,它确保了:
- 上下文并发性:重复利用当前的event-loop线程或者在worker线程执行
- 当前上下文与调度线程的thread local关联
- 任何抛出的未捕获异常都会在上下文中报告,这样的异常要么被记录,要么被传递给
context#exceptionHandler
如下展示了一个简短的服务启动代码:
1 | Handler<Channel> bindHandler = ch -> { |
emit
的典型用法是将一个或多个事件下发到同一个处理程序,就像事件处理程序一样。
当涉及到future
时,ContextInternal#promise
方法会创建一个promise
,这个promise
会像emit
一样对监听器执行操作。
服务端
你可以在这里找到原始的服务器代码实例。
本文示例的Vert.x TIME服务代码暴露了一个简单的接口:
- 一个创建
TimeServer
的静态方法 - 两个方法:通过
listen
绑定服务,通过close
解绑服务 requestHandler
用于设置处理请求的处理器handler
1 | public interface TimeServer { |
接下来实现一个返回当前JVM时间的TIME服务:
1 | Vertx vertx = Vertx.vertx(); |
现在让我们研究一下服务器的实现。
服务端的bootstrap
首先让我们看一下ServerBootstrap的创建和配置
1 | EventLoopGroup acceptorGroup = vertx.getAcceptorEventLoopGroup(); // 1 |
VertxInternal
返回event loop组当作Acceptor组(Boss接受请求)ContextInternal
返回 event loop组用child组(worker处理请求)- 创建并配置Netty的
ServerBootstrap
- 使用
requestHandler
初始化TimeServerHandler
并通过TimeServerHandler
配置channel
ServerBootstrap的创建非常简单,与直接使用Netty的版本非常相似,主要的区别在于,我们复用了Verticle和Vert.x提供的事件循环event loop,这确保了我们的服务器共享应用程序的资源(这里指event loop)。
这里要注意,TimeServerHandler
是用服务器的requestHandler
初始化的,这个handler将在提供TIME请求时使用。
服务绑定
现在让我们来看一下绑定操作,它与直接使用Netty的原始版本示例有很多不同但区别也不是特别大:
1 | Promise<Void> promise = context.promise(); // 1 |
- 创建一个绑定这个Server上下文的promise
- 将结果promise设置为完成或者成功(代码中应为完成或者失败)
- 返回future结果
此处最重要的部分是创建上下文promise,用于让应用程序知道绑定结果。
服务handler
现在,让我们用TimeServerHandler
来完成我们的服务器,它改写自Netty原始版本TimeServerHandler
:
1 | Promise<Long> result = Promise.promise(); // 1 |
- 创建一个将由
requestHandler
解析的空promise - 让上下文使用emit将事件发送给
requestHandler
- 当
requestHandler
的实现完成相关的promise时,调用future的处理程序 - 将当前时间写入channel,然后关闭
- 如果应用程序失败,只需关闭socket套接字
emit
是 TIME请求事件发生时使用的, 将需要完成(complete)的promise传递给 requestHandler
. 当promise完成了(completed), handler将时间结果写入到通道或将其关闭。
客户端
你可以在这里找到原始的客户端代码实例。
本文示例的Vert.x TIME客户端暴露了一个简单的接口:
- 一个创建
TimeClient
的静态方法 - 客户端从服务端获取时间的
getTime
方法
1 | public interface TimeClient { |
TIME客户端使用起来很简单:
1 | Vertx vertx = Vertx.vertx(); |
现在让我们研究下客户端的实现。
客户端的bootstrap
首先让我们看下客户端Bootstrap
的创建和配置
1 | EventLoop eventLoop = context.nettyEventLoop(); // 1 |
VertxInternal
返回event loop用作child组(用于接受请求)- 创建并配置 Netty bootstrap
- 通过返回结果处理器
resultHandler
(这里是个promise)初始化TimeClientHandler
,然后配置channel
Bootstrap的创建非常简单,与Netty原始版本非常相似,主要区别在于我们使用了Verticle提供的event loop事件循环,这确保了我们的客户复用与Verticel相同的event loop。
就像在服务器部分的示例中一样,我们使用ContextInternal
来获取要在Bootstrap
上设置的Netty的EventLoop
。
需要注意的是,TimeClientHandler
是用客户端resultHandler
初始化的,这个处理程序将用于TIME请求结果调用。
客户端连接
BootStrap程序的设置与原始的示例非常相似,在失败的情况下,应用程序将使用一个包含整体结果的promise作为回调。
1 | ChannelFuture connectFuture = bootstrap.connect(host, port); // 1 |
- 连接到服务器
- 一旦连接失败,将promise置为失败
客户端handler
现在,让我们用TimeClientHandler
来完成我们的客户端,它是对Netty原始版本TimeClientHandler
的改写:
1 | ByteBuf m = (ByteBuf) msg; |
- 解码从服务器返回的时间
- 使用response将
resultPromise
置为完成 - 将
resultPromise
设置为null - 关闭channel
这里重复说明下,当TIME响应事件发生时,我们将resultPromise
设置为完成。