Java tutorial
/** * * Copyright 2017 Florian Erhard * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package gedi.remote; import gedi.app.extension.ExtensionContext; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.Workarounds; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import java.net.URI; import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; public class RemoteConnections { private static final Logger log = Logger.getLogger(RemoteConnections.class.getName()); private static RemoteConnections instance; public static RemoteConnections getInstance() { if (instance == null) instance = new RemoteConnections(); return instance; } private RemoteConnections() { } /** * Connects to the given server; the mechanics is as follows: * <br/> * The initChannel consumer is supposed to register channel handlers (called upon channel.registered) * <br/> * Depending on the outcome of the Bootrap.connect method, either the errorHandler or the connectedHandler is called. * <br/> * If the connection is lost, the closedHandler is called. * <br/> * If you want to cancel the connection attempt, invoke cancel on the returned ChannelFuture. If you want to terminate the connection, use the * channel object from the connectedHandler. * <br /> * If the channel is unregistered, the added flag is reset for all handlers bound to the channel pipeline (important if you want to reuse * the handlers added by the initChannel consumer). * * @param uri * @param initChannel * @param errorHandler * @param connectedHandler * @param closedHandler * @return */ public ChannelFuture connect(URI uri, Consumer<SocketChannel> initChannel, Consumer<Throwable> errorHandler, Consumer<SocketChannel> connectedHandler, Runnable closedHandler) { final Protocol protocol = ProtocolExtensionPoint.getInstance().get(ExtensionContext.emptyContext(), uri.getScheme()); if (protocol == null) throw new RuntimeException("Protocol " + uri.getScheme() + " unknown!"); EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap(); b.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); protocol.setCodecs(pipeline); initChannel.accept(ch); pipeline.addLast(new ChannelInboundHandlerAdapter() { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { pipeline.addLast(new ConfigLoggingHandler(ConfigLoggingHandler.LogLevel.INFO)); connectedHandler.accept(ch); super.channelActive(ctx); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); closedHandler.run(); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { ctx.pipeline().iterator().forEachRemaining((e) -> Workarounds.removeAdded(e.getValue())); super.channelUnregistered(ctx); } }); } }); // Make a new connection and wait until closed. ChannelFuture f = b.connect(uri.getHost(), uri.getPort() == -1 ? protocol.getDefaultPort() : uri.getPort()) .addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { Throwable cause = future.cause(); if (cause != null) { log.log(Level.INFO, "Connection failed to server " + uri + ": " + cause.getMessage()); try { errorHandler.accept(cause); } finally { group.shutdownGracefully(); } } else { log.log(Level.INFO, "Client connected to server " + uri); future.channel().closeFuture().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { log.log(Level.INFO, "Connection closed to server " + uri); group.shutdownGracefully(); } }); } } }); return f; } // /** // * Blocks! // * @param url // * @param handler // * @return // */ // public void connectSync(URI uri, final Consumer<SocketChannel> initChannel) { // // final Protocol protocol = ProtocolExtensionPoint.getInstance().getProtocol(uri.getScheme()); // if (protocol==null) throw new RuntimeException("Protocol "+uri.getScheme()+" unknown!"); // // // EventLoopGroup group = new NioEventLoopGroup(); // try { // Bootstrap b = new Bootstrap(); // b.group(group) // .channel(NioSocketChannel.class) // .handler(new ChannelInitializer<SocketChannel>() { // // @Override // protected void initChannel(SocketChannel ch) throws Exception { // ChannelPipeline pipeline = ch.pipeline(); // pipeline.addLast(new LoggingHandler(LogLevel.INFO)); // protocol.setCodecs(pipeline); // initChannel.accept(ch); // } // }); // // // // Make a new connection and wait until closed. // ChannelFuture f = b.connect(uri.getHost(), uri.getPort()==-1?protocol.getDefaultPort():uri.getPort()) // .addListener(new ChannelFutureListener() { // // @Override // public void operationComplete(ChannelFuture future) throws Exception { // System.out.println(future.cause()); // } // }) // .sync(); // // log.log(Level.INFO, "Client connected to server "+uri); // f.channel().closeFuture().sync(); //// } catch (ConnectException e) { // // } catch (InterruptedException e) { // log.log(Level.SEVERE, "Client thread interrupted", e); // } finally { // group.shutdownGracefully(); // } // } // // /** // * Returns without blocking! // * @param url // * @param handler // * @return // */ // public void connectAsync(final URI uri, final Consumer<SocketChannel> initChannel) { // new Thread() { // public void run() { // connectSync(uri, initChannel); // } // // }.start(); // } public RetryRemoteConnection connectRetry(URI uri, final Consumer<SocketChannel> initChannel, long retryMillisec, boolean killConnectionOnInterupt) { RetryRemoteConnection re = new RetryRemoteConnection(uri, initChannel, retryMillisec, killConnectionOnInterupt); new Thread(re).start(); return re; } /** * Returns without blocking! * @param url * @param handler * @return */ public void serveSync(Protocol protocol, final Consumer<SocketChannel> initChannel) { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .handler(new ConfigLoggingHandler(ConfigLoggingHandler.LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ConfigLoggingHandler(ConfigLoggingHandler.LogLevel.INFO)); protocol.setCodecs(pipeline); initChannel.accept(ch); } }); ChannelFuture f = b.bind(protocol.getDefaultPort()).sync(); log.log(Level.INFO, "Server thread started for protocol " + protocol + " on port " + protocol.getDefaultPort()); f.channel().closeFuture().sync(); } catch (InterruptedException e) { log.log(Level.SEVERE, "Server thread interrupted", e); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }