me.jtalk.socketconnector.io.TCPManager.java Source code

Java tutorial

Introduction

Here is the source code for me.jtalk.socketconnector.io.TCPManager.java

Source

/*
 * Copyright (C) 2015 Jtalk
 *
 * This program 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 me.jtalk.socketconnector.io;

import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
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.io.Closeable;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.resource.ResourceException;
import javax.resource.spi.EISSystemException;
import me.jtalk.socketconnector.api.ConnectionClosedException;
import me.jtalk.socketconnector.SocketResourceAdapter;
import me.jtalk.socketconnector.TCPActivationSpec;

public class TCPManager implements Closeable {

    private static final Logger log = Logger.getLogger(TCPManager.class.getName());
    private static final long SHUTDOWN_TIMEOUT_SEC = 60;

    private SocketResourceAdapter parent;
    private final long id;

    private final EventLoopGroup listeners;
    private final EventLoopGroup workers;
    private final ServerBootstrap server;
    private final Bootstrap client;

    private final ConcurrentHashMap<Long, ConnectionContext> connections = new ConcurrentHashMap<>();
    private final AtomicLong ids = new AtomicLong(0);

    public TCPManager(SocketResourceAdapter parent, long id, TCPActivationSpec spec) throws ResourceException {

        this.parent = parent;
        this.id = id;

        ThreadFactory factory = new DaemonThreadFactory();
        this.listeners = new NioEventLoopGroup(spec.getListnerThreadsCount(), factory);
        this.workers = new NioEventLoopGroup(spec.getReceiverThreadsCount(), factory);

        this.server = this.instantiateServer(spec);
        this.client = this.instantiateClient();
    }

    public long connect(InetSocketAddress target) throws ResourceException {
        try {
            log.finest(String.format("Connection initialization to %s: starting", target));

            ChannelFuture completed = this.client.connect(target).sync();
            if (!completed.isSuccess()) {
                throw new EISSystemException("Connection failed", completed.cause());
            }
            Receiver handler = completed.channel().pipeline().get(Receiver.class);
            long connId = handler.getId();

            return connId;

        } catch (InterruptedException e) {
            throw new EISSystemException("Execution interrupted during connecting to remote client", e);
        }
    }

    public long listen(InetSocketAddress local) throws ResourceException {
        try {
            log.finest(String.format("Listening to %s: starting", local));

            ChannelFuture completed = this.server.bind(local).sync();
            if (!completed.isSuccess()) {
                throw new EISSystemException("Listening failed", completed.cause());
            }
            long connId = this.ids.incrementAndGet();

            log.finest(String.format("Listening to %s: connection id %d", local, connId));
            this.register(connId, completed.channel(), true);

            return connId;

        } catch (InterruptedException e) {
            throw new EISSystemException("Execution interrupted during listening setup", e);
        }
    }

    public void send(long id, ByteBuffer data) throws ResourceException {
        ConnectionContext ctx = this.connections.get(id);
        if (ctx == null) {
            throw new ConnectionClosedException("Connection is closed");
        }
        Channel output = ctx.channel;
        log.finest(String.format("Data sending to id %d, %d bytes", id, data.remaining()));
        output.writeAndFlush(Unpooled.wrappedBuffer(data)).addListener(f -> {
            if (f.isSuccess()) {
                log.finest(String.format("Data sent to id %d", id));
            } else {
                log.log(Level.FINE, "Error while sending data to id {0}: {1}", new Object[] { id, f.cause() });
            }
        });
    }

    public boolean close(long id) {
        log.finest(String.format("Connection closing for id %d: requested", id));
        final ConnectionContext ctx = this.connections.get(id);
        if (ctx == null) {
            log.finest(String.format("Connection closing for id %d: no context", id));
            return false;
        }
        ctx.channel.disconnect().addListener(f -> {
            if (!f.isSuccess()) {
                TCPManager.log.log(Level.SEVERE, "Disconnection failed due to error", f.cause());
            } else {
                TCPManager.log.finest(String.format("Connection closing for id %d: closed", id));
            }
        });
        log.finest(String.format("Connection closing for id %d: future fired", id));
        return true;
    }

    @Override
    public void close() {
        log.finest("Closing TCPManager with all connections");

        this.listeners.shutdownGracefully(0, SHUTDOWN_TIMEOUT_SEC, TimeUnit.SECONDS);
        this.workers.shutdownGracefully(0, SHUTDOWN_TIMEOUT_SEC, TimeUnit.SECONDS);
        this.connections.clear();
        this.parent = null;

        log.finest("TCPManager successfuly closed");
    }

    public boolean isListening(long id) throws ResourceException {
        ConnectionContext ctx = this.connections.get(id);
        if (ctx == null) {
            throw new ConnectionClosedException("Connection is closed");
        }
        return ctx.listening;
    }

    void register(long id, Channel channel, boolean listening) {

        log.finest(String.format("Connection established for id %d: creating new context", id));
        ConnectionContext context = new ConnectionContext(channel, channel.localAddress(), channel.remoteAddress(),
                listening);

        this.connections.put(id, context);

        log.finest(String.format("Connection established for id %d: connection added", id));
    }

    // Receiver callbacks
    void connectionShutdown(long id, Throwable cause) {

        ConnectionContext ctx = this.connections.remove(id);

        SocketAddress local = null;
        SocketAddress remote = null;
        if (ctx != null) {
            local = ctx.local;
            remote = ctx.remote;
        }
        this.parent.notifyShutdown(this.id, id, local, remote, cause);
    }

    void dataReceived(long id, byte[] data) {
        ConnectionContext ctx = this.connections.get(id);
        if (ctx == null) {
            // Drop data from closed connection
            log.finest(String.format("No context for id %s", id));
            return;
        }
        log.finest(String.format("Context for id %s found, data will be received", id));
        this.parent.notifyReceived(this.id, id, data, ctx.local, ctx.remote);
    }

    private ServerBootstrap instantiateServer(TCPActivationSpec spec) throws ResourceException {

        ServerBootstrap newServer = new ServerBootstrap();
        newServer.group(this.listeners, this.workers);
        newServer.channel(NioServerSocketChannel.class);
        newServer.childHandler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel c) throws Exception {
                c.pipeline().addLast(new Receiver(TCPManager.this, TCPManager.this.ids.incrementAndGet()))
                        .addLast(new Sender());
            }
        });
        newServer.option(ChannelOption.SO_KEEPALIVE, true);
        newServer.option(ChannelOption.SO_BACKLOG, spec.getBacklog());
        newServer.option(ChannelOption.TCP_NODELAY, true);

        return newServer;
    }

    private Bootstrap instantiateClient() {
        Bootstrap newClient = new Bootstrap();
        newClient.group(this.workers);
        newClient.channel(NioSocketChannel.class);
        newClient.handler(new ChannelInitializer<SocketChannel>() {

            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new Receiver(TCPManager.this, TCPManager.this.ids.incrementAndGet()))
                        .addLast(new Sender());
            }
        });
        newClient.option(ChannelOption.SO_KEEPALIVE, true);

        return newClient;
    }
}