/* Copyright (c) 2011 Danish Maritime Authority. * * 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 dk.dma.ais.reader; import static java.util.Objects.requireNonNull; import java.io.IOException; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.net.HostAndPort; import dk.dma.ais.sentence.Abk; import java.util.function.Consumer; /** * Thread class for reading AIS messages from a TCP stream. * * Connection handling is fail tolerant. Connection error is handled by waiting a specified amount of time before doing * a re-connect (default 5 sec). * * Handlers for the parsed AIS messages must be registered with registerHanlder() method. * * Example of use: * * IAisHandler handler = new SomeHandler(); * * AisTcpReader aisReader = new AisTcpReader("localhost", 4001); aisReader.registerHandler(handler); * aisReader.addProprietaryFactory(new GatehouseFactory()); aisReader.start(); aisReader.join(); * */ public class AisTcpReader extends AisReader { private static final Logger LOG = LoggerFactory.getLogger(AisTcpReader.class); protected volatile long reconnectInterval = 5000; // Default 5 sec protected OutputStream outputStream; protected final AtomicReference<Socket> clientSocket = new AtomicReference<>(new Socket()); protected volatile int timeout = 10; List<HostAndPort> hosts = new ArrayList<>(); int currentHostIndex = -1; // /** // * Constructor with hostname and port // * // * @param hostname // * @param port // */ // public AisTcpReader(String hostname, int port) { // hostAndPort = HostAndPort.fromParts(hostname, port); // } /** * Add another host/port on the form host:port * * @param hostPort */ void addHostPort(HostAndPort hostAndPort) { requireNonNull(hostAndPort); hosts.add(hostAndPort); currentHostIndex++; } /** * Main read loop */ @Override public void run() { while (!isShutdown()) { try { disconnect(); connect(); readLoop(clientSocket.get().getInputStream()); } catch (IOException e) { if (isShutdown() || isInterrupted()) { return; } LOG.error("Source communication failed: " + e.getMessage() + ": host:port: " + currentHost() + " Retry in " + reconnectInterval / 1000 + " seconds"); if (!isShutdown()) { try { // LOG.info("Starting to sleep " + getId() + " " + shutdown); shutdownLatch.await(reconnectInterval, TimeUnit.MILLISECONDS); // LOG.info("Wake up " + getId() + " " + shutdown); } catch (InterruptedException ignored) { LOG.info("Stopping reader"); return; } } currentHostIndex = (currentHostIndex + 1) % hosts.size(); } } } @Override public void stopReader() { super.stopReader(); try { // Close socket if open clientSocket.get().close(); } catch (IOException ignored) {} } protected void connect() throws IOException { try { LOG.info("Connecting to source " + currentHost()); clientSocket.set(new Socket()); InetSocketAddress address = new InetSocketAddress(currentHost().getHostText(), currentHost().getPort()); clientSocket.get().connect(address); if (timeout > 0) { clientSocket.get().setSoTimeout(timeout * 1000); } clientSocket.get().setKeepAlive(true); outputStream = clientSocket.get().getOutputStream(); LOG.info("Connected to source " + currentHost()); } catch (UnknownHostException e) { LOG.error("Unknown host: " + currentHost().getHostText() + ": " + e.getMessage()); throw e; } catch (IOException e) { if (!isShutdown()) { LOG.error("Could not connect to: " + currentHost() + ": " + e.getMessage()); } throw e; } } protected void disconnect() { if (getStatus() == Status.CONNECTED) { try { LOG.info("Disconnecting source " + currentHost()); clientSocket.get().close(); } catch (IOException ignored) {} } } /** * Send sendRequest to the socket output stream and get result sent to resultListener. * * @param sendRequest * @param resultListener * @throws SendException */ @Override public void send(SendRequest sendRequest, Consumer<Abk> resultListener) throws SendException { doSend(sendRequest, resultListener, outputStream); } public Status getStatus() { return clientSocket.get().isConnected() ? Status.CONNECTED : Status.DISCONNECTED; } /** * Get the interval in milliseconds between re-connect attempts * * @return reconnectInterval */ public long getReconnectInterval() { return reconnectInterval; } /** * Set the interval in milliseconds between re-connect attempts * * @param reconnectInterval */ public void setReconnectInterval(long reconnectInterval) { this.reconnectInterval = reconnectInterval; } /** * Get the number of hosts * * @return */ public int getHostCount() { return hosts.size(); } /** * Get socket read timeout * * @return */ public int getTimeout() { return timeout; } /** * Set socket read timeout * * @param timeout */ public void setTimeout(int timeout) { this.timeout = timeout; } /** * Returns the current hostname * * @return */ public String getHostname() { return currentHost().getHostText(); } HostAndPort currentHost() { return hosts.get(currentHostIndex); } /** * Get port * * @return */ public int getPort() { return currentHost().getPort(); } public String toString() { return "AisTcpReader [sourceId = " + getSourceId() + ", current host=" + currentHost() + "]"; } }