Java tutorial
/* * Copyright (c) 2016 "JackWhite20" * * This file is part of JaPS. * * JaPS is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.jackwhite20.japs.shared.nio; import de.jackwhite20.japs.shared.config.ClusterServer; import de.jackwhite20.japs.shared.net.ConnectException; import de.jackwhite20.japs.shared.pipeline.ChannelUtil; import de.jackwhite20.japs.shared.pipeline.PipelineUtils; import de.jackwhite20.japs.shared.pipeline.initialize.ClientChannelInitializer; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import org.json.JSONObject; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * Created by JackWhite20 on 16.06.2016. */ @ChannelHandler.Sharable public abstract class NioSocketClient extends SimpleChannelInboundHandler<JSONObject> { private static final int CONNECT_TIMEOUT = 2000; private List<ClusterServer> clusterServers = new ArrayList<>(); private Channel channel; private boolean connected; private AtomicBoolean reconnecting = new AtomicBoolean(false); private Queue<JSONObject> sendQueue = new ConcurrentLinkedQueue<>(); protected String name; private String host; private int port; public NioSocketClient(List<ClusterServer> clusterServers, String name) { // Randomize the list to give a chance for a better use of the cluster Collections.shuffle(clusterServers); if (name == null || name.isEmpty()) { throw new IllegalArgumentException("name cannot be null or empty"); } this.clusterServers = clusterServers; this.name = name; ClusterServer first = clusterServers.get(0); this.host = first.host(); this.port = first.port(); if (!connect(host, port)) { throw new ConnectException("cannot initially connect to " + first.host() + ":" + first.port()); } } public NioSocketClient(List<ClusterServer> clusterServers) { this(clusterServers, "server"); } public abstract void clientConnected(); public abstract void clientReconnected(); public abstract void received(JSONObject jsonObject); public boolean connect(String host, int port) { ChannelFuture channelFuture = new Bootstrap().group(PipelineUtils.newEventLoopGroup(1)) .channel(PipelineUtils.getChannel()).handler(new ClientChannelInitializer(this)) .option(ChannelOption.TCP_NODELAY, true) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT).connect(host, port); channelFuture.awaitUninterruptibly(); channel = channelFuture.channel(); CountDownLatch countDownLatch = new CountDownLatch(1); channelFuture.addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { connected = channelFuture.isSuccess(); countDownLatch.countDown(); } }); try { countDownLatch.await(2, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } return connected; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { clientConnected(); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { reconnect(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ChannelUtil.closeOnFlush(channel); if (!(cause instanceof IOException)) { cause.printStackTrace(); } } @Override protected void channelRead0(ChannelHandlerContext ctx, JSONObject jsonObject) throws Exception { received(jsonObject); } private void addToQueue(JSONObject jsonObject) { // Only queue up to 100 messages if (sendQueue.size() < 100) { sendQueue.offer(jsonObject); } } private void reconnect() { if (!reconnecting.get()) { reconnecting.set(true); connected = false; channel.eventLoop().schedule(() -> { if (!connect(host, port)) { reconnecting.set(false); reconnect(); } else { reconnecting.set(false); clientReconnected(); try { // Give the subscriber a chance to connect first Thread.sleep(1200); } catch (InterruptedException e) { e.printStackTrace(); } //Resend the queued messages if available while (sendQueue.size() > 0) { JSONObject jsonObject = sendQueue.poll(); if (jsonObject != null) { write(jsonObject); } } } }, 1, TimeUnit.SECONDS); } } public void close(boolean force) { if (connected) { connected = false; channel.close(); if (!force) { reconnect(); } } } public void write(JSONObject jsonObject, boolean queueEnabled) { if (!channel.isActive() && queueEnabled) { addToQueue(jsonObject); return; } channel.writeAndFlush(jsonObject); } public void write(JSONObject jsonObject) { write(jsonObject, true); } public List<ClusterServer> clusterServers() { return clusterServers; } public boolean isConnected() { return connected; } }