序
本文主要研究一下NettyConnector的start及shutdown
NettyConnector
reactor-netty-0.7.6.RELEASE-sources.jar!/reactor/ipc/netty/NettyConnector.java
/**
* A Netty connector is an inbound/outbound factory sharing configuration but usually no
* runtime
* (connection...) state at the exception of shared connection pool setups. Subscribing
* to the returned {@link Mono} will effectively
* create a new stateful "client" or "server" socket depending on the implementation.
* It might also be working on top of a socket pool or connection pool as well, but the
* state should be safely handled by the pool itself.
* <p>
* <p>Clients or Receivers will onSubscribe when their connection is established. They
* will complete when the unique returned closing {@link Publisher} completes itself or if
* the connection is remotely terminated. Calling the returned {@link
* Disposable#dispose()} from {@link Mono#subscribe()} will terminate the subscription
* and underlying connection from the local peer.
* <p>
* <p>Servers or Producers will onSubscribe when their socket is bound locally. They will
* never complete as many {@link Publisher} close selectors will be expected. Disposing
* the returned {@link Mono} will safely call shutdown.
*
* @param <INBOUND> incoming traffic API such as server request or client response
* @param <OUTBOUND> outgoing traffic API such as server response or client request
* @author Stephane Maldini
* @since 0.6
*/
public interface NettyConnector<INBOUND extends NettyInbound, OUTBOUND extends NettyOutbound> {
/**
* Prepare a {@link BiFunction} IO handler that will react on a new connected state
* each
* time
* the returned {@link Mono} is subscribed. This {@link NettyConnector} shouldn't assume
* any state related to the individual created/cleaned resources.
* <p>
* The IO handler will return {@link Publisher} to signal when to terminate the
* underlying resource channel.
*
* @param ioHandler the in/out callback returning a closing publisher
*
* @return a {@link Mono} completing with a {@link Disposable} token to dispose
* the active handler (server, client connection...) or failing with the connection
* error.
*/
Mono<? extends NettyContext> newHandler(BiFunction<? super INBOUND, ? super OUTBOUND, ? extends Publisher<Void>> ioHandler);
/**
* Start a Client or Server in a blocking fashion, and wait for it to finish initializing.
* The returned {@link BlockingNettyContext} class offers a simplified API around operating
* the client/server in a blocking fashion, including to {@link BlockingNettyContext#shutdown() shut it down}.
*
* @param handler the handler to start the client or server with.
* @param <T>
* @return a {@link BlockingNettyContext}
*/
default <T extends BiFunction<INBOUND, OUTBOUND, ? extends Publisher<Void>>>
BlockingNettyContext start(T handler) {
return new BlockingNettyContext(newHandler(handler), getClass().getSimpleName());
}
/**
* Start a Client or Server in a blocking fashion, and wait for it to finish initializing.
* The returned {@link BlockingNettyContext} class offers a simplified API around operating
* the client/server in a blocking fashion, including to {@link BlockingNettyContext#shutdown() shut it down}.
*
* @param handler the handler to start the client or server with.
* @param timeout wait for Client/Server to start for the specified timeout.
* @param <T>
* @return a {@link BlockingNettyContext}
*/
default <T extends BiFunction<INBOUND, OUTBOUND, ? extends Publisher<Void>>>
BlockingNettyContext start(T handler, Duration timeout) {
return new BlockingNettyContext(newHandler(handler), getClass().getSimpleName(), timeout);
}
/**
* Start a Client or Server in a fully blocking fashion, not only waiting for it to
* initialize but also blocking during the full lifecycle of the client/server.
* Since most servers will be long-lived, this is more adapted to running a server
* out of a main method, only allowing shutdown of the servers through sigkill.
* <p>
* Note that a {@link Runtime#addShutdownHook(Thread) JVM shutdown hook} is added
* by this method in order to properly disconnect the client/server upon receiving
* a sigkill signal.
*
* @param handler the handler to execute.
*/
default <T extends BiFunction<INBOUND, OUTBOUND, ? extends Publisher<Void>>>
void startAndAwait(T handler) {
startAndAwait(handler, null);
}
/**
* Start a Client or Server in a fully blocking fashion, not only waiting for it to
* initialize but also blocking during the full lifecycle of the client/server.
* Since most servers will be long-lived, this is more adapted to running a server
* out of a main method, only allowing shutdown of the servers through sigkill.
* <p>
* Note that a {@link Runtime#addShutdownHook(Thread) JVM shutdown hook} is added
* by this method in order to properly disconnect the client/server upon receiving
* a sigkill signal.
*
* @param handler the handler to execute.
* @param onStart an optional callback to be invoked once the client/server has finished
* initializing.
*/
default <T extends BiFunction<INBOUND, OUTBOUND, ? extends Publisher<Void>>>
void startAndAwait(T handler, @Nullable Consumer<BlockingNettyContext> onStart) {
BlockingNettyContext facade = new BlockingNettyContext(newHandler(handler), getClass().getSimpleName());
facade.installShutdownHook();
if (onStart != null) {
onStart.accept(facade);
}
facade.getContext()
.onClose()
.block();
}
}
可以看到这个类有5个方法,一个newHandler是non-blocking模式的,其他的几个start开头的都是blocking模式的(
duration参数用于指定等待初始化完成的超时时间
),使用的是BlockingNettyContext
newHandler
newHandler返回的是一个Mono<? extends NettyContext>,在这个mono完成的时候,会自己dispose。
实例如下
@Test
public void testNewHandler() throws InterruptedException {
TcpClient client = TcpClient.create("localhost", 9090);
Mono<? extends NettyContext> mono = client.newHandler((inbound,outbound) -> {
return outbound.sendString(Mono.just("Hello World!")).then();
});
CountDownLatch latch = new CountDownLatch(1);
Disposable disposable = mono
.doFinally(e -> {
System.out.println("finish:"+e);
latch.countDown();
})
.subscribe();
latch.await();
System.out.println(disposable.isDisposed());
}
start
start方法返回的是BlockingNettyContext,用户可以调用BlockingNettyContext的shutdown方法来dispose nettyContext,比如
@Test
public void testShutdown(){
TcpClient client = TcpClient.create("localhost", 9090);
CountDownLatch latch = new CountDownLatch(1);
BlockingNettyContext context = client.start((inbound, outbound) -> {
latch.countDown();
return outbound.sendString(Mono.just("hello world"))
.then();
});
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
context.shutdown();
}
}
reactor-netty-0.7.6.RELEASE-sources.jar!/reactor/ipc/netty/tcp/BlockingNettyContext.java
/**
* Shut down the {@link NettyContext} and wait for its termination, up to the
* {@link #setLifecycleTimeout(Duration) lifecycle timeout}.
*/
public void shutdown() {
if (context.isDisposed()) {
return;
}
removeShutdownHook(); //only applies if not called from the hook's thread
context.dispose();
context.onClose()
.doOnError(e -> LOG.error("Stopped {} on {} with an error {}", description, context.address(), e))
.doOnTerminate(() -> LOG.info("Stopped {} on {}", description, context.address()))
.timeout(lifecycleTimeout, Mono.error(new TimeoutException(description + " couldn't be stopped within " + lifecycleTimeout.toMillis() + "ms")))
.block();
}
/**
* Remove a {@link Runtime#removeShutdownHook(Thread) JVM shutdown hook} if one was
* {@link #installShutdownHook() installed} by this {@link BlockingNettyContext}.
*
* @return true if there was a hook and it was removed, false otherwise.
*/
public boolean removeShutdownHook() {
if (this.shutdownHook != null && Thread.currentThread() != this.shutdownHook) {
Thread sdh = this.shutdownHook;
this.shutdownHook = null;
return Runtime.getRuntime().removeShutdownHook(sdh);
}
return false;
}
这里的shutdown主要是移除当前的shutdownHook,然后dispose nettyContext
startAndAwait
startAndAwait方法调用了BlockingNettyContext的installShutdownHook来进行关闭
reactor-netty-0.7.6.RELEASE-sources.jar!/reactor/ipc/netty/tcp/BlockingNettyContext.java
/**
* Install a {@link Runtime#addShutdownHook(Thread) JVM shutdown hook} that will
* shutdown this {@link BlockingNettyContext} if the JVM is terminated externally.
* <p>
* The hook is removed if shutdown manually, and subsequent calls to this method are
* no-op.
*/
public void installShutdownHook() {
//don't return the hook to discourage uninstalling it externally
if (this.shutdownHook != null) {
return;
}
this.shutdownHook = new Thread(this::shutdownFromJVM);
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
protected void shutdownFromJVM() {
if (context.isDisposed()) {
return;
}
final String hookDesc = Thread.currentThread().toString();
context.dispose();
context.onClose()
.doOnError(e -> LOG.error("Stopped {} on {} with an error {} from JVM hook {}",
description, context.address(), e, hookDesc))
.doOnTerminate(() -> LOG.info("Stopped {} on {} from JVM hook {}",
description, context.address(), hookDesc))
.timeout(lifecycleTimeout, Mono.error(new TimeoutException(description +
" couldn't be stopped within " + lifecycleTimeout.toMillis() + "ms")))
.block();
}
在shutdownHook里头注册了shutdownFromJVM方法,用于关闭NettyContext。
实例
@Test
public void testStartAndAwait(){
TcpClient client = TcpClient.create("localhost", 9090);
client.startAndAwait((inbound, outbound) -> {
return outbound.sendString(Mono.just("hello world"))
.then();
});
}
小结
NettyConnector提供了non-blocking及blocking两种使用方式,non-blocking的话,使用newHandler返回一个Mono<? extends NettyContext>,在它会在完成的时候,自己dispose nettyContext;blocking的话,startAndAwait方法会自动帮你注册shutdownHook来dispose nettyContext,而start方法则返回BlockingNettyContext,允许调用shutdown方法来dispose nettyContext。