/* * TeleStax, Open Source Cloud Communications * Copyright 2011-2016, TeleStax Inc. and individual contributors * by the @authors tag. * * This program is free software: you can redistribute it and/or modify * under the terms of the GNU Affero 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/> */ package org.jdiameter.client.impl.transport.tcp.netty; import java.io.IOException; import java.net.InetSocketAddress; import org.jdiameter.client.api.IMessage; import org.jdiameter.client.api.parser.IMessageParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.concurrent.DefaultEventExecutorGroup; import io.netty.util.concurrent.EventExecutorGroup; /** * * @author <a href="mailto:jqayyum@gmail.com"> Jehanzeb Qayyum </a> */ public class TCPTransportClient { protected EventLoopGroup workerGroup; protected EventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup(1); protected Channel channel; protected TCPClientConnection parentConnection; protected InetSocketAddress destAddress; protected InetSocketAddress sourceAddress; // TODO: what? protected String socketDescription; protected static final Logger logger = LoggerFactory.getLogger(TCPClientConnection.class); protected IMessageParser parser; protected static final int CONNECT_TIMEOUT = 500; // mills protected TCPTransportClient(TCPClientConnection parentConnection, IMessageParser parser) { if (parentConnection == null) { throw new IllegalArgumentException("Parent connection cannot be null"); } this.parentConnection = parentConnection; if (parser == null) { throw new IllegalArgumentException("Parser cannot be null"); } this.parser = parser; } public TCPTransportClient(TCPClientConnection parentConnection, IMessageParser parser, InetSocketAddress destAddress, InetSocketAddress sourceAddress) { this(parentConnection, parser); logger.debug("Client only connection"); if (destAddress == null && sourceAddress == null) { throw new IllegalArgumentException("Either Destination or Source address is required"); } if (sourceAddress != null) { this.sourceAddress = sourceAddress; } if (destAddress != null) { this.destAddress = destAddress; this.socketDescription = destAddress.toString(); } } public TCPTransportClient(TCPClientConnection parentConnection, IMessageParser parser, Channel channel) { this(parentConnection, parser); logger.debug("Server only connection"); if (channel == null) { throw new IllegalArgumentException("Channel cannot be null"); } this.channel = channel; ChannelPipeline pipeline = this.channel.pipeline(); pipeline.addLast("decoder", new DiameterMessageDecoder(parentConnection, parser)); pipeline.addLast("encoder", new DiameterMessageEncoder(parser)); pipeline.addLast(eventExecutorGroup, "msgHandler", new DiameterMessageHandler(parentConnection)); this.destAddress = (InetSocketAddress) this.channel.remoteAddress(); } public void start() throws InterruptedException { logger.debug("Starting TCP Transport on [{}]", socketDescription); if (isConnected()) { logger.debug("TCP Transport already started, [{}]", socketDescription); return; } this.workerGroup = new NioEventLoopGroup(); Bootstrap bootstrap = new Bootstrap().group(workerGroup).channel(NioSocketChannel.class) .option(ChannelOption.SO_KEEPALIVE, true).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("decoder", new DiameterMessageDecoder(parentConnection, parser)); pipeline.addLast("encoder", new DiameterMessageEncoder(parser)); pipeline.addLast(eventExecutorGroup, "msgHandler", new DiameterMessageHandler(parentConnection)); } }); this.channel = bootstrap.remoteAddress(destAddress).connect().sync().channel(); logger.debug("TCP Transport connected successfully, [{}]", socketDescription); parentConnection.onConnected(); } public void stop() { logger.debug("Stopping TCP Transport, [{}]", socketDescription); if (!isConnected()) { logger.debug("Already stoppped TCP Transport, [{}]", socketDescription); return; } closeChannel(); closeWorkerGroup(); closeEventExecutorGroup(); logger.debug("Transport is stopped [{}]", socketDescription); } private void closeEventExecutorGroup() { if (eventExecutorGroup != null) { try { eventExecutorGroup.shutdownGracefully().sync(); } catch (InterruptedException e) { logger.error("Error stopping socket " + socketDescription, e); } eventExecutorGroup = null; } } private void closeWorkerGroup() { if (workerGroup != null) { try { workerGroup.shutdownGracefully().sync(); } catch (InterruptedException e) { logger.error("Error stopping socket " + socketDescription, e); } workerGroup = null; } } private void closeChannel() { if (channel != null) { try { channel.closeFuture().sync(); } catch (InterruptedException e) { logger.error("Error stopping socket " + socketDescription, e); } channel = null; } } public void release() throws InterruptedException, IOException { logger.debug("Releasing TCP Transport, [{}]", socketDescription); stop(); destAddress = null; sourceAddress = null; } public void sendMessage(IMessage message) { if (!isConnected()) { throw new IllegalStateException("TCP transport is stopped on socket " + socketDescription); } channel.writeAndFlush(message); } public String toString() { StringBuffer buffer = new StringBuffer(); buffer.append("Transport to "); if (this.destAddress != null) { buffer.append(this.destAddress.getHostName()); buffer.append(":"); buffer.append(this.destAddress.getPort()); } else { buffer.append("null"); } buffer.append("@"); buffer.append(super.toString()); return buffer.toString(); } public TCPClientConnection getParent() { return parentConnection; } public InetSocketAddress getDestAddress() { return this.destAddress; } boolean isConnected() { return channel != null && channel.isActive(); } }