/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.transport.socket; import java.io.IOException; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.Enumeration; import java.util.concurrent.ExecutorService; import org.fudgemsg.FudgeContext; import org.fudgemsg.FudgeMsg; import org.fudgemsg.MutableFudgeMsg; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.Lifecycle; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.transport.EndPointDescriptionProvider; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.TerminatableJob; import com.opengamma.util.ThreadUtils; /** * * */ public abstract class AbstractServerSocketProcess implements Lifecycle, EndPointDescriptionProvider { private static final Logger s_logger = LoggerFactory.getLogger(AbstractServerSocketProcess.class); private final ExecutorService _executorService; private int _portNumber; private InetAddress _bindAddress; private boolean _isDaemon = true; private ServerSocket _serverSocket; private Thread _socketAcceptThread; private SocketAcceptJob _socketAcceptJob; private boolean _started; protected AbstractServerSocketProcess() { _executorService = null; } protected AbstractServerSocketProcess(final ExecutorService executorService) { ArgumentChecker.notNull(executorService, "executorService"); _executorService = executorService; } /** * @return the portNumber */ public int getPortNumber() { return _portNumber; } /** * @param portNumber the portNumber to set */ public void setPortNumber(int portNumber) { _portNumber = portNumber; } /** * @return the bindAddress */ public InetAddress getBindAddress() { return _bindAddress; } /** * @param bindAddress the bindAddress to set */ public void setBindAddress(InetAddress bindAddress) { _bindAddress = bindAddress; } /** * @param isDaemon true if the socket accept thread should be run as a daemon thread, false otherwise */ public void setDaemon(final boolean isDaemon) { _isDaemon = isDaemon; } /** * @return true if the socket accept thread should be run as a daemon thread, false otherwise */ public boolean isDaemon() { return _isDaemon; } @Override public synchronized boolean isRunning() { return _started; } @Override public synchronized void start() { s_logger.info("Binding to {}:{}", getBindAddress(), getPortNumber()); try { // NOTE kirk 2010-05-12 -- Backlog of 50 from ServerSocket. _serverSocket = new ServerSocket(getPortNumber(), 50, getBindAddress()); if (getPortNumber() == 0) { s_logger.info("Received inbound port {}", _serverSocket.getLocalPort()); } setPortNumber(_serverSocket.getLocalPort()); } catch (IOException ioe) { throw new OpenGammaRuntimeException("Unable to bind to " + getBindAddress() + " port " + getPortNumber(), ioe); } _socketAcceptJob = new SocketAcceptJob(); _socketAcceptThread = new Thread(_socketAcceptJob, "Socket Accept Thread"); _socketAcceptThread.setDaemon(_isDaemon); _socketAcceptThread.start(); _started = true; } @Override public synchronized void stop() { _socketAcceptJob.terminate(); // Open a socket locally in case we're trapped in .accept(). try { Socket socket = new Socket(InetAddress.getLocalHost(), getPortNumber()); socket.close(); } catch (IOException e) { // Totally fine. } ThreadUtils.safeJoin(_socketAcceptThread, 60 * 1000L); try { _serverSocket.close(); } catch (IOException e) { s_logger.warn("Unable to close server socket on lifecycle stop", e); } _started = false; } protected boolean exceptionForcedByClose(final Exception e) { return (e instanceof SocketException) && "Socket closed".equals(e.getMessage()); } private class SocketAcceptJob extends TerminatableJob { @Override protected void runOneCycle() { cleanupPreAccept(); try { Socket socket = _serverSocket.accept(); // Double-check here because we sometimes open sockets just to force // termination. if (!isTerminated()) { socketOpened(socket); } } catch (IOException e) { s_logger.warn("Unable to accept a new connection", e); } } } protected void cleanupPreAccept() { } protected ExecutorService getExecutorService() { return _executorService; } protected abstract void socketOpened(Socket socket); private void loadInterfaceAddress(final NetworkInterface iface, final MutableFudgeMsg message) { final Enumeration<NetworkInterface> ni = iface.getSubInterfaces(); while (ni.hasMoreElements()) { loadInterfaceAddress(ni.nextElement(), message); } final Enumeration<InetAddress> ai = iface.getInetAddresses(); while (ai.hasMoreElements()) { final InetAddress a = ai.nextElement(); if (a.isLoopbackAddress()) { continue; } final String hostAddress = a.getHostAddress(); message.add(SocketEndPointDescriptionProvider.ADDRESS_KEY, hostAddress); s_logger.debug("Address {}/{}", iface.getName(), hostAddress); } } @Override public FudgeMsg getEndPointDescription(final FudgeContext fudgeContext) { final MutableFudgeMsg desc = fudgeContext.newMessage(); desc.add(SocketEndPointDescriptionProvider.TYPE_KEY, SocketEndPointDescriptionProvider.TYPE_VALUE); final InetAddress addr = _serverSocket.getInetAddress(); if (addr != null) { if (addr.isAnyLocalAddress()) { try { Enumeration<NetworkInterface> ni = NetworkInterface.getNetworkInterfaces(); while (ni.hasMoreElements()) { loadInterfaceAddress(ni.nextElement(), desc); } } catch (IOException e) { s_logger.warn("Error resolving local addresses", e); } } else { desc.add(SocketEndPointDescriptionProvider.ADDRESS_KEY, addr.getHostAddress()); } } desc.add(SocketEndPointDescriptionProvider.PORT_KEY, _serverSocket.getLocalPort()); return desc; } }