/* * Copyright (c) 2008-2017, Hazelcast, Inc. All Rights Reserved. * * 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 com.hazelcast.nio.tcp; import com.hazelcast.internal.metrics.DiscardableMetricsProvider; import com.hazelcast.internal.metrics.MetricsRegistry; import com.hazelcast.internal.metrics.Probe; import com.hazelcast.internal.networking.Channel; import com.hazelcast.internal.networking.ChannelConnection; import com.hazelcast.internal.networking.ChannelReader; import com.hazelcast.internal.networking.ChannelWriter; import com.hazelcast.internal.networking.EventLoopGroup; import com.hazelcast.internal.networking.OutboundFrame; import com.hazelcast.logging.ILogger; import com.hazelcast.nio.Address; import com.hazelcast.nio.ConnectionType; import com.hazelcast.nio.IOService; import java.io.EOFException; import java.net.InetAddress; import java.net.InetSocketAddress; import java.nio.channels.CancelledKeyException; import java.util.concurrent.atomic.AtomicBoolean; /** * The Tcp/Ip implementation of the {@link com.hazelcast.nio.Connection}. * <p> * A {@link TcpIpConnection} is not responsible for reading or writing data to a socket, this is done through: * <ol> * <li>{@link ChannelReader}: which care of reading from the socket and feeding it into the system/li> * <li>{@link ChannelWriter}: which care of writing data to the socket.</li> * </ol> * * @see EventLoopGroup */ @SuppressWarnings("checkstyle:methodcount") public final class TcpIpConnection implements ChannelConnection, DiscardableMetricsProvider { private final Channel channel; private final ChannelReader channelReader; private final ChannelWriter channelWriter; private final TcpIpConnectionManager connectionManager; private final AtomicBoolean alive = new AtomicBoolean(true); private final ILogger logger; private final int connectionId; private final IOService ioService; private Address endPoint; private TcpIpConnectionErrorHandler errorHandler; private volatile ConnectionType type = ConnectionType.NONE; private volatile Throwable closeCause; private volatile String closeReason; public TcpIpConnection(TcpIpConnectionManager connectionManager, int connectionId, Channel channel, EventLoopGroup eventLoopGroup) { this.connectionId = connectionId; this.connectionManager = connectionManager; this.ioService = connectionManager.getIoService(); this.logger = ioService.getLoggingService().getLogger(TcpIpConnection.class); this.channel = channel; this.channelWriter = eventLoopGroup.newSocketWriter(this); this.channelReader = eventLoopGroup.newSocketReader(this); } @Override public void provideMetrics(MetricsRegistry registry) { String metricsId = channel.getLocalSocketAddress() + "->" + channel.getRemoteSocketAddress(); registry.scanAndRegister(channelWriter, "tcp.connection[" + metricsId + "].out"); registry.scanAndRegister(channelReader, "tcp.connection[" + metricsId + "].in"); } @Override public void discardMetrics(MetricsRegistry registry) { registry.deregister(channelReader); registry.deregister(channelWriter); } public ChannelReader getChannelReader() { return channelReader; } public ChannelWriter getChannelWriter() { return channelWriter; } @Override public Channel getChannel() { return channel; } @Override public ConnectionType getType() { return type; } @Override public void setType(ConnectionType type) { if (this.type == ConnectionType.NONE) { this.type = type; } } @Probe private int getConnectionType() { ConnectionType t = type; return t == null ? -1 : t.ordinal(); } public TcpIpConnectionManager getConnectionManager() { return connectionManager; } @Override public InetAddress getInetAddress() { return channel.socket().getInetAddress(); } @Override public int getPort() { return channel.socket().getPort(); } @Override public InetSocketAddress getRemoteSocketAddress() { return (InetSocketAddress) channel.getRemoteSocketAddress(); } @Override public boolean isAlive() { return alive.get(); } @Override public long lastWriteTimeMillis() { return channelWriter.lastWriteTimeMillis(); } @Override public long lastReadTimeMillis() { return channelReader.lastReadTimeMillis(); } @Override public Address getEndPoint() { return endPoint; } public void setEndPoint(Address endPoint) { this.endPoint = endPoint; } public void setErrorHandler(TcpIpConnectionErrorHandler errorHandler) { this.errorHandler = errorHandler; } public int getConnectionId() { return connectionId; } @Override public boolean isClient() { ConnectionType t = type; return t != null && t != ConnectionType.NONE && t.isClient(); } /** * Starts this connection. * <p> * Starting means that the connection is going to register itself to listen to incoming traffic. */ public void start() { channelReader.init(); } @Override public boolean write(OutboundFrame frame) { if (!alive.get()) { if (logger.isFinestEnabled()) { logger.finest("Connection is closed, won't write packet -> " + frame); } return false; } channelWriter.write(frame); return true; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof TcpIpConnection)) { return false; } TcpIpConnection that = (TcpIpConnection) o; return connectionId == that.getConnectionId(); } @Override public int hashCode() { return connectionId; } @Override public void close(String reason, Throwable cause) { if (!alive.compareAndSet(true, false)) { // it is already closed. return; } this.closeCause = cause; this.closeReason = reason; logClose(); try { if (channel != null && channel.isOpen()) { channelReader.close(); channelWriter.close(); channel.close(); } } catch (Exception e) { logger.warning(e); } connectionManager.onConnectionClose(this); ioService.onDisconnect(endPoint, cause); if (cause != null && errorHandler != null) { errorHandler.onError(cause); } } private void logClose() { String message = toString() + " closed. Reason: "; if (closeReason != null) { message += closeReason; } else if (closeCause != null) { message += closeCause.getClass().getName() + "[" + closeCause.getMessage() + "]"; } else { message += "Socket explicitly closed"; } if (ioService.isActive()) { if (closeCause == null || closeCause instanceof EOFException || closeCause instanceof CancelledKeyException) { logger.info(message); } else { logger.warning(message, closeCause); } } else { if (closeCause == null) { logger.finest(message); } else { logger.finest(message, closeCause); } } } @Override public Throwable getCloseCause() { return closeCause; } @Override public String getCloseReason() { if (closeReason == null) { return closeCause == null ? null : closeCause.getMessage(); } else { return closeReason; } } @Override public String toString() { return "Connection[id=" + connectionId + ", " + channel.getLocalSocketAddress() + "->" + channel.getRemoteSocketAddress() + ", endpoint=" + endPoint + ", alive=" + alive + ", type=" + type + "]"; } }