Java tutorial
/* * This file is part of FFMQ. * * FFMQ is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * FFMQ 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with FFMQ; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package net.timewalker.ffmq4.listeners.tcp.io; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.security.KeyStore; import javax.jms.JMSException; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLServerSocket; import net.timewalker.ffmq4.FFMQCoreSettings; import net.timewalker.ffmq4.FFMQException; import net.timewalker.ffmq4.FFMQServerSettings; import net.timewalker.ffmq4.jmx.JMXAgent; import net.timewalker.ffmq4.listeners.ClientProcessor; import net.timewalker.ffmq4.listeners.tcp.AbstractTcpClientListener; import net.timewalker.ffmq4.local.FFMQEngine; import net.timewalker.ffmq4.transport.PacketTransport; import net.timewalker.ffmq4.transport.PacketTransportException; import net.timewalker.ffmq4.transport.PacketTransportType; import net.timewalker.ffmq4.transport.tcp.io.TcpPacketTransport; import net.timewalker.ffmq4.utils.Settings; import net.timewalker.ffmq4.utils.id.UUIDProvider; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * TcpNetworkServer */ public final class TcpListener extends AbstractTcpClientListener implements Runnable, TcpListenerMBean { private static final Log log = LogFactory.getLog(TcpListener.class); // Runtime private ServerSocket serverSocket; private Thread listenerThread; private boolean stopRequired = false; private boolean usingSSL; /** * Constructor */ public TcpListener(FFMQEngine engine, String listenAddr, int port, Settings settings) { this(engine, listenAddr, port, settings, null); } /** * Constructor */ public TcpListener(FFMQEngine engine, String listenAddr, int port, Settings settings, JMXAgent jmxAgent) { super(engine, settings, jmxAgent, listenAddr, port); this.usingSSL = settings.getBooleanProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_ENABLED, false); } /* (non-Javadoc) * @see net.timewalker.ffmq4.listeners.AbstractListener#start() */ @Override public synchronized void start() throws JMSException { if (started) return; log.info("Starting listener [" + getName() + "]"); stopRequired = false; initServerSocket(); listenerThread = new Thread(this, "FFMQ-TCP-Server-" + serverSocket.getLocalPort()); listenerThread.start(); started = true; } /* (non-Javadoc) * @see net.timewalker.ffmq4.listeners.Listener#getName() */ @Override public String getName() { return (usingSSL ? PacketTransportType.TCPS : PacketTransportType.TCP) + "-" + listenAddr + "-" + listenPort; } private void initServerSocket() throws JMSException { try { InetAddress bindAddress = getBindAddress(); int tcpBackLog = settings.getIntProperty(FFMQServerSettings.LISTENER_TCP_BACK_LOG, DEFAULT_TCP_BACK_LOG); log.debug("TCP back log = " + tcpBackLog); serverSocket = createServerSocket(listenPort, tcpBackLog, bindAddress, usingSSL); serverSocket.setReuseAddress(true); } catch (JMSException e) { throw e; } catch (Exception e) { throw new FFMQException("Could not initialize server socket", "NETWORK_ERROR", e); } } private void closeServerSocket() { // Close the listen socket try { if (serverSocket != null) serverSocket.close(); } catch (IOException e) { log.error("Could not close server socket", e); } finally { serverSocket = null; } } /* * (non-Javadoc) * @see net.timewalker.ffmq4.utils.concurrent.SynchronizableThread#run() */ @Override public void run() { try { log.debug("Waiting for clients [" + getName() + "]"); while (!stopRequired) { Socket clientSocket = serverSocket.accept(); // Enforce listener capacity int activeClients = getActiveClients(); if (activeClients >= listenerCapacity) { log.warn("Listener is full (max=" + listenerCapacity + "), dropping new connection attempt."); try { clientSocket.close(); } catch (Exception e) { log.error("Cannot close incoming connection", e); } continue; } String clientId = UUIDProvider.getInstance().getShortUUID(); log.debug("Accepting a new client from " + clientSocket.getInetAddress().getHostAddress() + " (" + (activeClients + 1) + ") : " + clientId + " [" + getName() + "]"); try { ClientProcessor processor = createProcessor(clientId, clientSocket); registerClient(processor); processor.start(); } catch (Exception e) { try { clientSocket.close(); } catch (Exception ex) { log.error("Could not close socket [" + getName() + "]", ex); } log.error("Client failed : " + clientId + " [" + getName() + "]", e); } } } catch (Exception e) { if (!stopRequired) log.fatal("Server failed [" + getName() + "]", e); } } /** * Create a new processor */ protected ClientProcessor createProcessor(String clientId, Socket clientSocket) throws PacketTransportException { PacketTransport transport = new TcpPacketTransport(clientId, clientSocket, settings); ClientProcessor clientProcessor = new ClientProcessor(clientId, this, localEngine, transport); return clientProcessor; } /* (non-Javadoc) * @see net.timewalker.ffmq4.listeners.AbstractListener#stop() */ @Override public synchronized void stop() { if (!started) return; log.info("Stopping listener [" + getName() + "]"); stopRequired = true; // Close the listen socket closeServerSocket(); // Wait for listener thread to stop try { if (listenerThread != null) listenerThread.join(); } catch (InterruptedException e) { log.error("Wait for listener thread termination was interrupted"); } finally { listenerThread = null; } // Then stop remaining clients closeRemainingClients(); started = false; } private ServerSocket createServerSocket(int port, int tcpBackLog, InetAddress localAddr, boolean useSSL) throws JMSException { try { if (useSSL) { SSLServerSocket socket = (SSLServerSocket) createSSLContext().getServerSocketFactory() .createServerSocket(port, tcpBackLog, localAddr); socket.setNeedClientAuth(false); return socket; } else return new ServerSocket(port, tcpBackLog, localAddr); } catch (Exception e) { throw new FFMQException("Cannot create server socket", "NETWORK_ERROR", e); } } private SSLContext createSSLContext() throws JMSException { try { String sslProtocol = settings.getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_PROTOCOL, "SSLv3"); String keyManagerAlgorithm = settings .getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_KEYMANAGER_ALGORITHM, "SunX509"); String keyStoreType = settings.getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_KEYSTORE_TYPE, "JKS"); String keyStorePath = settings.getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_KEYSTORE_PATH, "../conf/server-keystore.jks"); String keyStorePass = settings.getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_KEYSTORE_PASWORD, "ffmqpass"); String keyPass = settings.getStringProperty(FFMQCoreSettings.TRANSPORT_TCP_SSL_KEYSTORE_KEY_PASSWORD, "ffmqpass"); SSLContext sslContext = SSLContext.getInstance(sslProtocol); log.debug("Created an SSL context : protocol=[" + sslContext.getProtocol() + "] provider=[" + sslContext.getProvider() + "]"); // Load available keys KeyManager[] keyManagers; File keyStoreFile = new File(keyStorePath); if (!keyStoreFile.canRead()) throw new FFMQException("Cannot read keystore file : " + keyStoreFile.getAbsolutePath(), "FS_ERROR"); KeyStore ks = KeyStore.getInstance(keyStoreType); log.debug("Created keystore : type=[" + ks.getType() + "] provider=[" + ks.getProvider() + "]"); char ksPass[] = keyStorePass.toCharArray(); char ctPass[] = keyPass.toCharArray(); log.debug("Loading keystore from " + keyStoreFile.getAbsolutePath()); InputStream kis = new FileInputStream(keyStoreFile); ks.load(kis, ksPass); kis.close(); KeyManagerFactory kmf = KeyManagerFactory.getInstance(keyManagerAlgorithm); log.debug("Created KeyManagerFactory : algorithm=[" + kmf.getAlgorithm() + "] provider=[" + kmf.getProvider() + "]"); log.debug("Initializing KeyManagerFactory with keystore ..."); kmf.init(ks, ctPass); keyManagers = kmf.getKeyManagers(); sslContext.init(keyManagers, null, null); return sslContext; } catch (JMSException e) { throw e; } catch (Exception e) { throw new FFMQException("Cannot create SSL context", "NETWORK_ERROR", e); } } }